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,509 @@
1
+ from decimal import Decimal
2
+ from datetime import datetime, date, time
3
+ import locale
4
+ from mojo.helpers import logit
5
+
6
+ logger = logit.get_logger("localizers", "localizers.log")
7
+
8
+ # Registry of available localizers
9
+ LOCALIZER_REGISTRY = {}
10
+
11
+
12
+ def register_localizer(name, func):
13
+ """
14
+ Register a localizer function.
15
+
16
+ :param name: Name to register the localizer under
17
+ :param func: Localizer function that takes (value, extra=None)
18
+ """
19
+ LOCALIZER_REGISTRY[name] = func
20
+ logger.debug(f"Registered localizer: {name}")
21
+
22
+
23
+ def get_localizer(name):
24
+ """
25
+ Get a localizer function by name.
26
+
27
+ :param name: Localizer name
28
+ :return: Localizer function or None
29
+ """
30
+ return LOCALIZER_REGISTRY.get(name)
31
+
32
+
33
+ def list_localizers():
34
+ """
35
+ Get list of all registered localizer names.
36
+
37
+ :return: List of localizer names
38
+ """
39
+ return list(LOCALIZER_REGISTRY.keys())
40
+
41
+
42
+ # Currency localizers
43
+ def cents_to_currency(value, extra=None):
44
+ """
45
+ Convert cents to currency format.
46
+
47
+ :param value: Value in cents
48
+ :param extra: Currency symbol (default: no symbol)
49
+ :return: Formatted currency string
50
+ """
51
+ if value is None:
52
+ return "0.00"
53
+
54
+ try:
55
+ currency, cents = divmod(int(value), 100)
56
+ if extra:
57
+ return f"{extra}{currency}.{cents:02d}"
58
+ return f"{currency}.{cents:02d}"
59
+ except (ValueError, TypeError):
60
+ return str(value)
61
+
62
+
63
+ def cents_to_dollars(value, extra=None):
64
+ """
65
+ Convert cents to dollar format.
66
+
67
+ :param value: Value in cents
68
+ :param extra: Not used
69
+ :return: Dollar-formatted string
70
+ """
71
+ if value is None:
72
+ return "$0.00"
73
+
74
+ try:
75
+ dollars, cents = divmod(int(value), 100)
76
+ return f"${dollars}.{cents:02d}"
77
+ except (ValueError, TypeError):
78
+ return str(value)
79
+
80
+
81
+ def currency_format(value, extra="$"):
82
+ """
83
+ Format value as currency with specified symbol.
84
+
85
+ :param value: Numeric value
86
+ :param extra: Currency symbol
87
+ :return: Formatted currency string
88
+ """
89
+ if value is None:
90
+ return f"{extra}0.00"
91
+
92
+ try:
93
+ if isinstance(value, (int, float, Decimal)):
94
+ return f"{extra}{value:.2f}"
95
+ return f"{extra}{float(value):.2f}"
96
+ except (ValueError, TypeError):
97
+ return str(value)
98
+
99
+
100
+ # Date/time localizers
101
+ def date_format(value, extra="%Y-%m-%d"):
102
+ """
103
+ Format date with specified format.
104
+
105
+ :param value: Date value
106
+ :param extra: Date format string
107
+ :return: Formatted date string
108
+ """
109
+ if value is None:
110
+ return ""
111
+
112
+ try:
113
+ if isinstance(value, datetime):
114
+ return value.strftime(extra)
115
+ elif isinstance(value, date):
116
+ return value.strftime(extra)
117
+ elif isinstance(value, str):
118
+ # Try to parse string date
119
+ parsed_date = datetime.fromisoformat(value.replace('Z', '+00:00'))
120
+ return parsed_date.strftime(extra)
121
+ return str(value)
122
+ except (ValueError, AttributeError):
123
+ return str(value)
124
+
125
+
126
+ def datetime_format(value, extra="%Y-%m-%d %H:%M:%S"):
127
+ """
128
+ Format datetime with specified format.
129
+
130
+ :param value: Datetime value
131
+ :param extra: Datetime format string
132
+ :return: Formatted datetime string
133
+ """
134
+ if value is None:
135
+ return ""
136
+
137
+ try:
138
+ if isinstance(value, datetime):
139
+ return value.strftime(extra)
140
+ elif isinstance(value, str):
141
+ # Try to parse string datetime
142
+ parsed_datetime = datetime.fromisoformat(value.replace('Z', '+00:00'))
143
+ return parsed_datetime.strftime(extra)
144
+ return str(value)
145
+ except (ValueError, AttributeError):
146
+ return str(value)
147
+
148
+
149
+ def time_format(value, extra="%H:%M:%S"):
150
+ """
151
+ Format time with specified format.
152
+
153
+ :param value: Time value
154
+ :param extra: Time format string
155
+ :return: Formatted time string
156
+ """
157
+ if value is None:
158
+ return ""
159
+
160
+ try:
161
+ if isinstance(value, time):
162
+ return value.strftime(extra)
163
+ elif isinstance(value, datetime):
164
+ return value.time().strftime(extra)
165
+ return str(value)
166
+ except (ValueError, AttributeError):
167
+ return str(value)
168
+
169
+
170
+ def timezone_format(value, extra=None):
171
+ """
172
+ Format datetime with timezone information.
173
+
174
+ :param value: Datetime value
175
+ :param extra: Timezone name (optional)
176
+ :return: Formatted datetime with timezone
177
+ """
178
+ if value is None:
179
+ return ""
180
+
181
+ try:
182
+ # Try to import timezone helper
183
+ try:
184
+ from mojo.helpers import dates
185
+ if extra:
186
+ localized_value = dates.get_local_time(extra, value)
187
+ return localized_value.strftime("%Y-%m-%d %H:%M:%S %Z")
188
+ elif hasattr(value, 'strftime'):
189
+ return value.strftime("%Y-%m-%d %H:%M:%S %Z")
190
+ except ImportError:
191
+ logger.warning("mojo.helpers.dates not available for timezone formatting")
192
+
193
+ if hasattr(value, 'strftime'):
194
+ return value.strftime("%Y-%m-%d %H:%M:%S")
195
+ return str(value)
196
+ except Exception as e:
197
+ logger.warning(f"Timezone formatting failed: {e}")
198
+ return str(value)
199
+
200
+
201
+ # Number formatters
202
+ def number_format(value, extra="2"):
203
+ """
204
+ Format number with specified decimal places.
205
+
206
+ :param value: Numeric value
207
+ :param extra: Number of decimal places (default: 2)
208
+ :return: Formatted number string
209
+ """
210
+ if value is None:
211
+ return "0"
212
+
213
+ try:
214
+ decimal_places = int(extra) if extra else 2
215
+ if isinstance(value, (int, float, Decimal)):
216
+ return f"{float(value):.{decimal_places}f}"
217
+ return f"{float(value):.{decimal_places}f}"
218
+ except (ValueError, TypeError):
219
+ return str(value)
220
+
221
+
222
+ def percentage_format(value, extra="2"):
223
+ """
224
+ Format value as percentage.
225
+
226
+ :param value: Numeric value (0.1 = 10%)
227
+ :param extra: Number of decimal places
228
+ :return: Formatted percentage string
229
+ """
230
+ if value is None:
231
+ return "0%"
232
+
233
+ try:
234
+ decimal_places = int(extra) if extra else 2
235
+ percentage = float(value) * 100
236
+ return f"{percentage:.{decimal_places}f}%"
237
+ except (ValueError, TypeError):
238
+ return str(value)
239
+
240
+
241
+ def thousands_separator(value, extra=","):
242
+ """
243
+ Add thousands separator to number.
244
+
245
+ :param value: Numeric value
246
+ :param extra: Separator character (default: comma)
247
+ :return: Formatted number with separators
248
+ """
249
+ if value is None:
250
+ return "0"
251
+
252
+ try:
253
+ separator = extra if extra else ","
254
+ return f"{int(value):,}".replace(",", separator)
255
+ except (ValueError, TypeError):
256
+ return str(value)
257
+
258
+
259
+ # Text formatters
260
+ def title_case(value, extra=None):
261
+ """
262
+ Convert text to title case.
263
+
264
+ :param value: Text value
265
+ :param extra: Not used
266
+ :return: Title case text
267
+ """
268
+ if value is None:
269
+ return ""
270
+
271
+ return str(value).title()
272
+
273
+
274
+ def upper_case(value, extra=None):
275
+ """
276
+ Convert text to upper case.
277
+
278
+ :param value: Text value
279
+ :param extra: Not used
280
+ :return: Upper case text
281
+ """
282
+ if value is None:
283
+ return ""
284
+
285
+ return str(value).upper()
286
+
287
+
288
+ def lower_case(value, extra=None):
289
+ """
290
+ Convert text to lower case.
291
+
292
+ :param value: Text value
293
+ :param extra: Not used
294
+ :return: Lower case text
295
+ """
296
+ if value is None:
297
+ return ""
298
+
299
+ return str(value).lower()
300
+
301
+
302
+ def truncate_text(value, extra="50"):
303
+ """
304
+ Truncate text to specified length.
305
+
306
+ :param value: Text value
307
+ :param extra: Maximum length (default: 50)
308
+ :return: Truncated text
309
+ """
310
+ if value is None:
311
+ return ""
312
+
313
+ try:
314
+ max_length = int(extra) if extra else 50
315
+ text = str(value)
316
+ if len(text) <= max_length:
317
+ return text
318
+ return text[:max_length-3] + "..."
319
+ except (ValueError, TypeError):
320
+ return str(value)
321
+
322
+
323
+ # Boolean formatters
324
+ def yes_no(value, extra=None):
325
+ """
326
+ Convert boolean to Yes/No.
327
+
328
+ :param value: Boolean value
329
+ :param extra: Not used
330
+ :return: "Yes" or "No"
331
+ """
332
+ if value is None:
333
+ return "No"
334
+
335
+ return "Yes" if bool(value) else "No"
336
+
337
+
338
+ def true_false(value, extra=None):
339
+ """
340
+ Convert boolean to True/False.
341
+
342
+ :param value: Boolean value
343
+ :param extra: Not used
344
+ :return: "True" or "False"
345
+ """
346
+ if value is None:
347
+ return "False"
348
+
349
+ return "True" if bool(value) else "False"
350
+
351
+
352
+ def on_off(value, extra=None):
353
+ """
354
+ Convert boolean to On/Off.
355
+
356
+ :param value: Boolean value
357
+ :param extra: Not used
358
+ :return: "On" or "Off"
359
+ """
360
+ if value is None:
361
+ return "Off"
362
+
363
+ return "On" if bool(value) else "Off"
364
+
365
+
366
+ # List/collection formatters
367
+ def join_list(value, extra=", "):
368
+ """
369
+ Join list items with separator.
370
+
371
+ :param value: List or iterable
372
+ :param extra: Separator string (default: ", ")
373
+ :return: Joined string
374
+ """
375
+ if value is None:
376
+ return ""
377
+
378
+ try:
379
+ separator = extra if extra else ", "
380
+ if isinstance(value, (list, tuple)):
381
+ return separator.join(str(item) for item in value)
382
+ return str(value)
383
+ except Exception:
384
+ return str(value)
385
+
386
+
387
+ def list_count(value, extra=None):
388
+ """
389
+ Return count of items in list.
390
+
391
+ :param value: List or iterable
392
+ :param extra: Not used
393
+ :return: Count as string
394
+ """
395
+ if value is None:
396
+ return "0"
397
+
398
+ try:
399
+ if hasattr(value, '__len__'):
400
+ return str(len(value))
401
+ elif hasattr(value, 'count'):
402
+ return str(value.count())
403
+ return "1"
404
+ except Exception:
405
+ return "0"
406
+
407
+
408
+ # File size formatter
409
+ def file_size(value, extra="auto"):
410
+ """
411
+ Format file size in human readable format.
412
+
413
+ :param value: File size in bytes
414
+ :param extra: Unit preference ("auto", "KB", "MB", "GB")
415
+ :return: Formatted file size
416
+ """
417
+ if value is None:
418
+ return "0 B"
419
+
420
+ try:
421
+ size = float(value)
422
+ if extra != "auto":
423
+ unit = extra.upper()
424
+ if unit == "KB":
425
+ return f"{size/1024:.2f} KB"
426
+ elif unit == "MB":
427
+ return f"{size/(1024**2):.2f} MB"
428
+ elif unit == "GB":
429
+ return f"{size/(1024**3):.2f} GB"
430
+
431
+ # Auto-detect best unit
432
+ if size < 1024:
433
+ return f"{size:.0f} B"
434
+ elif size < 1024**2:
435
+ return f"{size/1024:.2f} KB"
436
+ elif size < 1024**3:
437
+ return f"{size/(1024**2):.2f} MB"
438
+ else:
439
+ return f"{size/(1024**3):.2f} GB"
440
+ except (ValueError, TypeError):
441
+ return str(value)
442
+
443
+
444
+ # Register all localizers
445
+ register_localizer('cents_to_currency', cents_to_currency)
446
+ register_localizer('cents_to_dollars', cents_to_dollars)
447
+ register_localizer('currency', currency_format)
448
+ register_localizer('date', date_format)
449
+ register_localizer('datetime', datetime_format)
450
+ register_localizer('time', time_format)
451
+ register_localizer('timezone', timezone_format)
452
+ register_localizer('number', number_format)
453
+ register_localizer('percentage', percentage_format)
454
+ register_localizer('thousands', thousands_separator)
455
+ register_localizer('title', title_case)
456
+ register_localizer('upper', upper_case)
457
+ register_localizer('lower', lower_case)
458
+ register_localizer('truncate', truncate_text)
459
+ register_localizer('yes_no', yes_no)
460
+ register_localizer('true_false', true_false)
461
+ register_localizer('on_off', on_off)
462
+ register_localizer('join', join_list)
463
+ register_localizer('count', list_count)
464
+ register_localizer('filesize', file_size)
465
+
466
+
467
+ # Custom localizer decorator
468
+ def localizer(name):
469
+ """
470
+ Decorator to register a custom localizer.
471
+
472
+ Usage:
473
+ @localizer('my_formatter')
474
+ def my_custom_formatter(value, extra=None):
475
+ return f"Custom: {value}"
476
+ """
477
+ def decorator(func):
478
+ register_localizer(name, func)
479
+ return func
480
+ return decorator
481
+
482
+
483
+ # Legacy support
484
+ def apply_localizer(value, localizer_config):
485
+ """
486
+ Apply localizer based on configuration string.
487
+
488
+ :param value: Value to localize
489
+ :param localizer_config: Configuration string like "currency|$" or "date|%Y-%m-%d"
490
+ :return: Localized value
491
+ """
492
+ if not localizer_config:
493
+ return value
494
+
495
+ try:
496
+ if '|' in localizer_config:
497
+ localizer_name, extra = localizer_config.split('|', 1)
498
+ else:
499
+ localizer_name, extra = localizer_config, None
500
+
501
+ localizer_func = get_localizer(localizer_name)
502
+ if localizer_func:
503
+ return localizer_func(value, extra)
504
+ else:
505
+ logger.warning(f"Unknown localizer: {localizer_name}")
506
+ return value
507
+ except Exception as e:
508
+ logger.error(f"Localizer error for '{localizer_config}': {e}")
509
+ return value