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
@@ -1,202 +0,0 @@
1
- from io import StringIO
2
- import email
3
- import re
4
- from email.utils import parseaddr, parsedate_to_datetime, getaddresses
5
- from email.header import decode_header
6
- from objict import objict
7
-
8
-
9
- def decode_payload(part):
10
- """
11
- Decode the email part payload.
12
-
13
- Args:
14
- part (email.message.Part): The email part.
15
-
16
- Returns:
17
- str: The decoded payload as a string.
18
- """
19
- charset = part.get_content_charset() or 'utf-8'
20
- return str(part.get_payload(decode=True), charset, 'replace')
21
-
22
-
23
- def parse_attachment(message_part):
24
- """
25
- Parse an email message part for attachments.
26
-
27
- Args:
28
- message_part (email.message.Part): The email message part.
29
-
30
- Returns:
31
- objict: An object representing the attachment, or None if not applicable.
32
- """
33
- content_disposition = message_part.get("Content-Disposition")
34
- content_type = message_part.get_content_type()
35
-
36
- if not content_disposition and content_type == "multipart/alternative":
37
- return None
38
-
39
- attachment = objict({
40
- 'dispositions': [],
41
- 'disposition': None,
42
- 'payload': message_part.get_payload(decode=False),
43
- 'charset': message_part.get_content_charset(),
44
- 'content_type': content_type,
45
- 'encoding': message_part.get("Content-Transfer-Encoding", "utf8"),
46
- 'content': None,
47
- 'name': None,
48
- 'create_date': None,
49
- 'mod_date': None,
50
- 'read_date': None,
51
- })
52
-
53
- dispositions = []
54
- if content_disposition:
55
- dispositions = content_disposition.strip().split(";")
56
- attachment['dispositions'] = dispositions
57
- attachment['disposition'] = dispositions[0]
58
-
59
- if attachment['disposition'] in [None, "inline"] and attachment['content_type'] in ["text/plain", "text/html"]:
60
- attachment.content = decode_payload(message_part)
61
-
62
- if content_disposition:
63
- for param in dispositions[1:]:
64
- name, value = param.split("=")
65
- name, value = name.strip().lower(), value.strip().strip('"\'')
66
- if name == "filename":
67
- attachment.name = value
68
- elif name in ["create-date", "creation-date"]:
69
- attachment.create_date = value
70
- elif name == "modification-date":
71
- attachment.mod_date = value
72
- elif name == "read-date":
73
- attachment.read_date = value
74
-
75
- return attachment
76
-
77
-
78
- def to_file_object(attachment):
79
- """
80
- Convert an attachment to a file-like object.
81
-
82
- Args:
83
- attachment (objict): The attachment object.
84
-
85
- Returns:
86
- StringIO: A StringIO object representing the attachment payload.
87
- """
88
- obj = StringIO(to_string(attachment['payload']))
89
- obj.name = attachment['name']
90
- obj.size = len(attachment['payload']) # 'size' is not typically an attribute of StringIO, but is set here for compatibility
91
- return obj
92
-
93
-
94
- def parse_addresses(input_string, force_name=False, emails_only=False):
95
- """
96
- Parse email addresses from a string.
97
-
98
- Args:
99
- input_string (str): The input string containing email addresses.
100
- force_name (bool): Force inclusion of the domain as the name if no name is present.
101
- emails_only (bool): Return only email addresses if True, otherwise return detailed information.
102
-
103
- Returns:
104
- list: A list of parsed email information or email addresses.
105
- """
106
- if not input_string:
107
- return []
108
-
109
- email_pattern = r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}'
110
- emails = re.findall(email_pattern, input_string)
111
-
112
- parsed_emails = []
113
- for addr in emails:
114
- addr = addr.strip().lower()
115
- name_match = re.search(r'([a-zA-Z\s]+)?\s*<{}>'.format(re.escape(addr)), input_string)
116
- name = name_match.group(1).strip() if name_match and name_match.group(1) else (addr.split('@')[1] if force_name else None)
117
- full_email = f"{name} <{addr}>" if name else addr
118
- parsed_emails.append(objict(name=name, email=addr, full_email=full_email))
119
-
120
- return [email.email for email in parsed_emails] if emails_only else parsed_emails
121
-
122
-
123
- def to_string(value):
124
- """
125
- Convert various types of values to a string.
126
-
127
- Args:
128
- value: The value to be converted.
129
-
130
- Returns:
131
- str: The string representation of the input value.
132
- """
133
- if isinstance(value, bytes):
134
- return value.decode()
135
- if isinstance(value, bytearray):
136
- return value.decode("utf-8")
137
- if isinstance(value, (int, float)):
138
- return str(value)
139
- return value
140
-
141
-
142
- def parse_raw_message(msg_obj):
143
- """
144
- Parse an email message and return a dictionary with its components.
145
-
146
- Args:
147
- msg_obj (str or email.message.Message): The email message object or its string representation.
148
-
149
- Returns:
150
- objict: A dictionary-like object containing email components.
151
- """
152
- if isinstance(msg_obj, str):
153
- msg_obj = email.message_from_string(msg_obj)
154
-
155
- subject, message, body, html = None, None, None, None
156
- attachments, body_parts, html_parts = [], [], []
157
-
158
- if msg_obj.get('Subject'):
159
- decoded_fragments = decode_header(msg_obj['Subject'])
160
- subject = ''.join(
161
- str(s, enc or 'utf-8', 'replace')
162
- for s, enc in decoded_fragments
163
- )
164
-
165
- for part in msg_obj.walk():
166
- attachment = parse_attachment(part)
167
- if attachment:
168
- if attachment.get('content'):
169
- (html_parts if attachment['content_type'] == "text/html" else body_parts).append(attachment.content)
170
- else:
171
- attachments.append(attachment)
172
-
173
- if body_parts:
174
- body = ''.join(body_parts).strip()
175
- message = '\n'.join(
176
- line.strip() for line in body.split('\n') if not line.startswith('>') or (blocks := 0) < 3
177
- ).strip()
178
-
179
- html = ''.join(html_parts) if html_parts else None
180
-
181
- from_addr = parseaddr(msg_obj.get('From', ''))
182
-
183
- date_time = None
184
- if msg_obj.get('Date'):
185
- date_time = parsedate_to_datetime(msg_obj['Date'])
186
- if date_time:
187
- date_time = date_time.replace(tzinfo=None)
188
-
189
- return objict({
190
- 'subject': subject.strip() if subject else '',
191
- 'body': body,
192
- 'sent_at': date_time,
193
- 'message': message,
194
- 'html': html,
195
- 'from_email': from_addr[1],
196
- 'from_name': from_addr[0],
197
- 'to': msg_obj.get("To"),
198
- 'to_addrs': getaddresses(msg_obj.get_all("To", [])),
199
- 'cc': msg_obj.get("Cc"),
200
- 'cc_addrs': getaddresses(msg_obj.get_all("Cc", [])),
201
- 'attachments': attachments,
202
- })
@@ -1,144 +0,0 @@
1
- from django.template.loader import render_to_string
2
- from email.mime.multipart import MIMEMultipart
3
- from email.mime.text import MIMEText
4
- from email.mime.application import MIMEApplication
5
- from mojo.helpers.settings import settings
6
- import os
7
- from objict import objict
8
- from mailman.models.template import MailTemplate
9
- import csv
10
- from io import StringIO
11
- import re
12
-
13
-
14
- def create_message(sender, recipients, subject, text=None, html=None, attachments=None, replyto=None):
15
- """
16
- Prepares an email message with given parameters.
17
-
18
- :param sender: The sender of the email.
19
- :param recipients: Can be a single email (string), or multiple emails (list or tuple).
20
- :param subject: Subject of the email.
21
- :param text: Plain text content of the email.
22
- :param html: HTML content of the email.
23
- :param attachments: List of attachments, can be strings or bytes.
24
- :param replyto: Email for reply-to address.
25
- :return: An object containing email details.
26
- """
27
- recipients = _parse_recipients(recipients)
28
- attachments = attachments or []
29
-
30
- message = objict(sender=sender, recipients=recipients)
31
- message.msg = create_multipart_message(sender, recipients, subject, text, html, attachments, replyto)
32
- return message
33
-
34
- def _parse_recipients(recipients):
35
- """
36
- Parses recipients input into a list of emails.
37
-
38
- :param recipients: Can be a single email (string) or delimited string (comma/semicolon), or a list/tuple.
39
- :return: List of email strings.
40
- """
41
- if isinstance(recipients, str):
42
- if ',' in recipients:
43
- recipients = [t.strip() for t in recipients.split(',')]
44
- elif ';' in recipients:
45
- recipients = [t.strip() for t in recipients.split(';')]
46
- else:
47
- recipients = [recipients]
48
- elif not isinstance(recipients, (tuple, list)):
49
- recipients = [recipients]
50
- return recipients
51
-
52
- def create_multipart_message(sender, recipients, subject, text=None, html=None, attachments=None, replyto=None):
53
- """
54
- Helper function to create a MIME multipart message with text, HTML, and attachments.
55
-
56
- :param sender: Email sender.
57
- :param recipients: List of recipient emails.
58
- :param subject: Subject of the email.
59
- :param text: Text content of the email.
60
- :param html: HTML content of the email.
61
- :param attachments: List of attachments.
62
- :param replyto: Reply-to email address.
63
- :return: A prepared MIMEMultipart message.
64
- """
65
- multipart_content_subtype = 'alternative' if text and html else 'mixed'
66
- msg = MIMEMultipart(multipart_content_subtype)
67
- msg['Subject'] = subject
68
- msg['From'] = sender
69
- msg['To'] = ', '.join(recipients)
70
- if replyto:
71
- msg.add_header('reply-to', replyto)
72
-
73
- if text:
74
- msg.attach(MIMEText(text, 'plain'))
75
- if html:
76
- # Remove non-ASCII characters to prevent encoding issues
77
- html = html.encode('ascii', 'ignore').decode('ascii')
78
- msg.attach(MIMEText(html, 'html'))
79
-
80
- for index, atch in enumerate(attachments or [], start=1):
81
- if isinstance(atch, (str, bytes)):
82
- atch = objict(name=f"attachment{index}.txt", data=atch, mimetype="text/plain")
83
- part = MIMEApplication(atch.data)
84
- part.add_header('Content-Type', atch.mimetype)
85
- part.add_header('Content-Disposition', 'attachment', filename=os.path.basename(atch.name))
86
- msg.attach(part)
87
-
88
- return msg
89
-
90
- def render_template(template_name, context, group=None):
91
- """
92
- Renders an email template with a provided context.
93
-
94
- :param template_name: Name of the template.
95
- :param context: Context to render the template.
96
- :param group: Template group filter (optional).
97
- :return: Rendered template as string or None.
98
- """
99
- context.update({
100
- "SITE_LABEL": settings.SITE_LABEL,
101
- "BASE_URL": settings.BASE_URL,
102
- "SITE_LOGO": settings.SITE_LOGO,
103
- "SERVER_NAME": settings.SERVER_NAME,
104
- "UNSUBSCRIBE_URL": settings.get("UNSUBSCRIBE_URL", f"{settings.BASE_URL}/api/account/unsubscribe"),
105
- "version": settings.VERSION,
106
- "COMPANY_NAME": settings.get("COMPANY_NAME", context.get("COMPANY_NAME", ""))
107
- })
108
-
109
- if template_name.endswith(("html", ".txt")):
110
- return render_to_string(template_name, context)
111
-
112
- qset = MailTemplate.objects.filter(name=template_name)
113
- if group is not None:
114
- qset = qset.filter(group=group)
115
-
116
- mtemp = qset.last()
117
- return mtemp.render(context) if mtemp else None
118
-
119
- def generate_csv(qset, fields, name):
120
- """
121
- Generates a CSV from a queryset.
122
-
123
- :param qset: Queryset containing data.
124
- :param fields: Fields to include in CSV.
125
- :param name: Name for the CSV file.
126
- :return: An object with CSV data and metadata.
127
- """
128
- csv_io = StringIO()
129
- csvwriter = csv.writer(csv_io)
130
-
131
- csvwriter.writerow(fields)
132
- for row in qset.values_list(*fields):
133
- csvwriter.writerow(map(str, row))
134
-
135
- return objict(name=name, file=csv_io, data=csv_io.getvalue(), mimetype="text/csv")
136
-
137
- def is_html(text):
138
- """
139
- Determine if the provided string contains HTML.
140
-
141
- :param text: The text to be checked.
142
- :return: True if HTML tags are found, False otherwise.
143
- """
144
- return bool(re.search(r'<[a-zA-Z0-9]+>.*?<\/[a-zA-Z0-9]+>', text))
mojo/apps/tasks/README.md DELETED
@@ -1,118 +0,0 @@
1
- # Taskit
2
-
3
- Taskit is a task management and processing library that uses Redis to handle task states across multiple channels. It provides a flexible and efficient way to publish, execute, and track tasks. Taskit is built with Django and is intended to be used in projects where distributed or asynchronous task execution is required.
4
-
5
- ## Table of Contents
6
-
7
- - [Features](#features)
8
- - [Installation](#installation)
9
- - [Configuration](#configuration)
10
- - [Usage](#usage)
11
- - [Publishing Tasks](#publishing-tasks)
12
- - [Monitoring Tasks](#monitoring-tasks)
13
- - [Advanced Usage](#advanced-usage)
14
- - [API Endpoints](#api-endpoints)
15
- - [Command Line Interface](#command-line-interface)
16
- - [License](#license)
17
-
18
- ## Features
19
-
20
- - Asynchronous task management using Redis-backed storage.
21
- - Support for multiple channels to organize and prioritize tasks.
22
- - Task lifecycle management including pending, running, completed, and error states.
23
- - Built-in REST API for monitoring task statuses.
24
- - Thread pool executor for concurrent task execution.
25
-
26
-
27
- ## Configuration
28
-
29
- Ensure your Django project is set up to use Taskit by configuring the necessary settings and Redis connection. Update your `settings.py` file:
30
-
31
- ```python
32
- # settings.py
33
-
34
- TASKIT_CHANNELS = ['channel1', 'channel2', 'broadcast']
35
- REDIS_HOST = 'localhost'
36
- REDIS_PORT = 6379
37
- ```
38
-
39
- ## Usage
40
-
41
- ### Publishing Tasks
42
-
43
- To publish a new task, use the `TaskManager` instance provided by Taskit:
44
-
45
- ```python
46
- from taskit.manager import TaskManager
47
-
48
- channels = ['channel1', 'channel2']
49
- task_manager = TaskManager(channels)
50
-
51
- def my_task_function(task_data):
52
- data = task_data.data
53
- print(f"Processing data: {data}")
54
-
55
- task_data = {'key': 'value'}
56
- task_id = task_manager.publish('module.my_task_function', task_data, channel='channel1')
57
-
58
- print(f"Task {task_id} published to channel1")
59
- ```
60
-
61
- ### Monitoring Tasks
62
-
63
- Taskit includes a REST API for checking the status of tasks:
64
-
65
- - `GET /status` - Summary of all tasks across all channels.
66
- - `GET /pending` - List of pending tasks.
67
- - `GET /completed` - List of completed tasks.
68
- - `GET /running` - List of tasks currently running.
69
- - `GET /errors` - List of tasks that encountered errors.
70
-
71
- ### Advanced Usage
72
-
73
- Use the `TaskEngine` to start the task processing engine. This engine will listen for tasks on the specified channels and execute them using a thread pool:
74
-
75
- ```python
76
- from taskit.runner import TaskEngine
77
-
78
- engine = TaskEngine(['channel1', 'channel2'], max_workers=10)
79
- engine.run()
80
- ```
81
-
82
- You can also leverage the daemon capabilities by using CLI commands like `--start` and `--stop` to manage the task engine:
83
-
84
- ```sh
85
- python manage.py taskit --start
86
- ```
87
-
88
- ## API Endpoints
89
-
90
- Taskit provides several Django REST API endpoints for managing tasks:
91
-
92
- - **Status:** `GET /status/` - Get the overall status of the task system.
93
- - **Pending Tasks:** `GET /pending/` - Retrieve all pending tasks.
94
- - **Completed Tasks:** `GET /completed/` - Retrieve all completed tasks.
95
- - **Running Tasks:** `GET /running/` - Retrieve all running tasks.
96
- - **Error Tasks:** `GET /errors/` - Retrieve tasks that encountered errors.
97
-
98
- ## Command Line Interface
99
-
100
- Taskit also offers CLI support for managing the task engine:
101
-
102
- ```sh
103
- python manage.py taskit --help
104
- ```
105
-
106
- Options:
107
- - `--start`: Start the task engine daemon.
108
- - `--stop`: Stop the task engine daemon.
109
- - `-f, --foreground`: Run the task engine in the foreground.
110
- - `-v, --verbose`: Enable verbose logging for more detailed output.
111
-
112
- ## License
113
-
114
- This project is licensed under the MIT License. See the LICENSE file for details.
115
-
116
- ---
117
-
118
- This README file provides a comprehensive overview and instructions for using Taskit. Please feel free to ask further questions or raise issues in the GitHub repository.
@@ -1,11 +0,0 @@
1
- from mojo.helpers.settings import settings
2
-
3
-
4
- def get_manager():
5
- from .manager import TaskManager
6
- return TaskManager(settings.TASKIT_CHANNELS)
7
-
8
-
9
- def publish(channel, function, data, expires=1800):
10
- man = get_manager()
11
- return man.publish(function, data, channel=channel, expires=expires)