django-nativemojo 0.1.10__py3-none-any.whl → 0.1.15__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 (120) hide show
  1. django_nativemojo-0.1.15.dist-info/METADATA +136 -0
  2. {django_nativemojo-0.1.10.dist-info → django_nativemojo-0.1.15.dist-info}/RECORD +105 -65
  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 +531 -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/models/group.py +25 -7
  10. mojo/apps/account/models/member.py +15 -4
  11. mojo/apps/account/models/user.py +197 -20
  12. mojo/apps/account/rest/group.py +1 -0
  13. mojo/apps/account/rest/user.py +6 -2
  14. mojo/apps/aws/rest/__init__.py +1 -0
  15. mojo/apps/aws/rest/s3.py +64 -0
  16. mojo/apps/fileman/README.md +8 -8
  17. mojo/apps/fileman/backends/base.py +76 -70
  18. mojo/apps/fileman/backends/filesystem.py +86 -86
  19. mojo/apps/fileman/backends/s3.py +200 -108
  20. mojo/apps/fileman/migrations/0001_initial.py +106 -0
  21. mojo/apps/fileman/migrations/0002_filemanager_parent_alter_filemanager_max_file_size.py +24 -0
  22. mojo/apps/fileman/migrations/0003_remove_file_fileman_fil_upload__c4bc35_idx_and_more.py +25 -0
  23. mojo/apps/fileman/migrations/0004_remove_file_original_filename_and_more.py +39 -0
  24. mojo/apps/fileman/migrations/0005_alter_file_upload_token.py +18 -0
  25. mojo/apps/fileman/migrations/0006_file_download_url_filemanager_forever_urls.py +23 -0
  26. mojo/apps/fileman/migrations/0007_remove_filemanager_forever_urls_and_more.py +22 -0
  27. mojo/apps/fileman/migrations/0008_file_category.py +18 -0
  28. mojo/apps/fileman/migrations/0009_rename_file_path_file_storage_file_path.py +18 -0
  29. mojo/apps/fileman/migrations/0010_filerendition.py +33 -0
  30. mojo/apps/fileman/migrations/0011_alter_filerendition_original_file.py +19 -0
  31. mojo/apps/fileman/models/__init__.py +1 -5
  32. mojo/apps/fileman/models/file.py +204 -58
  33. mojo/apps/fileman/models/manager.py +161 -31
  34. mojo/apps/fileman/models/rendition.py +118 -0
  35. mojo/apps/fileman/renderer/__init__.py +111 -0
  36. mojo/apps/fileman/renderer/audio.py +403 -0
  37. mojo/apps/fileman/renderer/base.py +205 -0
  38. mojo/apps/fileman/renderer/document.py +404 -0
  39. mojo/apps/fileman/renderer/image.py +222 -0
  40. mojo/apps/fileman/renderer/utils.py +297 -0
  41. mojo/apps/fileman/renderer/video.py +304 -0
  42. mojo/apps/fileman/rest/__init__.py +1 -18
  43. mojo/apps/fileman/rest/upload.py +22 -32
  44. mojo/apps/fileman/signals.py +58 -0
  45. mojo/apps/fileman/tasks.py +254 -0
  46. mojo/apps/fileman/utils/__init__.py +40 -16
  47. mojo/apps/incident/migrations/0005_incidenthistory.py +39 -0
  48. mojo/apps/incident/migrations/0006_alter_incident_state.py +18 -0
  49. mojo/apps/incident/models/__init__.py +1 -0
  50. mojo/apps/incident/models/history.py +36 -0
  51. mojo/apps/incident/models/incident.py +1 -1
  52. mojo/apps/incident/reporter.py +3 -1
  53. mojo/apps/incident/rest/event.py +7 -1
  54. mojo/apps/logit/migrations/0004_alter_log_level.py +18 -0
  55. mojo/apps/logit/models/log.py +4 -1
  56. mojo/apps/metrics/utils.py +2 -2
  57. mojo/apps/notify/handlers/ses/message.py +1 -1
  58. mojo/apps/notify/providers/aws.py +2 -2
  59. mojo/apps/tasks/__init__.py +34 -1
  60. mojo/apps/tasks/manager.py +200 -45
  61. mojo/apps/tasks/rest/tasks.py +24 -10
  62. mojo/apps/tasks/runner.py +283 -18
  63. mojo/apps/tasks/task.py +99 -0
  64. mojo/apps/tasks/tq_handlers.py +118 -0
  65. mojo/decorators/auth.py +6 -1
  66. mojo/decorators/http.py +7 -2
  67. mojo/helpers/aws/__init__.py +41 -0
  68. mojo/helpers/aws/ec2.py +804 -0
  69. mojo/helpers/aws/iam.py +748 -0
  70. mojo/helpers/aws/s3.py +451 -11
  71. mojo/helpers/aws/ses.py +483 -0
  72. mojo/helpers/aws/sns.py +461 -0
  73. mojo/helpers/crypto/__pycache__/hash.cpython-310.pyc +0 -0
  74. mojo/helpers/crypto/__pycache__/sign.cpython-310.pyc +0 -0
  75. mojo/helpers/crypto/__pycache__/utils.cpython-310.pyc +0 -0
  76. mojo/helpers/dates.py +18 -0
  77. mojo/helpers/response.py +6 -2
  78. mojo/helpers/settings/__init__.py +2 -0
  79. mojo/helpers/{settings.py → settings/helper.py} +1 -37
  80. mojo/helpers/settings/parser.py +132 -0
  81. mojo/middleware/logging.py +1 -1
  82. mojo/middleware/mojo.py +5 -0
  83. mojo/models/rest.py +261 -46
  84. mojo/models/secrets.py +13 -4
  85. mojo/serializers/__init__.py +100 -0
  86. mojo/serializers/advanced/README.md +363 -0
  87. mojo/serializers/advanced/__init__.py +247 -0
  88. mojo/serializers/advanced/formats/__init__.py +28 -0
  89. mojo/serializers/advanced/formats/csv.py +416 -0
  90. mojo/serializers/advanced/formats/excel.py +516 -0
  91. mojo/serializers/advanced/formats/json.py +239 -0
  92. mojo/serializers/advanced/formats/localizers.py +509 -0
  93. mojo/serializers/advanced/formats/response.py +485 -0
  94. mojo/serializers/advanced/serializer.py +568 -0
  95. mojo/serializers/manager.py +501 -0
  96. mojo/serializers/optimized.py +618 -0
  97. mojo/serializers/settings_example.py +322 -0
  98. mojo/serializers/{models.py → simple.py} +38 -15
  99. testit/helpers.py +21 -4
  100. django_nativemojo-0.1.10.dist-info/METADATA +0 -96
  101. mojo/apps/metrics/rest/db.py +0 -0
  102. mojo/helpers/aws/setup_email.py +0 -0
  103. mojo/ws4redis/README.md +0 -174
  104. mojo/ws4redis/__init__.py +0 -2
  105. mojo/ws4redis/client.py +0 -283
  106. mojo/ws4redis/connection.py +0 -327
  107. mojo/ws4redis/exceptions.py +0 -32
  108. mojo/ws4redis/redis.py +0 -183
  109. mojo/ws4redis/servers/base.py +0 -86
  110. mojo/ws4redis/servers/django.py +0 -171
  111. mojo/ws4redis/servers/uwsgi.py +0 -63
  112. mojo/ws4redis/settings.py +0 -45
  113. mojo/ws4redis/utf8validator.py +0 -128
  114. mojo/ws4redis/websocket.py +0 -403
  115. {django_nativemojo-0.1.10.dist-info → django_nativemojo-0.1.15.dist-info}/LICENSE +0 -0
  116. {django_nativemojo-0.1.10.dist-info → django_nativemojo-0.1.15.dist-info}/NOTICE +0 -0
  117. {django_nativemojo-0.1.10.dist-info → django_nativemojo-0.1.15.dist-info}/WHEEL +0 -0
  118. /mojo/{ws4redis/servers → apps/aws}/__init__.py +0 -0
  119. /mojo/apps/{fileman/models/render.py → aws/models/__init__.py} +0 -0
  120. /mojo/apps/fileman/{rest/__init__ → migrations/__init__.py} +0 -0
@@ -0,0 +1,461 @@
1
+ """
2
+ AWS SNS Helper Module
3
+
4
+ Provides simple interfaces for managing AWS SNS (Simple Notification Service).
5
+ """
6
+
7
+ import json
8
+ import logging
9
+ import boto3
10
+ import botocore
11
+ from typing import Dict, List, Optional, Union, Any
12
+
13
+ from .client import get_session
14
+ from mojo.helpers.settings import settings
15
+ from mojo.helpers import logit
16
+
17
+ logger = logit.get_logger(__name__)
18
+
19
+
20
+ class SNSTopic:
21
+ """
22
+ Simple interface for managing SNS topics.
23
+ """
24
+
25
+ def __init__(self, name: str, access_key: Optional[str] = None,
26
+ secret_key: Optional[str] = None, region: Optional[str] = None):
27
+ """
28
+ Initialize a topic manager for the specified SNS topic.
29
+
30
+ Args:
31
+ name: The topic name
32
+ access_key: AWS access key, defaults to settings.AWS_KEY
33
+ secret_key: AWS secret key, defaults to settings.AWS_SECRET
34
+ region: AWS region, defaults to settings.AWS_REGION if available
35
+ """
36
+ self.name = name
37
+ self.access_key = access_key or settings.AWS_KEY
38
+ self.secret_key = secret_key or settings.AWS_SECRET
39
+ self.region = region or getattr(settings, 'AWS_REGION', 'us-east-1')
40
+
41
+ session = get_session(self.access_key, self.secret_key, self.region)
42
+ self.client = session.client('sns')
43
+ self.arn = None
44
+ self.exists = self._check_exists()
45
+
46
+ def _check_exists(self) -> bool:
47
+ """
48
+ Check if the topic exists and set ARN if it does.
49
+
50
+ Returns:
51
+ True if the topic exists, False otherwise
52
+ """
53
+ try:
54
+ # List topics and check if this one exists
55
+ response = self.client.list_topics()
56
+
57
+ for topic in response.get('Topics', []):
58
+ topic_arn = topic['TopicArn']
59
+ if topic_arn.split(':')[-1] == self.name:
60
+ self.arn = topic_arn
61
+ return True
62
+
63
+ return False
64
+ except botocore.exceptions.ClientError as e:
65
+ logger.error(f"Error checking topic existence: {e}")
66
+ return False
67
+
68
+ def create(self, display_name: Optional[str] = None,
69
+ tags: Optional[List[Dict[str, str]]] = None) -> bool:
70
+ """
71
+ Create an SNS topic.
72
+
73
+ Args:
74
+ display_name: Display name for the topic
75
+ tags: Optional tags for the topic
76
+
77
+ Returns:
78
+ True if topic was created, False if it already exists
79
+ """
80
+ if self.exists:
81
+ logger.info(f"Topic {self.name} already exists")
82
+ return False
83
+
84
+ try:
85
+ # Prepare creation parameters
86
+ create_params = {'Name': self.name}
87
+ attributes = {}
88
+
89
+ if display_name:
90
+ attributes['DisplayName'] = display_name
91
+
92
+ if attributes:
93
+ create_params['Attributes'] = attributes
94
+
95
+ if tags:
96
+ create_params['Tags'] = tags
97
+
98
+ # Create the topic
99
+ response = self.client.create_topic(**create_params)
100
+ self.arn = response['TopicArn']
101
+ self.exists = True
102
+ return True
103
+ except botocore.exceptions.ClientError as e:
104
+ logger.error(f"Failed to create topic {self.name}: {e}")
105
+ return False
106
+
107
+ def delete(self) -> bool:
108
+ """
109
+ Delete the SNS topic.
110
+
111
+ Returns:
112
+ True if successfully deleted, False otherwise
113
+ """
114
+ if not self.exists:
115
+ logger.info(f"Topic {self.name} does not exist")
116
+ return False
117
+
118
+ try:
119
+ self.client.delete_topic(TopicArn=self.arn)
120
+ self.arn = None
121
+ self.exists = False
122
+ return True
123
+ except botocore.exceptions.ClientError as e:
124
+ logger.error(f"Failed to delete topic {self.name}: {e}")
125
+ return False
126
+
127
+ def publish(self, message: str, subject: Optional[str] = None,
128
+ attributes: Optional[Dict[str, Dict]] = None,
129
+ message_structure: Optional[str] = None) -> Dict:
130
+ """
131
+ Publish a message to the topic.
132
+
133
+ Args:
134
+ message: Message to publish
135
+ subject: Optional subject for the message
136
+ attributes: Optional message attributes
137
+ message_structure: Optional message structure (e.g., 'json')
138
+
139
+ Returns:
140
+ Response dict containing MessageId if successful
141
+ """
142
+ if not self.exists:
143
+ logger.warning(f"Topic {self.name} does not exist")
144
+ return {'Error': 'Topic does not exist'}
145
+
146
+ try:
147
+ # Prepare publish parameters
148
+ publish_params = {
149
+ 'TopicArn': self.arn,
150
+ 'Message': message
151
+ }
152
+
153
+ if subject:
154
+ publish_params['Subject'] = subject
155
+
156
+ if attributes:
157
+ publish_params['MessageAttributes'] = attributes
158
+
159
+ if message_structure:
160
+ publish_params['MessageStructure'] = message_structure
161
+
162
+ # Publish the message
163
+ response = self.client.publish(**publish_params)
164
+ logger.info(f"Message published successfully with MessageId: {response['MessageId']}")
165
+ return response
166
+ except botocore.exceptions.ClientError as e:
167
+ logger.error(f"Failed to publish message to topic {self.name}: {e}")
168
+ return {'Error': str(e)}
169
+
170
+ def set_attributes(self, attributes: Dict[str, str]) -> bool:
171
+ """
172
+ Set topic attributes.
173
+
174
+ Args:
175
+ attributes: Dictionary of attribute name-value pairs
176
+
177
+ Returns:
178
+ True if successful, False otherwise
179
+ """
180
+ if not self.exists:
181
+ logger.warning(f"Topic {self.name} does not exist")
182
+ return False
183
+
184
+ try:
185
+ self.client.set_topic_attributes(
186
+ TopicArn=self.arn,
187
+ AttributeName=list(attributes.keys())[0], # Can only set one at a time
188
+ AttributeValue=list(attributes.values())[0]
189
+ )
190
+ return True
191
+ except botocore.exceptions.ClientError as e:
192
+ logger.error(f"Failed to set attributes for topic {self.name}: {e}")
193
+ return False
194
+
195
+ def get_attributes(self) -> Dict[str, str]:
196
+ """
197
+ Get topic attributes.
198
+
199
+ Returns:
200
+ Dictionary of topic attributes
201
+ """
202
+ if not self.exists:
203
+ logger.warning(f"Topic {self.name} does not exist")
204
+ return {}
205
+
206
+ try:
207
+ response = self.client.get_topic_attributes(TopicArn=self.arn)
208
+ return response.get('Attributes', {})
209
+ except botocore.exceptions.ClientError as e:
210
+ logger.error(f"Failed to get attributes for topic {self.name}: {e}")
211
+ return {}
212
+
213
+ def list_subscriptions(self) -> List[Dict]:
214
+ """
215
+ List all subscriptions to this topic.
216
+
217
+ Returns:
218
+ List of subscription dictionaries
219
+ """
220
+ if not self.exists:
221
+ logger.warning(f"Topic {self.name} does not exist")
222
+ return []
223
+
224
+ try:
225
+ response = self.client.list_subscriptions_by_topic(TopicArn=self.arn)
226
+ return response.get('Subscriptions', [])
227
+ except botocore.exceptions.ClientError as e:
228
+ logger.error(f"Failed to list subscriptions for topic {self.name}: {e}")
229
+ return []
230
+
231
+ @staticmethod
232
+ def list_all_topics() -> List[Dict]:
233
+ """
234
+ List all SNS topics.
235
+
236
+ Returns:
237
+ List of topic dictionaries
238
+ """
239
+ client = boto3.client('sns',
240
+ aws_access_key_id=settings.AWS_KEY,
241
+ aws_secret_access_key=settings.AWS_SECRET,
242
+ region_name=getattr(settings, 'AWS_REGION', 'us-east-1'))
243
+
244
+ try:
245
+ response = client.list_topics()
246
+ return response.get('Topics', [])
247
+ except botocore.exceptions.ClientError as e:
248
+ logger.error(f"Failed to list topics: {e}")
249
+ return []
250
+
251
+
252
+ class SNSSubscription:
253
+ """
254
+ Simple interface for managing SNS subscriptions.
255
+ """
256
+
257
+ def __init__(self, topic_arn: str, access_key: Optional[str] = None,
258
+ secret_key: Optional[str] = None, region: Optional[str] = None):
259
+ """
260
+ Initialize a subscription manager for the specified SNS topic.
261
+
262
+ Args:
263
+ topic_arn: The ARN of the topic
264
+ access_key: AWS access key, defaults to settings.AWS_KEY
265
+ secret_key: AWS secret key, defaults to settings.AWS_SECRET
266
+ region: AWS region, defaults to settings.AWS_REGION if available
267
+ """
268
+ self.topic_arn = topic_arn
269
+ self.access_key = access_key or settings.AWS_KEY
270
+ self.secret_key = secret_key or settings.AWS_SECRET
271
+ self.region = region or getattr(settings, 'AWS_REGION', 'us-east-1')
272
+
273
+ session = get_session(self.access_key, self.secret_key, self.region)
274
+ self.client = session.client('sns')
275
+
276
+ def subscribe(self, protocol: str, endpoint: str,
277
+ attributes: Optional[Dict[str, str]] = None,
278
+ return_subscription_arn: bool = False) -> Dict:
279
+ """
280
+ Subscribe an endpoint to the topic.
281
+
282
+ Args:
283
+ protocol: Protocol to use (http, https, email, sms, sqs, application, lambda)
284
+ endpoint: Endpoint to subscribe
285
+ attributes: Optional subscription attributes
286
+ return_subscription_arn: Whether to return the subscription ARN
287
+
288
+ Returns:
289
+ Response dict containing SubscriptionArn
290
+ """
291
+ try:
292
+ # Prepare subscription parameters
293
+ subscribe_params = {
294
+ 'TopicArn': self.topic_arn,
295
+ 'Protocol': protocol,
296
+ 'Endpoint': endpoint,
297
+ 'ReturnSubscriptionArn': return_subscription_arn
298
+ }
299
+
300
+ if attributes:
301
+ subscribe_params['Attributes'] = attributes
302
+
303
+ # Create the subscription
304
+ response = self.client.subscribe(**subscribe_params)
305
+ logger.info(f"Subscription created: {response.get('SubscriptionArn')}")
306
+ return response
307
+ except botocore.exceptions.ClientError as e:
308
+ logger.error(f"Failed to create subscription: {e}")
309
+ return {'Error': str(e)}
310
+
311
+ def unsubscribe(self, subscription_arn: str) -> bool:
312
+ """
313
+ Unsubscribe from the topic.
314
+
315
+ Args:
316
+ subscription_arn: ARN of the subscription to delete
317
+
318
+ Returns:
319
+ True if successful, False otherwise
320
+ """
321
+ try:
322
+ self.client.unsubscribe(SubscriptionArn=subscription_arn)
323
+ logger.info(f"Subscription {subscription_arn} deleted")
324
+ return True
325
+ except botocore.exceptions.ClientError as e:
326
+ logger.error(f"Failed to delete subscription {subscription_arn}: {e}")
327
+ return False
328
+
329
+ def set_attributes(self, subscription_arn: str,
330
+ attribute_name: str, attribute_value: str) -> bool:
331
+ """
332
+ Set subscription attributes.
333
+
334
+ Args:
335
+ subscription_arn: ARN of the subscription
336
+ attribute_name: Name of the attribute to set
337
+ attribute_value: Value of the attribute
338
+
339
+ Returns:
340
+ True if successful, False otherwise
341
+ """
342
+ try:
343
+ self.client.set_subscription_attributes(
344
+ SubscriptionArn=subscription_arn,
345
+ AttributeName=attribute_name,
346
+ AttributeValue=attribute_value
347
+ )
348
+ return True
349
+ except botocore.exceptions.ClientError as e:
350
+ logger.error(f"Failed to set attributes for subscription {subscription_arn}: {e}")
351
+ return False
352
+
353
+ def get_attributes(self, subscription_arn: str) -> Dict[str, str]:
354
+ """
355
+ Get subscription attributes.
356
+
357
+ Args:
358
+ subscription_arn: ARN of the subscription
359
+
360
+ Returns:
361
+ Dictionary of subscription attributes
362
+ """
363
+ try:
364
+ response = self.client.get_subscription_attributes(
365
+ SubscriptionArn=subscription_arn
366
+ )
367
+ return response.get('Attributes', {})
368
+ except botocore.exceptions.ClientError as e:
369
+ logger.error(f"Failed to get attributes for subscription {subscription_arn}: {e}")
370
+ return {}
371
+
372
+ @staticmethod
373
+ def list_all_subscriptions() -> List[Dict]:
374
+ """
375
+ List all SNS subscriptions.
376
+
377
+ Returns:
378
+ List of subscription dictionaries
379
+ """
380
+ client = boto3.client('sns',
381
+ aws_access_key_id=settings.AWS_KEY,
382
+ aws_secret_access_key=settings.AWS_SECRET,
383
+ region_name=getattr(settings, 'AWS_REGION', 'us-east-1'))
384
+
385
+ try:
386
+ response = client.list_subscriptions()
387
+ return response.get('Subscriptions', [])
388
+ except botocore.exceptions.ClientError as e:
389
+ logger.error(f"Failed to list subscriptions: {e}")
390
+ return []
391
+
392
+
393
+ # Utility functions
394
+ def create_topic_and_subscribe(topic_name: str, protocol: str, endpoint: str,
395
+ display_name: Optional[str] = None) -> Dict:
396
+ """
397
+ Create a topic and subscribe an endpoint in one operation.
398
+
399
+ Args:
400
+ topic_name: Name of the topic to create
401
+ protocol: Protocol to use for subscription
402
+ endpoint: Endpoint to subscribe
403
+ display_name: Optional display name for the topic
404
+
405
+ Returns:
406
+ Dict with topic ARN and subscription ARN
407
+ """
408
+ result = {}
409
+
410
+ # Create the topic
411
+ topic = SNSTopic(topic_name)
412
+ if not topic.exists:
413
+ if not topic.create(display_name=display_name):
414
+ return {'Error': f"Failed to create topic {topic_name}"}
415
+
416
+ result['TopicArn'] = topic.arn
417
+
418
+ # Create the subscription
419
+ subscription = SNSSubscription(topic.arn)
420
+ response = subscription.subscribe(protocol, endpoint, return_subscription_arn=True)
421
+
422
+ if 'Error' in response:
423
+ result['Error'] = response['Error']
424
+ else:
425
+ result['SubscriptionArn'] = response.get('SubscriptionArn')
426
+
427
+ return result
428
+
429
+
430
+ def publish_message(topic_name: str, message: str, subject: Optional[str] = None) -> Dict:
431
+ """
432
+ Publish a message to a topic by name.
433
+
434
+ Args:
435
+ topic_name: Name of the topic
436
+ message: Message to publish
437
+ subject: Optional message subject
438
+
439
+ Returns:
440
+ Response dict containing MessageId if successful
441
+ """
442
+ topic = SNSTopic(topic_name)
443
+
444
+ if not topic.exists:
445
+ return {'Error': f"Topic {topic_name} does not exist"}
446
+
447
+ return topic.publish(message, subject)
448
+
449
+
450
+ def get_topic_arn(topic_name: str) -> Optional[str]:
451
+ """
452
+ Get the ARN for a topic by name.
453
+
454
+ Args:
455
+ topic_name: Name of the topic
456
+
457
+ Returns:
458
+ Topic ARN if found, None otherwise
459
+ """
460
+ topic = SNSTopic(topic_name)
461
+ return topic.arn if topic.exists else None
mojo/helpers/dates.py CHANGED
@@ -67,3 +67,21 @@ def subtract(when=None, seconds=None, minutes=None, hours=None, days=None):
67
67
 
68
68
  def has_time_elsapsed(when, seconds=None, minutes=None, hours=None, days=None):
69
69
  return utcnow() >= add(when, seconds, minutes, hours, days)
70
+
71
+
72
+ def is_today(when, timezone=None):
73
+ if timezone is None:
74
+ timezone = 'UTC'
75
+
76
+ # Convert when to the specified timezone
77
+ if when.tzinfo is None:
78
+ when = pytz.UTC.localize(when)
79
+ local_tz = pytz.timezone(timezone)
80
+ when_local = when.astimezone(local_tz)
81
+
82
+ # Get today's date in the specified timezone
83
+ now_utc = utcnow()
84
+ now_local = now_utc.astimezone(local_tz)
85
+
86
+ # Compare dates
87
+ return when_local.date() == now_local.date()
mojo/helpers/response.py CHANGED
@@ -1,4 +1,4 @@
1
- import ujson
1
+ from objict import objict
2
2
  from django.http import HttpResponse
3
3
 
4
4
 
@@ -10,5 +10,9 @@ class JsonResponse(HttpResponse):
10
10
  'safe parameter to False.'
11
11
  )
12
12
  kwargs.setdefault('content_type', 'application/json')
13
- data = ujson.dumps(data)
13
+ if not isinstance(data, objict):
14
+ data = objict.from_dict(data)
15
+ if "code" not in data:
16
+ data.code = status
17
+ data = data.to_json(as_string=True)
14
18
  super().__init__(content=data, status=status, **kwargs)
@@ -0,0 +1,2 @@
1
+ from .helper import load_settings_profile, settings
2
+ from .parser import load_settings_config
@@ -1,5 +1,5 @@
1
1
  import importlib
2
- from typing import Any, Union
2
+ from typing import Any
3
3
 
4
4
  UNKNOWN = Ellipsis
5
5
 
@@ -16,42 +16,6 @@ def load_settings_profile(context):
16
16
  modules.load_module_to_globals("settings.defaults", context)
17
17
  modules.load_module_to_globals(f"settings.{profile}", context)
18
18
 
19
- def load_settings_config(context):
20
- # Load config from django.conf file
21
- from mojo.helpers import paths
22
- config_path = paths.VAR_ROOT / "django.conf"
23
- if not config_path.exists():
24
- raise Exception(f"Required configuration file not found: {config_path}")
25
-
26
- with open(config_path, 'r') as file:
27
- for line in file:
28
- if '=' in line:
29
- key, value = line.strip().split('=', 1)
30
- value = value.strip()
31
- if value.startswith('"') and value.endswith('"'):
32
- value = value[1:-1]
33
- elif value.startswith("'") and value.endswith("'"):
34
- value = value[1:-1]
35
- if value.startswith('f"') or value.startswith("f'"):
36
- value = eval(value)
37
- elif value.lower() == 'true':
38
- value = True
39
- elif value.lower() == 'false':
40
- value = False
41
- else:
42
- try:
43
- if '.' in value:
44
- value = float(value)
45
- else:
46
- value = int(value)
47
- except ValueError:
48
- pass
49
- context[key.strip()] = value
50
-
51
- if context.get("ALLOW_ADMIN_SITE", True):
52
- if "django.contrib.admin" not in context["INSTALLED_APPS"]:
53
- context["INSTALLED_APPS"].insert(0, "django.contrib.admin")
54
-
55
19
 
56
20
  class SettingsHelper:
57
21
  """
@@ -0,0 +1,132 @@
1
+ class DjangoConfigLoader:
2
+ """
3
+ A clean, expandable class for loading Django configuration from django.conf files.
4
+ """
5
+
6
+ def __init__(self, config_path=None):
7
+ """
8
+ Initialize the config loader.
9
+
10
+ :param config_path: Path to the django.conf file. If None, uses default VAR_ROOT path.
11
+ """
12
+ if config_path is None:
13
+ from mojo.helpers import paths
14
+ self.config_path = paths.VAR_ROOT / "django.conf"
15
+ else:
16
+ self.config_path = config_path
17
+
18
+ def load_config(self, context):
19
+ """
20
+ Load configuration from django.conf file into the provided context.
21
+
22
+ :param context: Dictionary to load configuration values into.
23
+ :raises Exception: If the required configuration file is not found.
24
+ """
25
+ self._validate_config_file()
26
+ self._parse_config_file(context)
27
+ self._apply_admin_site_config(context)
28
+
29
+ def _validate_config_file(self):
30
+ """Validate that the configuration file exists."""
31
+ if not self.config_path.exists():
32
+ raise Exception(f"Required configuration file not found: {self.config_path}")
33
+
34
+ def _parse_config_file(self, context):
35
+ """Parse the configuration file and populate the context."""
36
+ with open(self.config_path, 'r') as file:
37
+ for line in file:
38
+ if '=' in line:
39
+ key, value = line.strip().split('=', 1)
40
+ parsed_value = self._parse_value(value.strip())
41
+ context[key.strip()] = parsed_value
42
+
43
+ def _parse_value(self, value):
44
+ """
45
+ Parse a configuration value string into the appropriate Python type.
46
+
47
+ :param value: String value to parse.
48
+ :return: Parsed value with appropriate type.
49
+ """
50
+ if self._is_list_value(value):
51
+ return self._parse_list_value(value)
52
+ elif self._is_quoted_string(value):
53
+ return self._parse_quoted_string(value)
54
+ elif self._is_f_string(value):
55
+ return eval(value)
56
+ elif self._is_boolean(value):
57
+ return self._parse_boolean(value)
58
+ else:
59
+ return self._parse_numeric_or_string(value)
60
+
61
+ def _is_list_value(self, value):
62
+ """Check if value is a list format."""
63
+ return value.startswith('[') and value.endswith(']')
64
+
65
+ def _is_quoted_string(self, value):
66
+ """Check if value is a quoted string."""
67
+ return ((value.startswith('"') and value.endswith('"')) or
68
+ (value.startswith("'") and value.endswith("'")))
69
+
70
+ def _is_f_string(self, value):
71
+ """Check if value is an f-string."""
72
+ return value.startswith('f"') or value.startswith("f'")
73
+
74
+ def _is_boolean(self, value):
75
+ """Check if value is a boolean."""
76
+ return value.lower() in ('true', 'false')
77
+
78
+ def _parse_list_value(self, value):
79
+ """Parse a list value string into a Python list."""
80
+ list_content = value[1:-1].strip()
81
+ if not list_content:
82
+ return []
83
+
84
+ items = []
85
+ for item in list_content.split(','):
86
+ item = item.strip()
87
+ parsed_item = self._parse_list_item(item)
88
+ items.append(parsed_item)
89
+ return items
90
+
91
+ def _parse_list_item(self, item):
92
+ """Parse an individual list item."""
93
+ if self._is_quoted_string(item):
94
+ return item[1:-1] # Remove quotes
95
+ else:
96
+ return self._parse_numeric_or_string(item)
97
+
98
+ def _parse_quoted_string(self, value):
99
+ """Parse a quoted string by removing the quotes."""
100
+ return value[1:-1]
101
+
102
+ def _parse_boolean(self, value):
103
+ """Parse a boolean string."""
104
+ return value.lower() == 'true'
105
+
106
+ def _parse_numeric_or_string(self, value):
107
+ """Parse a value as numeric if possible, otherwise return as string."""
108
+ try:
109
+ if '.' in value:
110
+ return float(value)
111
+ else:
112
+ return int(value)
113
+ except ValueError:
114
+ return value
115
+
116
+ def _apply_admin_site_config(self, context):
117
+ """Apply Django admin site configuration if enabled."""
118
+ if context.get("ALLOW_ADMIN_SITE", True):
119
+ installed_apps = context.get("INSTALLED_APPS", [])
120
+ if "django.contrib.admin" not in installed_apps:
121
+ installed_apps.insert(0, "django.contrib.admin")
122
+ context["INSTALLED_APPS"] = installed_apps
123
+
124
+
125
+ def load_settings_config(context):
126
+ """
127
+ Load Django configuration from django.conf file.
128
+
129
+ :param context: Dictionary to load configuration values into.
130
+ """
131
+ loader = DjangoConfigLoader()
132
+ loader.load_config(context)