django-nativemojo 0.1.10__py3-none-any.whl → 0.1.16__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_nativemojo-0.1.16.dist-info/METADATA +138 -0
- django_nativemojo-0.1.16.dist-info/RECORD +302 -0
- mojo/__init__.py +1 -1
- mojo/apps/account/management/__init__.py +5 -0
- mojo/apps/account/management/commands/__init__.py +6 -0
- mojo/apps/account/management/commands/serializer_admin.py +651 -0
- mojo/apps/account/migrations/0004_user_avatar.py +20 -0
- mojo/apps/account/migrations/0005_group_last_activity.py +18 -0
- mojo/apps/account/migrations/0006_add_device_tracking_models.py +72 -0
- mojo/apps/account/migrations/0007_delete_userdevicelocation.py +16 -0
- mojo/apps/account/migrations/0008_userdevicelocation.py +33 -0
- mojo/apps/account/migrations/0009_geolocatedip_subnet.py +18 -0
- mojo/apps/account/migrations/0010_group_avatar.py +20 -0
- mojo/apps/account/migrations/0011_user_org_registereddevice_pushconfig_and_more.py +118 -0
- mojo/apps/account/migrations/0012_remove_pushconfig_apns_key_file_and_more.py +21 -0
- mojo/apps/account/migrations/0013_pushconfig_test_mode_alter_pushconfig_apns_enabled_and_more.py +28 -0
- mojo/apps/account/migrations/0014_notificationdelivery_data_payload_and_more.py +48 -0
- mojo/apps/account/models/__init__.py +2 -0
- mojo/apps/account/models/device.py +281 -0
- mojo/apps/account/models/group.py +319 -15
- mojo/apps/account/models/member.py +29 -5
- mojo/apps/account/models/push/__init__.py +4 -0
- mojo/apps/account/models/push/config.py +112 -0
- mojo/apps/account/models/push/delivery.py +93 -0
- mojo/apps/account/models/push/device.py +66 -0
- mojo/apps/account/models/push/template.py +99 -0
- mojo/apps/account/models/user.py +369 -19
- mojo/apps/account/rest/__init__.py +2 -0
- mojo/apps/account/rest/device.py +39 -0
- mojo/apps/account/rest/group.py +9 -0
- mojo/apps/account/rest/push.py +187 -0
- mojo/apps/account/rest/user.py +100 -6
- mojo/apps/account/services/__init__.py +1 -0
- mojo/apps/account/services/push.py +363 -0
- mojo/apps/aws/migrations/0001_initial.py +206 -0
- mojo/apps/aws/migrations/0002_emaildomain_can_recv_emaildomain_can_send_and_more.py +28 -0
- mojo/apps/aws/migrations/0003_mailbox_is_domain_default_mailbox_is_system_default_and_more.py +31 -0
- mojo/apps/aws/migrations/0004_s3bucket.py +39 -0
- mojo/apps/aws/migrations/0005_alter_emaildomain_region_delete_s3bucket.py +21 -0
- mojo/apps/aws/models/__init__.py +19 -0
- mojo/apps/aws/models/email_attachment.py +99 -0
- mojo/apps/aws/models/email_domain.py +218 -0
- mojo/apps/aws/models/email_template.py +132 -0
- mojo/apps/aws/models/incoming_email.py +197 -0
- mojo/apps/aws/models/mailbox.py +288 -0
- mojo/apps/aws/models/sent_message.py +175 -0
- mojo/apps/aws/rest/__init__.py +7 -0
- mojo/apps/aws/rest/email.py +33 -0
- mojo/apps/aws/rest/email_ops.py +183 -0
- mojo/apps/aws/rest/messages.py +32 -0
- mojo/apps/aws/rest/s3.py +64 -0
- mojo/apps/aws/rest/send.py +101 -0
- mojo/apps/aws/rest/sns.py +403 -0
- mojo/apps/aws/rest/templates.py +19 -0
- mojo/apps/aws/services/__init__.py +32 -0
- mojo/apps/aws/services/email.py +390 -0
- mojo/apps/aws/services/email_ops.py +548 -0
- mojo/apps/docit/__init__.py +6 -0
- mojo/apps/docit/markdown_plugins/syntax_highlight.py +25 -0
- mojo/apps/docit/markdown_plugins/toc.py +12 -0
- mojo/apps/docit/migrations/0001_initial.py +113 -0
- mojo/apps/docit/migrations/0002_alter_book_modified_by_alter_page_modified_by.py +26 -0
- mojo/apps/docit/migrations/0003_alter_book_group.py +20 -0
- mojo/apps/docit/models/__init__.py +17 -0
- mojo/apps/docit/models/asset.py +231 -0
- mojo/apps/docit/models/book.py +227 -0
- mojo/apps/docit/models/page.py +319 -0
- mojo/apps/docit/models/page_revision.py +203 -0
- mojo/apps/docit/rest/__init__.py +10 -0
- mojo/apps/docit/rest/asset.py +17 -0
- mojo/apps/docit/rest/book.py +22 -0
- mojo/apps/docit/rest/page.py +22 -0
- mojo/apps/docit/rest/page_revision.py +17 -0
- mojo/apps/docit/services/__init__.py +11 -0
- mojo/apps/docit/services/docit.py +315 -0
- mojo/apps/docit/services/markdown.py +44 -0
- mojo/apps/fileman/README.md +8 -8
- mojo/apps/fileman/backends/base.py +76 -70
- mojo/apps/fileman/backends/filesystem.py +86 -86
- mojo/apps/fileman/backends/s3.py +409 -108
- mojo/apps/fileman/migrations/0001_initial.py +106 -0
- mojo/apps/fileman/migrations/0002_filemanager_parent_alter_filemanager_max_file_size.py +24 -0
- mojo/apps/fileman/migrations/0003_remove_file_fileman_fil_upload__c4bc35_idx_and_more.py +25 -0
- mojo/apps/fileman/migrations/0004_remove_file_original_filename_and_more.py +39 -0
- mojo/apps/fileman/migrations/0005_alter_file_upload_token.py +18 -0
- mojo/apps/fileman/migrations/0006_file_download_url_filemanager_forever_urls.py +23 -0
- mojo/apps/fileman/migrations/0007_remove_filemanager_forever_urls_and_more.py +22 -0
- mojo/apps/fileman/migrations/0008_file_category.py +18 -0
- mojo/apps/fileman/migrations/0009_rename_file_path_file_storage_file_path.py +18 -0
- mojo/apps/fileman/migrations/0010_filerendition.py +33 -0
- mojo/apps/fileman/migrations/0011_alter_filerendition_original_file.py +19 -0
- mojo/apps/fileman/models/__init__.py +1 -5
- mojo/apps/fileman/models/file.py +240 -58
- mojo/apps/fileman/models/manager.py +427 -31
- mojo/apps/fileman/models/rendition.py +118 -0
- mojo/apps/fileman/renderer/__init__.py +111 -0
- mojo/apps/fileman/renderer/audio.py +403 -0
- mojo/apps/fileman/renderer/base.py +205 -0
- mojo/apps/fileman/renderer/document.py +404 -0
- mojo/apps/fileman/renderer/image.py +222 -0
- mojo/apps/fileman/renderer/utils.py +297 -0
- mojo/apps/fileman/renderer/video.py +304 -0
- mojo/apps/fileman/rest/__init__.py +1 -18
- mojo/apps/fileman/rest/upload.py +22 -32
- mojo/apps/fileman/signals.py +58 -0
- mojo/apps/fileman/tasks.py +254 -0
- mojo/apps/fileman/utils/__init__.py +40 -16
- mojo/apps/incident/migrations/0005_incidenthistory.py +39 -0
- mojo/apps/incident/migrations/0006_alter_incident_state.py +18 -0
- mojo/apps/incident/migrations/0007_event_uid.py +18 -0
- mojo/apps/incident/migrations/0008_ticket_ticketnote.py +55 -0
- mojo/apps/incident/migrations/0009_incident_status.py +18 -0
- mojo/apps/incident/migrations/0010_event_country_code.py +18 -0
- mojo/apps/incident/migrations/0011_incident_country_code.py +18 -0
- mojo/apps/incident/migrations/0012_alter_incident_status.py +18 -0
- mojo/apps/incident/models/__init__.py +2 -0
- mojo/apps/incident/models/event.py +35 -0
- mojo/apps/incident/models/history.py +36 -0
- mojo/apps/incident/models/incident.py +3 -1
- mojo/apps/incident/models/ticket.py +62 -0
- mojo/apps/incident/reporter.py +21 -1
- mojo/apps/incident/rest/__init__.py +1 -0
- mojo/apps/incident/rest/event.py +7 -1
- mojo/apps/incident/rest/ticket.py +43 -0
- mojo/apps/jobs/__init__.py +489 -0
- mojo/apps/jobs/adapters.py +24 -0
- mojo/apps/jobs/cli.py +616 -0
- mojo/apps/jobs/daemon.py +370 -0
- mojo/apps/jobs/examples/sample_jobs.py +376 -0
- mojo/apps/jobs/examples/webhook_examples.py +203 -0
- mojo/apps/jobs/handlers/__init__.py +5 -0
- mojo/apps/jobs/handlers/webhook.py +317 -0
- mojo/apps/jobs/job_engine.py +734 -0
- mojo/apps/jobs/keys.py +203 -0
- mojo/apps/jobs/local_queue.py +363 -0
- mojo/apps/jobs/management/__init__.py +3 -0
- mojo/apps/jobs/management/commands/__init__.py +3 -0
- mojo/apps/jobs/manager.py +1327 -0
- mojo/apps/jobs/migrations/0001_initial.py +97 -0
- mojo/apps/jobs/migrations/0002_alter_job_max_retries_joblog.py +39 -0
- mojo/apps/jobs/models/__init__.py +6 -0
- mojo/apps/jobs/models/job.py +441 -0
- mojo/apps/jobs/rest/__init__.py +2 -0
- mojo/apps/jobs/rest/control.py +466 -0
- mojo/apps/jobs/rest/jobs.py +421 -0
- mojo/apps/jobs/scheduler.py +571 -0
- mojo/apps/jobs/services/__init__.py +6 -0
- mojo/apps/jobs/services/job_actions.py +465 -0
- mojo/apps/jobs/settings.py +209 -0
- mojo/apps/logit/migrations/0004_alter_log_level.py +18 -0
- mojo/apps/logit/models/log.py +7 -1
- mojo/apps/metrics/__init__.py +8 -1
- mojo/apps/metrics/redis_metrics.py +198 -0
- mojo/apps/metrics/rest/__init__.py +3 -0
- mojo/apps/metrics/rest/categories.py +266 -0
- mojo/apps/metrics/rest/helpers.py +48 -0
- mojo/apps/metrics/rest/permissions.py +99 -0
- mojo/apps/metrics/rest/values.py +277 -0
- mojo/apps/metrics/utils.py +19 -2
- mojo/decorators/auth.py +6 -1
- mojo/decorators/http.py +47 -3
- mojo/helpers/aws/__init__.py +45 -0
- mojo/helpers/aws/ec2.py +804 -0
- mojo/helpers/aws/iam.py +748 -0
- mojo/helpers/aws/inbound_email.py +309 -0
- mojo/helpers/aws/kms.py +413 -0
- mojo/helpers/aws/s3.py +451 -11
- mojo/helpers/aws/ses.py +483 -0
- mojo/helpers/aws/ses_domain.py +959 -0
- mojo/helpers/aws/sns.py +461 -0
- mojo/helpers/crypto/__init__.py +1 -1
- mojo/helpers/crypto/utils.py +15 -0
- mojo/helpers/dates.py +18 -0
- mojo/helpers/location/__init__.py +2 -0
- mojo/helpers/location/countries.py +262 -0
- mojo/helpers/location/geolocation.py +196 -0
- mojo/helpers/logit.py +37 -0
- mojo/helpers/redis/__init__.py +2 -0
- mojo/helpers/redis/adapter.py +606 -0
- mojo/helpers/redis/client.py +48 -0
- mojo/helpers/redis/pool.py +225 -0
- mojo/helpers/request.py +8 -0
- mojo/helpers/response.py +14 -2
- mojo/helpers/settings/__init__.py +2 -0
- mojo/helpers/{settings.py → settings/helper.py} +1 -37
- mojo/helpers/settings/parser.py +132 -0
- mojo/middleware/auth.py +1 -1
- mojo/middleware/cors.py +40 -0
- mojo/middleware/logging.py +131 -12
- mojo/middleware/mojo.py +10 -0
- mojo/models/rest.py +494 -65
- mojo/models/secrets.py +98 -3
- mojo/serializers/__init__.py +106 -0
- mojo/serializers/core/__init__.py +90 -0
- mojo/serializers/core/cache/__init__.py +121 -0
- mojo/serializers/core/cache/backends.py +518 -0
- mojo/serializers/core/cache/base.py +102 -0
- mojo/serializers/core/cache/disabled.py +181 -0
- mojo/serializers/core/cache/memory.py +287 -0
- mojo/serializers/core/cache/redis.py +533 -0
- mojo/serializers/core/cache/utils.py +454 -0
- mojo/serializers/core/manager.py +550 -0
- mojo/serializers/core/serializer.py +475 -0
- mojo/serializers/examples/settings.py +322 -0
- mojo/serializers/formats/csv.py +393 -0
- mojo/serializers/formats/localizers.py +509 -0
- mojo/serializers/{models.py → simple.py} +38 -15
- mojo/serializers/suggested_improvements.md +388 -0
- testit/client.py +1 -1
- testit/helpers.py +35 -4
- testit/runner.py +23 -6
- django_nativemojo-0.1.10.dist-info/METADATA +0 -96
- django_nativemojo-0.1.10.dist-info/RECORD +0 -194
- mojo/apps/metrics/rest/db.py +0 -0
- mojo/apps/notify/README.md +0 -91
- mojo/apps/notify/README_NOTIFICATIONS.md +0 -566
- mojo/apps/notify/admin.py +0 -52
- mojo/apps/notify/handlers/example_handlers.py +0 -516
- mojo/apps/notify/handlers/ses/__init__.py +0 -25
- mojo/apps/notify/handlers/ses/bounce.py +0 -0
- mojo/apps/notify/handlers/ses/complaint.py +0 -25
- mojo/apps/notify/handlers/ses/message.py +0 -86
- mojo/apps/notify/management/commands/__init__.py +0 -1
- mojo/apps/notify/management/commands/process_notifications.py +0 -370
- mojo/apps/notify/mod +0 -0
- mojo/apps/notify/models/__init__.py +0 -12
- mojo/apps/notify/models/account.py +0 -128
- mojo/apps/notify/models/attachment.py +0 -24
- mojo/apps/notify/models/bounce.py +0 -68
- mojo/apps/notify/models/complaint.py +0 -40
- mojo/apps/notify/models/inbox.py +0 -113
- mojo/apps/notify/models/inbox_message.py +0 -173
- mojo/apps/notify/models/outbox.py +0 -129
- mojo/apps/notify/models/outbox_message.py +0 -288
- mojo/apps/notify/models/template.py +0 -30
- mojo/apps/notify/providers/aws.py +0 -73
- mojo/apps/notify/rest/ses.py +0 -0
- mojo/apps/notify/utils/__init__.py +0 -2
- mojo/apps/notify/utils/notifications.py +0 -404
- mojo/apps/notify/utils/parsing.py +0 -202
- mojo/apps/notify/utils/render.py +0 -144
- mojo/apps/tasks/README.md +0 -118
- mojo/apps/tasks/__init__.py +0 -11
- mojo/apps/tasks/manager.py +0 -489
- mojo/apps/tasks/rest/__init__.py +0 -2
- mojo/apps/tasks/rest/hooks.py +0 -0
- mojo/apps/tasks/rest/tasks.py +0 -62
- mojo/apps/tasks/runner.py +0 -174
- mojo/apps/tasks/tq_handlers.py +0 -14
- mojo/helpers/aws/setup_email.py +0 -0
- mojo/helpers/redis.py +0 -10
- mojo/models/meta.py +0 -262
- mojo/ws4redis/README.md +0 -174
- mojo/ws4redis/__init__.py +0 -2
- mojo/ws4redis/client.py +0 -283
- mojo/ws4redis/connection.py +0 -327
- mojo/ws4redis/exceptions.py +0 -32
- mojo/ws4redis/redis.py +0 -183
- mojo/ws4redis/servers/base.py +0 -86
- mojo/ws4redis/servers/django.py +0 -171
- mojo/ws4redis/servers/uwsgi.py +0 -63
- mojo/ws4redis/settings.py +0 -45
- mojo/ws4redis/utf8validator.py +0 -128
- mojo/ws4redis/websocket.py +0 -403
- {django_nativemojo-0.1.10.dist-info → django_nativemojo-0.1.16.dist-info}/LICENSE +0 -0
- {django_nativemojo-0.1.10.dist-info → django_nativemojo-0.1.16.dist-info}/NOTICE +0 -0
- {django_nativemojo-0.1.10.dist-info → django_nativemojo-0.1.16.dist-info}/WHEEL +0 -0
- /mojo/apps/{notify → aws}/__init__.py +0 -0
- /mojo/apps/{notify/handlers → aws/migrations}/__init__.py +0 -0
- /mojo/apps/{notify/management → docit/markdown_plugins}/__init__.py +0 -0
- /mojo/apps/{notify/providers → docit/migrations}/__init__.py +0 -0
- /mojo/apps/{notify/rest → fileman/migrations}/__init__.py +0 -0
- /mojo/{ws4redis/servers → apps/jobs/examples}/__init__.py +0 -0
- /mojo/apps/{fileman/models/render.py → jobs/migrations/__init__.py} +0 -0
- /mojo/{serializers → rest}/openapi.py +0 -0
- /mojo/{apps/fileman/rest/__init__ → serializers/formats/__init__.py} +0 -0
@@ -0,0 +1,254 @@
|
|
1
|
+
import logging
|
2
|
+
from celery import shared_task
|
3
|
+
from mojo.apps.fileman.models import File, FileRendition
|
4
|
+
from mojo.apps.fileman.renderer import process_new_file, get_renderer_for_file
|
5
|
+
|
6
|
+
logger = logging.getLogger(__name__)
|
7
|
+
|
8
|
+
@shared_task
|
9
|
+
def process_file_renditions(file_id):
|
10
|
+
"""
|
11
|
+
Process renditions for a newly uploaded file
|
12
|
+
|
13
|
+
Args:
|
14
|
+
file_id: ID of the File to process
|
15
|
+
|
16
|
+
Returns:
|
17
|
+
dict: Result information including number of renditions created
|
18
|
+
"""
|
19
|
+
try:
|
20
|
+
file = File.objects.get(id=file_id)
|
21
|
+
|
22
|
+
if not file.is_completed:
|
23
|
+
logger.warning(f"Cannot process renditions for incomplete file {file_id}")
|
24
|
+
return {
|
25
|
+
'status': 'error',
|
26
|
+
'message': 'File upload is not complete',
|
27
|
+
'file_id': file_id,
|
28
|
+
'renditions_created': 0
|
29
|
+
}
|
30
|
+
|
31
|
+
renditions = process_new_file(file)
|
32
|
+
|
33
|
+
return {
|
34
|
+
'status': 'success',
|
35
|
+
'message': f'Created {len(renditions)} renditions',
|
36
|
+
'file_id': file_id,
|
37
|
+
'renditions_created': len(renditions),
|
38
|
+
'rendition_roles': [r.role for r in renditions]
|
39
|
+
}
|
40
|
+
|
41
|
+
except File.DoesNotExist:
|
42
|
+
logger.error(f"File with ID {file_id} not found")
|
43
|
+
return {
|
44
|
+
'status': 'error',
|
45
|
+
'message': 'File not found',
|
46
|
+
'file_id': file_id,
|
47
|
+
'renditions_created': 0
|
48
|
+
}
|
49
|
+
except Exception as e:
|
50
|
+
logger.exception(f"Error processing renditions for file {file_id}: {str(e)}")
|
51
|
+
return {
|
52
|
+
'status': 'error',
|
53
|
+
'message': str(e),
|
54
|
+
'file_id': file_id,
|
55
|
+
'renditions_created': 0
|
56
|
+
}
|
57
|
+
|
58
|
+
@shared_task
|
59
|
+
def cleanup_renditions(file_id):
|
60
|
+
"""
|
61
|
+
Clean up all renditions for a file
|
62
|
+
|
63
|
+
Args:
|
64
|
+
file_id: ID of the File whose renditions should be cleaned up
|
65
|
+
|
66
|
+
Returns:
|
67
|
+
dict: Result information
|
68
|
+
"""
|
69
|
+
try:
|
70
|
+
# First get the file to make sure it exists
|
71
|
+
file = File.objects.get(id=file_id)
|
72
|
+
|
73
|
+
# Get all renditions
|
74
|
+
renditions = FileRendition.objects.filter(original_file_id=file_id)
|
75
|
+
count = renditions.count()
|
76
|
+
|
77
|
+
# Delete the files from storage
|
78
|
+
for rendition in renditions:
|
79
|
+
try:
|
80
|
+
if rendition.storage_path:
|
81
|
+
file.file_manager.backend.delete(rendition.storage_path)
|
82
|
+
except Exception as e:
|
83
|
+
logger.warning(f"Error deleting rendition file {rendition.id}: {str(e)}")
|
84
|
+
|
85
|
+
# Delete the database records
|
86
|
+
renditions.delete()
|
87
|
+
|
88
|
+
return {
|
89
|
+
'status': 'success',
|
90
|
+
'message': f'Cleaned up {count} renditions',
|
91
|
+
'file_id': file_id,
|
92
|
+
'renditions_deleted': count
|
93
|
+
}
|
94
|
+
|
95
|
+
except File.DoesNotExist:
|
96
|
+
logger.error(f"File with ID {file_id} not found")
|
97
|
+
return {
|
98
|
+
'status': 'error',
|
99
|
+
'message': 'File not found',
|
100
|
+
'file_id': file_id,
|
101
|
+
'renditions_deleted': 0
|
102
|
+
}
|
103
|
+
except Exception as e:
|
104
|
+
logger.exception(f"Error cleaning up renditions for file {file_id}: {str(e)}")
|
105
|
+
return {
|
106
|
+
'status': 'error',
|
107
|
+
'message': str(e),
|
108
|
+
'file_id': file_id,
|
109
|
+
'renditions_deleted': 0
|
110
|
+
}
|
111
|
+
|
112
|
+
@shared_task
|
113
|
+
def regenerate_renditions(file_id, roles=None):
|
114
|
+
"""
|
115
|
+
Regenerate specific or all renditions for a file
|
116
|
+
|
117
|
+
Args:
|
118
|
+
file_id: ID of the File to process
|
119
|
+
roles: Optional list of roles to regenerate (None for all)
|
120
|
+
|
121
|
+
Returns:
|
122
|
+
dict: Result information
|
123
|
+
"""
|
124
|
+
try:
|
125
|
+
file = File.objects.get(id=file_id)
|
126
|
+
|
127
|
+
if not file.is_completed:
|
128
|
+
logger.warning(f"Cannot regenerate renditions for incomplete file {file_id}")
|
129
|
+
return {
|
130
|
+
'status': 'error',
|
131
|
+
'message': 'File upload is not complete',
|
132
|
+
'file_id': file_id,
|
133
|
+
'renditions_created': 0
|
134
|
+
}
|
135
|
+
|
136
|
+
# Get renderer for this file
|
137
|
+
renderer = get_renderer_for_file(file)
|
138
|
+
if not renderer:
|
139
|
+
return {
|
140
|
+
'status': 'error',
|
141
|
+
'message': f'No renderer available for file type {file.category}',
|
142
|
+
'file_id': file_id,
|
143
|
+
'renditions_created': 0
|
144
|
+
}
|
145
|
+
|
146
|
+
# If specific roles are requested, delete and regenerate those
|
147
|
+
if roles:
|
148
|
+
# Delete existing renditions for these roles
|
149
|
+
FileRendition.objects.filter(original_file=file, role__in=roles).delete()
|
150
|
+
|
151
|
+
# Create new renditions
|
152
|
+
created_renditions = []
|
153
|
+
for role in roles:
|
154
|
+
rendition = renderer.create_rendition(role)
|
155
|
+
if rendition:
|
156
|
+
created_renditions.append(rendition)
|
157
|
+
else:
|
158
|
+
# Delete all existing renditions
|
159
|
+
FileRendition.objects.filter(original_file=file).delete()
|
160
|
+
|
161
|
+
# Create all default renditions
|
162
|
+
created_renditions = renderer.create_all_renditions()
|
163
|
+
|
164
|
+
return {
|
165
|
+
'status': 'success',
|
166
|
+
'message': f'Regenerated {len(created_renditions)} renditions',
|
167
|
+
'file_id': file_id,
|
168
|
+
'renditions_created': len(created_renditions),
|
169
|
+
'rendition_roles': [r.role for r in created_renditions]
|
170
|
+
}
|
171
|
+
|
172
|
+
except File.DoesNotExist:
|
173
|
+
logger.error(f"File with ID {file_id} not found")
|
174
|
+
return {
|
175
|
+
'status': 'error',
|
176
|
+
'message': 'File not found',
|
177
|
+
'file_id': file_id,
|
178
|
+
'renditions_created': 0
|
179
|
+
}
|
180
|
+
except Exception as e:
|
181
|
+
logger.exception(f"Error regenerating renditions for file {file_id}: {str(e)}")
|
182
|
+
return {
|
183
|
+
'status': 'error',
|
184
|
+
'message': str(e),
|
185
|
+
'file_id': file_id,
|
186
|
+
'renditions_created': 0
|
187
|
+
}
|
188
|
+
|
189
|
+
@shared_task
|
190
|
+
def process_bulk_renditions(file_ids, roles=None):
|
191
|
+
"""
|
192
|
+
Process renditions for multiple files
|
193
|
+
|
194
|
+
Args:
|
195
|
+
file_ids: List of File IDs to process
|
196
|
+
roles: Optional list of roles to generate (None for all default roles)
|
197
|
+
|
198
|
+
Returns:
|
199
|
+
dict: Result information
|
200
|
+
"""
|
201
|
+
results = {
|
202
|
+
'total': len(file_ids),
|
203
|
+
'successful': 0,
|
204
|
+
'failed': 0,
|
205
|
+
'errors': []
|
206
|
+
}
|
207
|
+
|
208
|
+
for file_id in file_ids:
|
209
|
+
try:
|
210
|
+
file = File.objects.get(id=file_id)
|
211
|
+
|
212
|
+
if not file.is_completed:
|
213
|
+
results['failed'] += 1
|
214
|
+
results['errors'].append({
|
215
|
+
'file_id': file_id,
|
216
|
+
'error': 'File upload is not complete'
|
217
|
+
})
|
218
|
+
continue
|
219
|
+
|
220
|
+
renderer = get_renderer_for_file(file)
|
221
|
+
if not renderer:
|
222
|
+
results['failed'] += 1
|
223
|
+
results['errors'].append({
|
224
|
+
'file_id': file_id,
|
225
|
+
'error': f'No renderer for file type {file.category}'
|
226
|
+
})
|
227
|
+
continue
|
228
|
+
|
229
|
+
if roles:
|
230
|
+
renditions = []
|
231
|
+
for role in roles:
|
232
|
+
rendition = renderer.get_rendition(role)
|
233
|
+
if rendition:
|
234
|
+
renditions.append(rendition)
|
235
|
+
else:
|
236
|
+
renditions = renderer.create_all_renditions()
|
237
|
+
|
238
|
+
results['successful'] += 1
|
239
|
+
|
240
|
+
except File.DoesNotExist:
|
241
|
+
results['failed'] += 1
|
242
|
+
results['errors'].append({
|
243
|
+
'file_id': file_id,
|
244
|
+
'error': 'File not found'
|
245
|
+
})
|
246
|
+
except Exception as e:
|
247
|
+
results['failed'] += 1
|
248
|
+
results['errors'].append({
|
249
|
+
'file_id': file_id,
|
250
|
+
'error': str(e)
|
251
|
+
})
|
252
|
+
logger.exception(f"Error processing renditions for file {file_id}: {str(e)}")
|
253
|
+
|
254
|
+
return results
|
@@ -1,19 +1,43 @@
|
|
1
1
|
# File upload utility functions
|
2
2
|
|
3
|
-
from .upload import (
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
)
|
3
|
+
# from .upload import (
|
4
|
+
# get_file_manager,
|
5
|
+
# validate_file_request,
|
6
|
+
# initiate_upload,
|
7
|
+
# finalize_upload,
|
8
|
+
# direct_upload,
|
9
|
+
# get_download_url
|
10
|
+
# )
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
12
|
+
def get_file_category(content_type: str) -> str:
|
13
|
+
if not content_type:
|
14
|
+
return "unknown"
|
15
|
+
|
16
|
+
content_type = content_type.lower()
|
17
|
+
|
18
|
+
if content_type.startswith("image/"):
|
19
|
+
return "image"
|
20
|
+
elif content_type.startswith("video/"):
|
21
|
+
return "video"
|
22
|
+
elif content_type.startswith("audio/"):
|
23
|
+
return "audio"
|
24
|
+
elif content_type == "application/pdf":
|
25
|
+
return "pdf"
|
26
|
+
elif content_type in ["text/csv", "application/csv"]:
|
27
|
+
return "csv"
|
28
|
+
elif content_type in [
|
29
|
+
"application/vnd.ms-excel",
|
30
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
31
|
+
]:
|
32
|
+
return "spreadsheet"
|
33
|
+
elif content_type in [
|
34
|
+
"application/msword",
|
35
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
36
|
+
]:
|
37
|
+
return "document"
|
38
|
+
elif content_type in ["application/zip", "application/x-zip-compressed"]:
|
39
|
+
return "archive"
|
40
|
+
elif content_type.startswith("text/"):
|
41
|
+
return "text"
|
42
|
+
else:
|
43
|
+
return "other"
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# Generated by Django 4.2.21 on 2025-06-07 13:32
|
2
|
+
|
3
|
+
from django.conf import settings
|
4
|
+
from django.db import migrations, models
|
5
|
+
import django.db.models.deletion
|
6
|
+
import mojo.models.rest
|
7
|
+
|
8
|
+
|
9
|
+
class Migration(migrations.Migration):
|
10
|
+
|
11
|
+
dependencies = [
|
12
|
+
('account', '0003_group_mojo_secrets_user_mojo_secrets'),
|
13
|
+
('fileman', '0001_initial'),
|
14
|
+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
15
|
+
('incident', '0004_alter_incident_model_id'),
|
16
|
+
]
|
17
|
+
|
18
|
+
operations = [
|
19
|
+
migrations.CreateModel(
|
20
|
+
name='IncidentHistory',
|
21
|
+
fields=[
|
22
|
+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
23
|
+
('created', models.DateTimeField(auto_now_add=True)),
|
24
|
+
('kind', models.CharField(blank=True, db_index=True, default=None, max_length=80, null=True)),
|
25
|
+
('state', models.IntegerField(default=0)),
|
26
|
+
('priority', models.IntegerField(default=0)),
|
27
|
+
('note', models.TextField(blank=True, default=None, null=True)),
|
28
|
+
('by', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to=settings.AUTH_USER_MODEL)),
|
29
|
+
('group', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='account.group')),
|
30
|
+
('media', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='fileman.file')),
|
31
|
+
('parent', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='history', to='incident.incident')),
|
32
|
+
('to', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to=settings.AUTH_USER_MODEL)),
|
33
|
+
],
|
34
|
+
options={
|
35
|
+
'ordering': ['-created'],
|
36
|
+
},
|
37
|
+
bases=(models.Model, mojo.models.rest.MojoModel),
|
38
|
+
),
|
39
|
+
]
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# Generated by Django 4.2.21 on 2025-06-07 14:13
|
2
|
+
|
3
|
+
from django.db import migrations, models
|
4
|
+
|
5
|
+
|
6
|
+
class Migration(migrations.Migration):
|
7
|
+
|
8
|
+
dependencies = [
|
9
|
+
('incident', '0005_incidenthistory'),
|
10
|
+
]
|
11
|
+
|
12
|
+
operations = [
|
13
|
+
migrations.AlterField(
|
14
|
+
model_name='incident',
|
15
|
+
name='state',
|
16
|
+
field=models.CharField(db_index=True, default=0, max_length=24),
|
17
|
+
),
|
18
|
+
]
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# Generated by Django 4.2.21 on 2025-08-29 18:04
|
2
|
+
|
3
|
+
from django.db import migrations, models
|
4
|
+
|
5
|
+
|
6
|
+
class Migration(migrations.Migration):
|
7
|
+
|
8
|
+
dependencies = [
|
9
|
+
('incident', '0006_alter_incident_state'),
|
10
|
+
]
|
11
|
+
|
12
|
+
operations = [
|
13
|
+
migrations.AddField(
|
14
|
+
model_name='event',
|
15
|
+
name='uid',
|
16
|
+
field=models.IntegerField(db_index=True, default=None, null=True),
|
17
|
+
),
|
18
|
+
]
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# Generated by Django 4.2.21 on 2025-08-30 02:36
|
2
|
+
|
3
|
+
from django.conf import settings
|
4
|
+
from django.db import migrations, models
|
5
|
+
import django.db.models.deletion
|
6
|
+
import mojo.models.rest
|
7
|
+
|
8
|
+
|
9
|
+
class Migration(migrations.Migration):
|
10
|
+
|
11
|
+
dependencies = [
|
12
|
+
('fileman', '0011_alter_filerendition_original_file'),
|
13
|
+
('account', '0011_user_org_registereddevice_pushconfig_and_more'),
|
14
|
+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
15
|
+
('incident', '0007_event_uid'),
|
16
|
+
]
|
17
|
+
|
18
|
+
operations = [
|
19
|
+
migrations.CreateModel(
|
20
|
+
name='Ticket',
|
21
|
+
fields=[
|
22
|
+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
23
|
+
('created', models.DateTimeField(auto_now_add=True)),
|
24
|
+
('modified', models.DateTimeField(auto_now=True)),
|
25
|
+
('title', models.CharField(max_length=255)),
|
26
|
+
('description', models.TextField(blank=True, null=True)),
|
27
|
+
('status', models.CharField(db_index=True, default='open', max_length=50)),
|
28
|
+
('priority', models.IntegerField(db_index=True, default=1)),
|
29
|
+
('metadata', models.JSONField(blank=True, default=dict)),
|
30
|
+
('assignee', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assigned_tickets', to=settings.AUTH_USER_MODEL)),
|
31
|
+
('group', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='account.group')),
|
32
|
+
('incident', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tickets', to='incident.incident')),
|
33
|
+
('user', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
|
34
|
+
],
|
35
|
+
options={
|
36
|
+
'ordering': ['-created'],
|
37
|
+
},
|
38
|
+
bases=(models.Model, mojo.models.rest.MojoModel),
|
39
|
+
),
|
40
|
+
migrations.CreateModel(
|
41
|
+
name='TicketNote',
|
42
|
+
fields=[
|
43
|
+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
44
|
+
('created', models.DateTimeField(auto_now_add=True)),
|
45
|
+
('note', models.TextField(blank=True, null=True)),
|
46
|
+
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to=settings.AUTH_USER_MODEL)),
|
47
|
+
('media', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='fileman.file')),
|
48
|
+
('parent', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notes', to='incident.ticket')),
|
49
|
+
],
|
50
|
+
options={
|
51
|
+
'ordering': ['-created'],
|
52
|
+
},
|
53
|
+
bases=(models.Model, mojo.models.rest.MojoModel),
|
54
|
+
),
|
55
|
+
]
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# Generated by Django 4.2.23 on 2025-09-05 22:03
|
2
|
+
|
3
|
+
from django.db import migrations, models
|
4
|
+
|
5
|
+
|
6
|
+
class Migration(migrations.Migration):
|
7
|
+
|
8
|
+
dependencies = [
|
9
|
+
('incident', '0008_ticket_ticketnote'),
|
10
|
+
]
|
11
|
+
|
12
|
+
operations = [
|
13
|
+
migrations.AddField(
|
14
|
+
model_name='incident',
|
15
|
+
name='status',
|
16
|
+
field=models.CharField(db_index=True, default='open', max_length=50),
|
17
|
+
),
|
18
|
+
]
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# Generated by Django 4.2.23 on 2025-09-05 22:22
|
2
|
+
|
3
|
+
from django.db import migrations, models
|
4
|
+
|
5
|
+
|
6
|
+
class Migration(migrations.Migration):
|
7
|
+
|
8
|
+
dependencies = [
|
9
|
+
('incident', '0009_incident_status'),
|
10
|
+
]
|
11
|
+
|
12
|
+
operations = [
|
13
|
+
migrations.AddField(
|
14
|
+
model_name='event',
|
15
|
+
name='country_code',
|
16
|
+
field=models.CharField(db_index=True, default=None, max_length=2, null=True),
|
17
|
+
),
|
18
|
+
]
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# Generated by Django 4.2.23 on 2025-09-05 22:25
|
2
|
+
|
3
|
+
from django.db import migrations, models
|
4
|
+
|
5
|
+
|
6
|
+
class Migration(migrations.Migration):
|
7
|
+
|
8
|
+
dependencies = [
|
9
|
+
('incident', '0010_event_country_code'),
|
10
|
+
]
|
11
|
+
|
12
|
+
operations = [
|
13
|
+
migrations.AddField(
|
14
|
+
model_name='incident',
|
15
|
+
name='country_code',
|
16
|
+
field=models.CharField(db_index=True, default=None, max_length=2, null=True),
|
17
|
+
),
|
18
|
+
]
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# Generated by Django 4.2.23 on 2025-09-06 00:00
|
2
|
+
|
3
|
+
from django.db import migrations, models
|
4
|
+
|
5
|
+
|
6
|
+
class Migration(migrations.Migration):
|
7
|
+
|
8
|
+
dependencies = [
|
9
|
+
('incident', '0011_incident_country_code'),
|
10
|
+
]
|
11
|
+
|
12
|
+
operations = [
|
13
|
+
migrations.AlterField(
|
14
|
+
model_name='incident',
|
15
|
+
name='status',
|
16
|
+
field=models.CharField(db_index=True, default='new', max_length=50),
|
17
|
+
),
|
18
|
+
]
|
@@ -4,6 +4,7 @@ from mojo.models import MojoModel
|
|
4
4
|
from mojo.helpers import dates
|
5
5
|
from mojo.helpers.settings import settings
|
6
6
|
from mojo.apps import metrics
|
7
|
+
from mojo.apps.account.models import GeoLocatedIP
|
7
8
|
|
8
9
|
|
9
10
|
INCIDENT_LEVEL_THRESHOLD = settings.get('INCIDENT_LEVEL_THRESHOLD', 7)
|
@@ -22,6 +23,8 @@ class Event(models.Model, MojoModel):
|
|
22
23
|
category = models.CharField(max_length=124, db_index=True)
|
23
24
|
source_ip = models.CharField(max_length=16, null=True, default=None, db_index=True)
|
24
25
|
hostname = models.CharField(max_length=16, null=True, default=None, db_index=True)
|
26
|
+
uid = models.IntegerField(default=None, null=True, db_index=True)
|
27
|
+
country_code = models.CharField(max_length=2, default=None, null=True, db_index=True)
|
25
28
|
|
26
29
|
title = models.TextField(default=None, null=True)
|
27
30
|
details = models.TextField(default=None, null=True)
|
@@ -40,6 +43,16 @@ class Event(models.Model, MojoModel):
|
|
40
43
|
VIEW_PERMS = ["view_incidents"]
|
41
44
|
CREATE_PERMS = None
|
42
45
|
|
46
|
+
_geo_ip = None
|
47
|
+
@property
|
48
|
+
def geo_ip(self):
|
49
|
+
if self._geo_ip is None and self.source_ip:
|
50
|
+
try:
|
51
|
+
self._geo_ip = GeoLocatedIP.geolocate(self.source_ip, subdomain_only=True)
|
52
|
+
except Exception:
|
53
|
+
pass
|
54
|
+
return self._geo_ip
|
55
|
+
|
43
56
|
def sync_metadata(self):
|
44
57
|
# Gather all field values into the metadata
|
45
58
|
field_values = {
|
@@ -50,6 +63,17 @@ class Event(models.Model, MojoModel):
|
|
50
63
|
'details': self.details,
|
51
64
|
'model_name': self.model_name,
|
52
65
|
'model_id': self.model_id }
|
66
|
+
|
67
|
+
if not self.country_code and self.geo_ip:
|
68
|
+
self.country_code = self.geo_ip.country_code
|
69
|
+
field_values["country_code"] = self.geo_ip.country_code
|
70
|
+
field_values["country_name"] = self.geo_ip.country_name
|
71
|
+
field_values["city"] = self.geo_ip.city
|
72
|
+
field_values["region"] = self.geo_ip.region
|
73
|
+
field_values["latitude"] = self.geo_ip.latitude
|
74
|
+
field_values["longitude"] = self.geo_ip.longitude
|
75
|
+
field_values["timezone"] = self.geo_ip.timezone
|
76
|
+
|
53
77
|
# Update the metadata with these values
|
54
78
|
self.metadata.update(field_values)
|
55
79
|
|
@@ -69,11 +93,21 @@ class Event(models.Model, MojoModel):
|
|
69
93
|
if settings.INCIDENT_EVENT_METRICS:
|
70
94
|
metrics.record('incident_events', account="incident",
|
71
95
|
min_granularity=settings.get("INCIDENT_METRICS_MIN_GRANULARITY", "hours"))
|
96
|
+
if self.country_code:
|
97
|
+
metrics.record(f'incident_events:country:{self.country_code}',
|
98
|
+
account="incident",
|
99
|
+
category="incident_events_by_country",
|
100
|
+
min_granularity=settings.get("INCIDENT_METRICS_MIN_GRANULARITY", "hours"))
|
72
101
|
|
73
102
|
def record_incident_metrics(self):
|
74
103
|
if settings.INCIDENT_EVENT_METRICS:
|
75
104
|
metrics.record('incidents', account="incident",
|
76
105
|
min_granularity=settings.get("INCIDENT_METRICS_MIN_GRANULARITY", "hours"))
|
106
|
+
if self.country_code:
|
107
|
+
metrics.record(f'incident:country:{self.country_code}',
|
108
|
+
account="incident",
|
109
|
+
category="incidents_by_country",
|
110
|
+
min_granularity=settings.get("INCIDENT_METRICS_MIN_GRANULARITY", "hours"))
|
77
111
|
|
78
112
|
def get_or_create_incident(self, rule_set=None):
|
79
113
|
"""
|
@@ -94,6 +128,7 @@ class Event(models.Model, MojoModel):
|
|
94
128
|
priority=self.level,
|
95
129
|
state=0,
|
96
130
|
category=self.category,
|
131
|
+
country_code=self.country_code,
|
97
132
|
title=self.title,
|
98
133
|
details=self.details,
|
99
134
|
hostname=self.hostname,
|
@@ -0,0 +1,36 @@
|
|
1
|
+
from django.db import models
|
2
|
+
from mojo.models import MojoModel
|
3
|
+
|
4
|
+
class IncidentHistory(models.Model, MojoModel):
|
5
|
+
class Meta:
|
6
|
+
ordering = ['-created']
|
7
|
+
|
8
|
+
class RestMeta:
|
9
|
+
GRAPHS = {
|
10
|
+
"default": {
|
11
|
+
"extra": [
|
12
|
+
("get_state_display", "state_display"),
|
13
|
+
("get_priority_display", "priority_display"),
|
14
|
+
],
|
15
|
+
"graphs": {
|
16
|
+
"by": "basic",
|
17
|
+
"to": "basic",
|
18
|
+
"media": "basic"
|
19
|
+
}
|
20
|
+
},
|
21
|
+
}
|
22
|
+
parent = models.ForeignKey("incident.Incident", related_name="history", on_delete=models.CASCADE)
|
23
|
+
created = models.DateTimeField(auto_now_add=True, editable=False)
|
24
|
+
|
25
|
+
group = models.ForeignKey("account.Group", blank=True, null=True, default=None, related_name="+", on_delete=models.CASCADE)
|
26
|
+
|
27
|
+
kind = models.CharField(max_length=80, blank=True, null=True, default=None, db_index=True)
|
28
|
+
|
29
|
+
to = models.ForeignKey("account.User", blank=True, null=True, default=None, related_name="+", on_delete=models.CASCADE)
|
30
|
+
by = models.ForeignKey("account.User", blank=True, null=True, default=None, related_name="+", on_delete=models.CASCADE)
|
31
|
+
|
32
|
+
state = models.IntegerField(default=0)
|
33
|
+
priority = models.IntegerField(default=0)
|
34
|
+
|
35
|
+
note = models.TextField(blank=True, null=True, default=None)
|
36
|
+
media = models.ForeignKey("fileman.File", related_name="+", null=True, default=None, on_delete=models.CASCADE)
|
@@ -9,8 +9,10 @@ class Incident(models.Model, MojoModel):
|
|
9
9
|
created = models.DateTimeField(auto_now_add=True, editable=False, db_index=True)
|
10
10
|
|
11
11
|
priority = models.IntegerField(default=0, db_index=True)
|
12
|
-
state = models.
|
12
|
+
state = models.CharField(max_length=24, default=0, db_index=True)
|
13
|
+
status = models.CharField(max_length=50, default='new', db_index=True)
|
13
14
|
category = models.CharField(max_length=124, db_index=True)
|
15
|
+
country_code = models.CharField(max_length=2, default=None, null=True, db_index=True)
|
14
16
|
title = models.TextField(default=None, null=True)
|
15
17
|
details = models.TextField(default=None, null=True)
|
16
18
|
|