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.
Files changed (276) hide show
  1. django_nativemojo-0.1.16.dist-info/METADATA +138 -0
  2. django_nativemojo-0.1.16.dist-info/RECORD +302 -0
  3. mojo/__init__.py +1 -1
  4. mojo/apps/account/management/__init__.py +5 -0
  5. mojo/apps/account/management/commands/__init__.py +6 -0
  6. mojo/apps/account/management/commands/serializer_admin.py +651 -0
  7. mojo/apps/account/migrations/0004_user_avatar.py +20 -0
  8. mojo/apps/account/migrations/0005_group_last_activity.py +18 -0
  9. mojo/apps/account/migrations/0006_add_device_tracking_models.py +72 -0
  10. mojo/apps/account/migrations/0007_delete_userdevicelocation.py +16 -0
  11. mojo/apps/account/migrations/0008_userdevicelocation.py +33 -0
  12. mojo/apps/account/migrations/0009_geolocatedip_subnet.py +18 -0
  13. mojo/apps/account/migrations/0010_group_avatar.py +20 -0
  14. mojo/apps/account/migrations/0011_user_org_registereddevice_pushconfig_and_more.py +118 -0
  15. mojo/apps/account/migrations/0012_remove_pushconfig_apns_key_file_and_more.py +21 -0
  16. mojo/apps/account/migrations/0013_pushconfig_test_mode_alter_pushconfig_apns_enabled_and_more.py +28 -0
  17. mojo/apps/account/migrations/0014_notificationdelivery_data_payload_and_more.py +48 -0
  18. mojo/apps/account/models/__init__.py +2 -0
  19. mojo/apps/account/models/device.py +281 -0
  20. mojo/apps/account/models/group.py +319 -15
  21. mojo/apps/account/models/member.py +29 -5
  22. mojo/apps/account/models/push/__init__.py +4 -0
  23. mojo/apps/account/models/push/config.py +112 -0
  24. mojo/apps/account/models/push/delivery.py +93 -0
  25. mojo/apps/account/models/push/device.py +66 -0
  26. mojo/apps/account/models/push/template.py +99 -0
  27. mojo/apps/account/models/user.py +369 -19
  28. mojo/apps/account/rest/__init__.py +2 -0
  29. mojo/apps/account/rest/device.py +39 -0
  30. mojo/apps/account/rest/group.py +9 -0
  31. mojo/apps/account/rest/push.py +187 -0
  32. mojo/apps/account/rest/user.py +100 -6
  33. mojo/apps/account/services/__init__.py +1 -0
  34. mojo/apps/account/services/push.py +363 -0
  35. mojo/apps/aws/migrations/0001_initial.py +206 -0
  36. mojo/apps/aws/migrations/0002_emaildomain_can_recv_emaildomain_can_send_and_more.py +28 -0
  37. mojo/apps/aws/migrations/0003_mailbox_is_domain_default_mailbox_is_system_default_and_more.py +31 -0
  38. mojo/apps/aws/migrations/0004_s3bucket.py +39 -0
  39. mojo/apps/aws/migrations/0005_alter_emaildomain_region_delete_s3bucket.py +21 -0
  40. mojo/apps/aws/models/__init__.py +19 -0
  41. mojo/apps/aws/models/email_attachment.py +99 -0
  42. mojo/apps/aws/models/email_domain.py +218 -0
  43. mojo/apps/aws/models/email_template.py +132 -0
  44. mojo/apps/aws/models/incoming_email.py +197 -0
  45. mojo/apps/aws/models/mailbox.py +288 -0
  46. mojo/apps/aws/models/sent_message.py +175 -0
  47. mojo/apps/aws/rest/__init__.py +7 -0
  48. mojo/apps/aws/rest/email.py +33 -0
  49. mojo/apps/aws/rest/email_ops.py +183 -0
  50. mojo/apps/aws/rest/messages.py +32 -0
  51. mojo/apps/aws/rest/s3.py +64 -0
  52. mojo/apps/aws/rest/send.py +101 -0
  53. mojo/apps/aws/rest/sns.py +403 -0
  54. mojo/apps/aws/rest/templates.py +19 -0
  55. mojo/apps/aws/services/__init__.py +32 -0
  56. mojo/apps/aws/services/email.py +390 -0
  57. mojo/apps/aws/services/email_ops.py +548 -0
  58. mojo/apps/docit/__init__.py +6 -0
  59. mojo/apps/docit/markdown_plugins/syntax_highlight.py +25 -0
  60. mojo/apps/docit/markdown_plugins/toc.py +12 -0
  61. mojo/apps/docit/migrations/0001_initial.py +113 -0
  62. mojo/apps/docit/migrations/0002_alter_book_modified_by_alter_page_modified_by.py +26 -0
  63. mojo/apps/docit/migrations/0003_alter_book_group.py +20 -0
  64. mojo/apps/docit/models/__init__.py +17 -0
  65. mojo/apps/docit/models/asset.py +231 -0
  66. mojo/apps/docit/models/book.py +227 -0
  67. mojo/apps/docit/models/page.py +319 -0
  68. mojo/apps/docit/models/page_revision.py +203 -0
  69. mojo/apps/docit/rest/__init__.py +10 -0
  70. mojo/apps/docit/rest/asset.py +17 -0
  71. mojo/apps/docit/rest/book.py +22 -0
  72. mojo/apps/docit/rest/page.py +22 -0
  73. mojo/apps/docit/rest/page_revision.py +17 -0
  74. mojo/apps/docit/services/__init__.py +11 -0
  75. mojo/apps/docit/services/docit.py +315 -0
  76. mojo/apps/docit/services/markdown.py +44 -0
  77. mojo/apps/fileman/README.md +8 -8
  78. mojo/apps/fileman/backends/base.py +76 -70
  79. mojo/apps/fileman/backends/filesystem.py +86 -86
  80. mojo/apps/fileman/backends/s3.py +409 -108
  81. mojo/apps/fileman/migrations/0001_initial.py +106 -0
  82. mojo/apps/fileman/migrations/0002_filemanager_parent_alter_filemanager_max_file_size.py +24 -0
  83. mojo/apps/fileman/migrations/0003_remove_file_fileman_fil_upload__c4bc35_idx_and_more.py +25 -0
  84. mojo/apps/fileman/migrations/0004_remove_file_original_filename_and_more.py +39 -0
  85. mojo/apps/fileman/migrations/0005_alter_file_upload_token.py +18 -0
  86. mojo/apps/fileman/migrations/0006_file_download_url_filemanager_forever_urls.py +23 -0
  87. mojo/apps/fileman/migrations/0007_remove_filemanager_forever_urls_and_more.py +22 -0
  88. mojo/apps/fileman/migrations/0008_file_category.py +18 -0
  89. mojo/apps/fileman/migrations/0009_rename_file_path_file_storage_file_path.py +18 -0
  90. mojo/apps/fileman/migrations/0010_filerendition.py +33 -0
  91. mojo/apps/fileman/migrations/0011_alter_filerendition_original_file.py +19 -0
  92. mojo/apps/fileman/models/__init__.py +1 -5
  93. mojo/apps/fileman/models/file.py +240 -58
  94. mojo/apps/fileman/models/manager.py +427 -31
  95. mojo/apps/fileman/models/rendition.py +118 -0
  96. mojo/apps/fileman/renderer/__init__.py +111 -0
  97. mojo/apps/fileman/renderer/audio.py +403 -0
  98. mojo/apps/fileman/renderer/base.py +205 -0
  99. mojo/apps/fileman/renderer/document.py +404 -0
  100. mojo/apps/fileman/renderer/image.py +222 -0
  101. mojo/apps/fileman/renderer/utils.py +297 -0
  102. mojo/apps/fileman/renderer/video.py +304 -0
  103. mojo/apps/fileman/rest/__init__.py +1 -18
  104. mojo/apps/fileman/rest/upload.py +22 -32
  105. mojo/apps/fileman/signals.py +58 -0
  106. mojo/apps/fileman/tasks.py +254 -0
  107. mojo/apps/fileman/utils/__init__.py +40 -16
  108. mojo/apps/incident/migrations/0005_incidenthistory.py +39 -0
  109. mojo/apps/incident/migrations/0006_alter_incident_state.py +18 -0
  110. mojo/apps/incident/migrations/0007_event_uid.py +18 -0
  111. mojo/apps/incident/migrations/0008_ticket_ticketnote.py +55 -0
  112. mojo/apps/incident/migrations/0009_incident_status.py +18 -0
  113. mojo/apps/incident/migrations/0010_event_country_code.py +18 -0
  114. mojo/apps/incident/migrations/0011_incident_country_code.py +18 -0
  115. mojo/apps/incident/migrations/0012_alter_incident_status.py +18 -0
  116. mojo/apps/incident/models/__init__.py +2 -0
  117. mojo/apps/incident/models/event.py +35 -0
  118. mojo/apps/incident/models/history.py +36 -0
  119. mojo/apps/incident/models/incident.py +3 -1
  120. mojo/apps/incident/models/ticket.py +62 -0
  121. mojo/apps/incident/reporter.py +21 -1
  122. mojo/apps/incident/rest/__init__.py +1 -0
  123. mojo/apps/incident/rest/event.py +7 -1
  124. mojo/apps/incident/rest/ticket.py +43 -0
  125. mojo/apps/jobs/__init__.py +489 -0
  126. mojo/apps/jobs/adapters.py +24 -0
  127. mojo/apps/jobs/cli.py +616 -0
  128. mojo/apps/jobs/daemon.py +370 -0
  129. mojo/apps/jobs/examples/sample_jobs.py +376 -0
  130. mojo/apps/jobs/examples/webhook_examples.py +203 -0
  131. mojo/apps/jobs/handlers/__init__.py +5 -0
  132. mojo/apps/jobs/handlers/webhook.py +317 -0
  133. mojo/apps/jobs/job_engine.py +734 -0
  134. mojo/apps/jobs/keys.py +203 -0
  135. mojo/apps/jobs/local_queue.py +363 -0
  136. mojo/apps/jobs/management/__init__.py +3 -0
  137. mojo/apps/jobs/management/commands/__init__.py +3 -0
  138. mojo/apps/jobs/manager.py +1327 -0
  139. mojo/apps/jobs/migrations/0001_initial.py +97 -0
  140. mojo/apps/jobs/migrations/0002_alter_job_max_retries_joblog.py +39 -0
  141. mojo/apps/jobs/models/__init__.py +6 -0
  142. mojo/apps/jobs/models/job.py +441 -0
  143. mojo/apps/jobs/rest/__init__.py +2 -0
  144. mojo/apps/jobs/rest/control.py +466 -0
  145. mojo/apps/jobs/rest/jobs.py +421 -0
  146. mojo/apps/jobs/scheduler.py +571 -0
  147. mojo/apps/jobs/services/__init__.py +6 -0
  148. mojo/apps/jobs/services/job_actions.py +465 -0
  149. mojo/apps/jobs/settings.py +209 -0
  150. mojo/apps/logit/migrations/0004_alter_log_level.py +18 -0
  151. mojo/apps/logit/models/log.py +7 -1
  152. mojo/apps/metrics/__init__.py +8 -1
  153. mojo/apps/metrics/redis_metrics.py +198 -0
  154. mojo/apps/metrics/rest/__init__.py +3 -0
  155. mojo/apps/metrics/rest/categories.py +266 -0
  156. mojo/apps/metrics/rest/helpers.py +48 -0
  157. mojo/apps/metrics/rest/permissions.py +99 -0
  158. mojo/apps/metrics/rest/values.py +277 -0
  159. mojo/apps/metrics/utils.py +19 -2
  160. mojo/decorators/auth.py +6 -1
  161. mojo/decorators/http.py +47 -3
  162. mojo/helpers/aws/__init__.py +45 -0
  163. mojo/helpers/aws/ec2.py +804 -0
  164. mojo/helpers/aws/iam.py +748 -0
  165. mojo/helpers/aws/inbound_email.py +309 -0
  166. mojo/helpers/aws/kms.py +413 -0
  167. mojo/helpers/aws/s3.py +451 -11
  168. mojo/helpers/aws/ses.py +483 -0
  169. mojo/helpers/aws/ses_domain.py +959 -0
  170. mojo/helpers/aws/sns.py +461 -0
  171. mojo/helpers/crypto/__init__.py +1 -1
  172. mojo/helpers/crypto/utils.py +15 -0
  173. mojo/helpers/dates.py +18 -0
  174. mojo/helpers/location/__init__.py +2 -0
  175. mojo/helpers/location/countries.py +262 -0
  176. mojo/helpers/location/geolocation.py +196 -0
  177. mojo/helpers/logit.py +37 -0
  178. mojo/helpers/redis/__init__.py +2 -0
  179. mojo/helpers/redis/adapter.py +606 -0
  180. mojo/helpers/redis/client.py +48 -0
  181. mojo/helpers/redis/pool.py +225 -0
  182. mojo/helpers/request.py +8 -0
  183. mojo/helpers/response.py +14 -2
  184. mojo/helpers/settings/__init__.py +2 -0
  185. mojo/helpers/{settings.py → settings/helper.py} +1 -37
  186. mojo/helpers/settings/parser.py +132 -0
  187. mojo/middleware/auth.py +1 -1
  188. mojo/middleware/cors.py +40 -0
  189. mojo/middleware/logging.py +131 -12
  190. mojo/middleware/mojo.py +10 -0
  191. mojo/models/rest.py +494 -65
  192. mojo/models/secrets.py +98 -3
  193. mojo/serializers/__init__.py +106 -0
  194. mojo/serializers/core/__init__.py +90 -0
  195. mojo/serializers/core/cache/__init__.py +121 -0
  196. mojo/serializers/core/cache/backends.py +518 -0
  197. mojo/serializers/core/cache/base.py +102 -0
  198. mojo/serializers/core/cache/disabled.py +181 -0
  199. mojo/serializers/core/cache/memory.py +287 -0
  200. mojo/serializers/core/cache/redis.py +533 -0
  201. mojo/serializers/core/cache/utils.py +454 -0
  202. mojo/serializers/core/manager.py +550 -0
  203. mojo/serializers/core/serializer.py +475 -0
  204. mojo/serializers/examples/settings.py +322 -0
  205. mojo/serializers/formats/csv.py +393 -0
  206. mojo/serializers/formats/localizers.py +509 -0
  207. mojo/serializers/{models.py → simple.py} +38 -15
  208. mojo/serializers/suggested_improvements.md +388 -0
  209. testit/client.py +1 -1
  210. testit/helpers.py +35 -4
  211. testit/runner.py +23 -6
  212. django_nativemojo-0.1.10.dist-info/METADATA +0 -96
  213. django_nativemojo-0.1.10.dist-info/RECORD +0 -194
  214. mojo/apps/metrics/rest/db.py +0 -0
  215. mojo/apps/notify/README.md +0 -91
  216. mojo/apps/notify/README_NOTIFICATIONS.md +0 -566
  217. mojo/apps/notify/admin.py +0 -52
  218. mojo/apps/notify/handlers/example_handlers.py +0 -516
  219. mojo/apps/notify/handlers/ses/__init__.py +0 -25
  220. mojo/apps/notify/handlers/ses/bounce.py +0 -0
  221. mojo/apps/notify/handlers/ses/complaint.py +0 -25
  222. mojo/apps/notify/handlers/ses/message.py +0 -86
  223. mojo/apps/notify/management/commands/__init__.py +0 -1
  224. mojo/apps/notify/management/commands/process_notifications.py +0 -370
  225. mojo/apps/notify/mod +0 -0
  226. mojo/apps/notify/models/__init__.py +0 -12
  227. mojo/apps/notify/models/account.py +0 -128
  228. mojo/apps/notify/models/attachment.py +0 -24
  229. mojo/apps/notify/models/bounce.py +0 -68
  230. mojo/apps/notify/models/complaint.py +0 -40
  231. mojo/apps/notify/models/inbox.py +0 -113
  232. mojo/apps/notify/models/inbox_message.py +0 -173
  233. mojo/apps/notify/models/outbox.py +0 -129
  234. mojo/apps/notify/models/outbox_message.py +0 -288
  235. mojo/apps/notify/models/template.py +0 -30
  236. mojo/apps/notify/providers/aws.py +0 -73
  237. mojo/apps/notify/rest/ses.py +0 -0
  238. mojo/apps/notify/utils/__init__.py +0 -2
  239. mojo/apps/notify/utils/notifications.py +0 -404
  240. mojo/apps/notify/utils/parsing.py +0 -202
  241. mojo/apps/notify/utils/render.py +0 -144
  242. mojo/apps/tasks/README.md +0 -118
  243. mojo/apps/tasks/__init__.py +0 -11
  244. mojo/apps/tasks/manager.py +0 -489
  245. mojo/apps/tasks/rest/__init__.py +0 -2
  246. mojo/apps/tasks/rest/hooks.py +0 -0
  247. mojo/apps/tasks/rest/tasks.py +0 -62
  248. mojo/apps/tasks/runner.py +0 -174
  249. mojo/apps/tasks/tq_handlers.py +0 -14
  250. mojo/helpers/aws/setup_email.py +0 -0
  251. mojo/helpers/redis.py +0 -10
  252. mojo/models/meta.py +0 -262
  253. mojo/ws4redis/README.md +0 -174
  254. mojo/ws4redis/__init__.py +0 -2
  255. mojo/ws4redis/client.py +0 -283
  256. mojo/ws4redis/connection.py +0 -327
  257. mojo/ws4redis/exceptions.py +0 -32
  258. mojo/ws4redis/redis.py +0 -183
  259. mojo/ws4redis/servers/base.py +0 -86
  260. mojo/ws4redis/servers/django.py +0 -171
  261. mojo/ws4redis/servers/uwsgi.py +0 -63
  262. mojo/ws4redis/settings.py +0 -45
  263. mojo/ws4redis/utf8validator.py +0 -128
  264. mojo/ws4redis/websocket.py +0 -403
  265. {django_nativemojo-0.1.10.dist-info → django_nativemojo-0.1.16.dist-info}/LICENSE +0 -0
  266. {django_nativemojo-0.1.10.dist-info → django_nativemojo-0.1.16.dist-info}/NOTICE +0 -0
  267. {django_nativemojo-0.1.10.dist-info → django_nativemojo-0.1.16.dist-info}/WHEEL +0 -0
  268. /mojo/apps/{notify → aws}/__init__.py +0 -0
  269. /mojo/apps/{notify/handlers → aws/migrations}/__init__.py +0 -0
  270. /mojo/apps/{notify/management → docit/markdown_plugins}/__init__.py +0 -0
  271. /mojo/apps/{notify/providers → docit/migrations}/__init__.py +0 -0
  272. /mojo/apps/{notify/rest → fileman/migrations}/__init__.py +0 -0
  273. /mojo/{ws4redis/servers → apps/jobs/examples}/__init__.py +0 -0
  274. /mojo/apps/{fileman/models/render.py → jobs/migrations/__init__.py} +0 -0
  275. /mojo/{serializers → rest}/openapi.py +0 -0
  276. /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
- get_file_manager,
5
- validate_file_request,
6
- initiate_upload,
7
- finalize_upload,
8
- direct_upload,
9
- get_download_url
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
- __all__ = [
13
- 'get_file_manager',
14
- 'validate_file_request',
15
- 'initiate_upload',
16
- 'finalize_upload',
17
- 'direct_upload',
18
- 'get_download_url'
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
+ ]
@@ -1,3 +1,5 @@
1
1
  from .event import Event
2
2
  from .rule import RuleSet, Rule
3
3
  from .incident import Incident
4
+ from .history import IncidentHistory
5
+ from .ticket import Ticket, TicketNote
@@ -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.IntegerField(default=0, db_index=True)
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