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,651 @@
1
+ """
2
+ Django management command for serializer administration and benchmarking.
3
+
4
+ Provides comprehensive serializer management capabilities including:
5
+ - Performance benchmarking and comparison
6
+ - Serializer registration and configuration
7
+ - Cache management and statistics
8
+ - Health checks and diagnostics
9
+
10
+ Usage:
11
+ # List all registered serializers
12
+ python manage.py serializer_admin list
13
+
14
+ # Benchmark serializers
15
+ python manage.py serializer_admin benchmark --model MyModel --count 1000
16
+
17
+ # Set default serializer
18
+ python manage.py serializer_admin set-default optimized
19
+
20
+ # Get performance statistics
21
+ python manage.py serializer_admin stats
22
+
23
+ # Clear caches
24
+ python manage.py serializer_admin clear-cache
25
+ """
26
+
27
+ import time
28
+ import json
29
+ from django.core.management.base import BaseCommand, CommandError
30
+ from django.apps import apps
31
+ from django.db import models
32
+ from django.core.management import color
33
+
34
+ from mojo.serializers import (
35
+ get_serializer_manager,
36
+ get_performance_stats,
37
+ clear_serializer_caches,
38
+ benchmark_serializers,
39
+ list_serializers,
40
+ set_default_serializer,
41
+ HAS_UJSON,
42
+ UJSON_VERSION
43
+ )
44
+
45
+ # Import cache system for enhanced functionality
46
+ try:
47
+ from mojo.serializers.core.cache import (
48
+ get_cache_backend,
49
+ get_cache_stats,
50
+ get_available_backends,
51
+ test_backend_connectivity,
52
+ get_cache_health
53
+ )
54
+ HAS_CACHE_SYSTEM = True
55
+ except ImportError:
56
+ HAS_CACHE_SYSTEM = False
57
+
58
+
59
+ class Command(BaseCommand):
60
+ help = 'Manage MOJO serializers: benchmark, configure, and monitor performance'
61
+
62
+ def add_arguments(self, parser):
63
+ """Add command line arguments."""
64
+
65
+ subparsers = parser.add_subparsers(dest='action', help='Available actions')
66
+
67
+ # List serializers
68
+ list_parser = subparsers.add_parser('list', help='List all registered serializers')
69
+ list_parser.add_argument(
70
+ '--detail',
71
+ action='store_true',
72
+ help='Show detailed information about each serializer'
73
+ )
74
+
75
+ # Benchmark serializers
76
+ bench_parser = subparsers.add_parser('benchmark', help='Benchmark serializer performance')
77
+ bench_parser.add_argument(
78
+ '--model',
79
+ type=str,
80
+ required=True,
81
+ help='Model name to benchmark (format: app.ModelName or ModelName)'
82
+ )
83
+ bench_parser.add_argument(
84
+ '--count',
85
+ type=int,
86
+ default=100,
87
+ help='Number of objects to serialize (default: 100)'
88
+ )
89
+ bench_parser.add_argument(
90
+ '--graph',
91
+ type=str,
92
+ default='default',
93
+ help='Graph configuration to use (default: "default")'
94
+ )
95
+ bench_parser.add_argument(
96
+ '--iterations',
97
+ type=int,
98
+ default=5,
99
+ help='Number of benchmark iterations (default: 5)'
100
+ )
101
+ bench_parser.add_argument(
102
+ '--serializers',
103
+ nargs='+',
104
+ help='Specific serializers to benchmark (default: all)'
105
+ )
106
+ bench_parser.add_argument(
107
+ '--output-json',
108
+ type=str,
109
+ help='Save results to JSON file'
110
+ )
111
+
112
+ # Set default serializer
113
+ default_parser = subparsers.add_parser('set-default', help='Set default serializer')
114
+ default_parser.add_argument(
115
+ 'serializer_name',
116
+ type=str,
117
+ help='Name of serializer to set as default'
118
+ )
119
+
120
+ # Performance statistics
121
+ stats_parser = subparsers.add_parser('stats', help='Show performance statistics')
122
+ stats_parser.add_argument(
123
+ '--clear',
124
+ action='store_true',
125
+ help='Clear statistics after showing them'
126
+ )
127
+ stats_parser.add_argument(
128
+ '--json',
129
+ action='store_true',
130
+ help='Output statistics as JSON'
131
+ )
132
+
133
+ # Cache management
134
+ cache_parser = subparsers.add_parser('clear-cache', help='Clear serializer caches')
135
+ cache_parser.add_argument(
136
+ '--serializer',
137
+ type=str,
138
+ help='Clear cache for specific serializer (default: all)'
139
+ )
140
+
141
+ # Health check
142
+ health_parser = subparsers.add_parser('health', help='Run serializer health checks')
143
+ health_parser.add_argument(
144
+ '--model',
145
+ type=str,
146
+ help='Test specific model (format: app.ModelName or ModelName)'
147
+ )
148
+
149
+ # Test serializers
150
+ test_parser = subparsers.add_parser('test', help='Test serializer functionality')
151
+ test_parser.add_argument(
152
+ '--model',
153
+ type=str,
154
+ required=True,
155
+ help='Model to test (format: app.ModelName or ModelName)'
156
+ )
157
+ test_parser.add_argument(
158
+ '--graph',
159
+ type=str,
160
+ default='default',
161
+ help='Graph configuration to test'
162
+ )
163
+
164
+ # Cache information
165
+ cache_parser = subparsers.add_parser('cache-info', help='Show detailed cache information')
166
+ cache_parser.add_argument(
167
+ '--test-connectivity',
168
+ action='store_true',
169
+ help='Test cache backend connectivity'
170
+ )
171
+
172
+ def handle(self, *args, **options):
173
+ """Handle command execution."""
174
+ action = options.get('action')
175
+
176
+ if not action:
177
+ self.print_help()
178
+ return
179
+
180
+ # Route to appropriate handler
181
+ handler_map = {
182
+ 'list': self.handle_list,
183
+ 'benchmark': self.handle_benchmark,
184
+ 'set-default': self.handle_set_default,
185
+ 'stats': self.handle_stats,
186
+ 'clear-cache': self.handle_clear_cache,
187
+ 'health': self.handle_health,
188
+ 'test': self.handle_test,
189
+ 'cache-info': self.handle_cache_info,
190
+ }
191
+
192
+ handler = handler_map.get(action)
193
+ if handler:
194
+ try:
195
+ handler(options)
196
+ except Exception as e:
197
+ raise CommandError(f"Error executing {action}: {str(e)}")
198
+ else:
199
+ raise CommandError(f"Unknown action: {action}")
200
+
201
+ def handle_list(self, options):
202
+ """List all registered serializers."""
203
+ serializers = list_serializers()
204
+
205
+ if not serializers:
206
+ self.stdout.write(
207
+ self.style.WARNING('No serializers registered')
208
+ )
209
+ return
210
+
211
+ self.stdout.write(
212
+ self.style.SUCCESS('Registered Serializers:')
213
+ )
214
+
215
+ for name, info in serializers.items():
216
+ status = " (DEFAULT)" if info['is_default'] else ""
217
+ self.stdout.write(f" • {name}{status}")
218
+
219
+ if options.get('detail'):
220
+ self.stdout.write(f" Class: {info['class_name']}")
221
+ self.stdout.write(f" Description: {info['description']}")
222
+
223
+ # Show ujson information
224
+ self.stdout.write(f"\nPerformance Information:")
225
+ if HAS_UJSON:
226
+ self.stdout.write(f" ✓ ujson {UJSON_VERSION} available - optimal JSON performance")
227
+ else:
228
+ self.stdout.write(f" ⚠ ujson not available - using standard json (slower)")
229
+ self.stdout.write(f" Install with: pip install ujson")
230
+
231
+ # Show cache backend information
232
+ if HAS_CACHE_SYSTEM:
233
+ try:
234
+ cache_backend = get_cache_backend()
235
+ cache_stats = cache_backend.stats()
236
+ backend_type = cache_stats.get('backend', 'unknown')
237
+ self.stdout.write(f" ✓ Cache backend: {backend_type}")
238
+ except Exception as e:
239
+ self.stdout.write(f" ⚠ Cache backend error: {e}")
240
+
241
+ def handle_benchmark(self, options):
242
+ """Benchmark serializer performance."""
243
+ model_class = self.get_model_class(options['model'])
244
+ count = options['count']
245
+ graph = options['graph']
246
+ iterations = options['iterations']
247
+ serializer_types = options.get('serializers')
248
+
249
+ # Check if model has enough instances
250
+ total_instances = model_class.objects.count()
251
+ if total_instances < count:
252
+ self.stdout.write(
253
+ self.style.WARNING(
254
+ f"Model {model_class.__name__} only has {total_instances} instances, "
255
+ f"but {count} requested. Using available instances."
256
+ )
257
+ )
258
+ count = min(count, total_instances)
259
+
260
+ if count == 0:
261
+ raise CommandError(f"No instances found for model {model_class.__name__}")
262
+
263
+ # Get test queryset
264
+ test_queryset = model_class.objects.all()[:count]
265
+
266
+ self.stdout.write(
267
+ self.style.SUCCESS(
268
+ f"Benchmarking serializers with {count} {model_class.__name__} instances"
269
+ )
270
+ )
271
+ self.stdout.write(f"Graph: {graph}")
272
+ self.stdout.write(f"Iterations: {iterations}")
273
+
274
+ # Run benchmark
275
+ try:
276
+ results = benchmark_serializers(
277
+ instance=test_queryset,
278
+ graph=graph,
279
+ serializer_types=serializer_types,
280
+ iterations=iterations
281
+ )
282
+
283
+ # Display results
284
+ self.display_benchmark_results(results)
285
+
286
+ # Save to JSON if requested
287
+ if options.get('output_json'):
288
+ self.save_benchmark_results(results, options['output_json'])
289
+
290
+ except Exception as e:
291
+ raise CommandError(f"Benchmark failed: {str(e)}")
292
+
293
+ def handle_set_default(self, options):
294
+ """Set default serializer."""
295
+ serializer_name = options['serializer_name']
296
+
297
+ if set_default_serializer(serializer_name):
298
+ self.stdout.write(
299
+ self.style.SUCCESS(
300
+ f"Default serializer set to: {serializer_name}"
301
+ )
302
+ )
303
+ else:
304
+ raise CommandError(f"Failed to set default serializer to {serializer_name}")
305
+
306
+ def handle_stats(self, options):
307
+ """Show performance statistics."""
308
+ stats = get_performance_stats()
309
+
310
+ if options.get('json'):
311
+ self.stdout.write(json.dumps(stats, indent=2))
312
+ else:
313
+ self.display_stats(stats)
314
+
315
+ if options.get('clear'):
316
+ # Clear statistics if requested
317
+ manager = get_serializer_manager()
318
+ if hasattr(manager, 'reset_performance_stats'):
319
+ manager.reset_performance_stats()
320
+ self.stdout.write(
321
+ self.style.SUCCESS("Performance statistics cleared")
322
+ )
323
+
324
+ def handle_cache_info(self, options):
325
+ """Show detailed cache information."""
326
+ if not HAS_CACHE_SYSTEM:
327
+ self.stdout.write(
328
+ self.style.ERROR("Cache system not available")
329
+ )
330
+ return
331
+
332
+ self.stdout.write(self.style.SUCCESS("Cache System Information:"))
333
+
334
+ try:
335
+ # Show available backends
336
+ backends = get_available_backends()
337
+ self.stdout.write(f"\nAvailable Backends:")
338
+ for name, info in backends.items():
339
+ status = "✓" if info['available'] else "✗"
340
+ self.stdout.write(f" {status} {name}: {info['description']}")
341
+ if info.get('ujson_available'):
342
+ self.stdout.write(f" ujson: {info.get('ujson_version', 'available')}")
343
+ if info.get('error'):
344
+ self.stdout.write(f" Error: {info['error']}")
345
+
346
+ # Show current backend health
347
+ health = get_cache_health()
348
+ self.stdout.write(f"\nCache Health: {health['status'].upper()}")
349
+ if health.get('issues'):
350
+ for issue in health['issues']:
351
+ self.stdout.write(f" ⚠ {issue}")
352
+
353
+ # Show recommendations
354
+ if health.get('recommendations'):
355
+ self.stdout.write(f"\nRecommendations:")
356
+ for rec in health['recommendations']:
357
+ self.stdout.write(f" • {rec}")
358
+
359
+ # Test connectivity if requested
360
+ if options.get('test_connectivity'):
361
+ self.stdout.write(f"\nTesting Cache Connectivity:")
362
+ backend_type = health.get('backend_type', 'unknown')
363
+ test_result = test_backend_connectivity(backend_type)
364
+
365
+ if test_result['connectivity']:
366
+ self.stdout.write(f" ✓ {backend_type} backend connectivity OK")
367
+ if test_result['functionality']:
368
+ perf = test_result.get('performance', {})
369
+ self.stdout.write(f" ✓ Functionality test passed")
370
+ if perf:
371
+ self.stdout.write(f" Set time: {perf.get('set_time_ms', 0)}ms")
372
+ self.stdout.write(f" Get time: {perf.get('get_time_ms', 0)}ms")
373
+ else:
374
+ self.stdout.write(f" ✗ Functionality test failed")
375
+ else:
376
+ self.stdout.write(f" ✗ {backend_type} backend connectivity failed")
377
+
378
+ for error in test_result.get('errors', []):
379
+ self.stdout.write(f" Error: {error}")
380
+
381
+ except Exception as e:
382
+ self.stdout.write(
383
+ self.style.ERROR(f"Error getting cache information: {e}")
384
+ )
385
+
386
+ def handle_clear_cache(self, options):
387
+ """Clear serializer caches."""
388
+ serializer_name = options.get('serializer')
389
+
390
+ if serializer_name:
391
+ clear_serializer_caches(serializer_name)
392
+ self.stdout.write(
393
+ self.style.SUCCESS(f"Cache cleared for {serializer_name}")
394
+ )
395
+ else:
396
+ clear_serializer_caches()
397
+ self.stdout.write(
398
+ self.style.SUCCESS("All serializer caches cleared")
399
+ )
400
+
401
+ def handle_health(self, options):
402
+ """Run serializer health checks."""
403
+ model_name = options.get('model')
404
+
405
+ if model_name:
406
+ # Test specific model
407
+ model_class = self.get_model_class(model_name)
408
+ self.run_model_health_check(model_class)
409
+ else:
410
+ # Test all MojoModel subclasses
411
+ self.run_full_health_check()
412
+
413
+ def handle_test(self, options):
414
+ """Test serializer functionality."""
415
+ model_class = self.get_model_class(options['model'])
416
+ graph = options['graph']
417
+
418
+ # Get a test instance
419
+ instance = model_class.objects.first()
420
+ if not instance:
421
+ raise CommandError(f"No instances found for model {model_class.__name__}")
422
+
423
+ self.stdout.write(
424
+ self.style.SUCCESS(f"Testing {model_class.__name__} serialization")
425
+ )
426
+
427
+ # Test each registered serializer
428
+ serializers = list_serializers()
429
+ manager = get_serializer_manager()
430
+
431
+ for serializer_name in serializers.keys():
432
+ self.stdout.write(f"\nTesting {serializer_name}:")
433
+
434
+ try:
435
+ # Test single instance
436
+ serializer = manager.get_serializer(instance, graph=graph, serializer_type=serializer_name)
437
+ data = serializer.serialize()
438
+ json_output = serializer.to_json()
439
+
440
+ self.stdout.write(
441
+ self.style.SUCCESS(f" ✓ Single instance: {len(str(data))} chars")
442
+ )
443
+
444
+ # Test queryset
445
+ queryset = model_class.objects.all()[:5] # Test with 5 instances
446
+ serializer = manager.get_serializer(queryset, graph=graph, serializer_type=serializer_name, many=True)
447
+ list_data = serializer.serialize()
448
+ list_json = serializer.to_json()
449
+
450
+ self.stdout.write(
451
+ self.style.SUCCESS(f" ✓ QuerySet ({len(list_data)} items): {len(str(list_json))} chars")
452
+ )
453
+
454
+ except Exception as e:
455
+ self.stdout.write(
456
+ self.style.ERROR(f" ✗ Failed: {str(e)}")
457
+ )
458
+
459
+ def get_model_class(self, model_string):
460
+ """Get model class from string."""
461
+ try:
462
+ if '.' in model_string:
463
+ app_label, model_name = model_string.split('.', 1)
464
+ return apps.get_model(app_label, model_name)
465
+ else:
466
+ # Try to find model in any app
467
+ for app_config in apps.get_app_configs():
468
+ try:
469
+ return app_config.get_model(model_string)
470
+ except LookupError:
471
+ continue
472
+ raise CommandError(f"Model '{model_string}' not found")
473
+ except Exception as e:
474
+ raise CommandError(f"Invalid model specification '{model_string}': {str(e)}")
475
+
476
+ def display_benchmark_results(self, results):
477
+ """Display benchmark results in a formatted table."""
478
+ if not results:
479
+ self.stdout.write(self.style.WARNING("No benchmark results"))
480
+ return
481
+
482
+ self.stdout.write("\nBenchmark Results:")
483
+ self.stdout.write("=" * 80)
484
+
485
+ # Table header
486
+ header = f"{'Serializer':<15} {'Avg Time':<12} {'Min Time':<12} {'Max Time':<12} {'Obj/Sec':<10}"
487
+ self.stdout.write(header)
488
+ self.stdout.write("-" * 80)
489
+
490
+ # Sort by average time
491
+ sorted_results = sorted(
492
+ results.items(),
493
+ key=lambda x: x[1].get('avg_time', float('inf'))
494
+ )
495
+
496
+ for name, stats in sorted_results:
497
+ if 'error' in stats:
498
+ row = f"{name:<15} {stats['error']:<50}"
499
+ self.stdout.write(self.style.ERROR(row))
500
+ else:
501
+ avg_time = f"{stats['avg_time']:.4f}s"
502
+ min_time = f"{stats['min_time']:.4f}s"
503
+ max_time = f"{stats['max_time']:.4f}s"
504
+ obj_per_sec = f"{stats.get('objects_per_second', 0):.1f}"
505
+
506
+ row = f"{name:<15} {avg_time:<12} {min_time:<12} {max_time:<12} {obj_per_sec:<10}"
507
+ self.stdout.write(row)
508
+
509
+ if stats.get('errors', 0) > 0:
510
+ self.stdout.write(
511
+ self.style.WARNING(f" └─ {stats['errors']} errors occurred")
512
+ )
513
+
514
+ def display_stats(self, stats):
515
+ """Display performance statistics."""
516
+ self.stdout.write(self.style.SUCCESS("Serializer Performance Statistics:"))
517
+
518
+ # Default serializer
519
+ default_serializer = stats.get('default_serializer')
520
+ if default_serializer:
521
+ self.stdout.write(f"Default Serializer: {default_serializer}")
522
+
523
+ # Registered serializers
524
+ registered = stats.get('registered_serializers', {})
525
+ if registered:
526
+ self.stdout.write(f"Registered Serializers: {len(registered)}")
527
+ for name, info in registered.items():
528
+ status = " (default)" if info['is_default'] else ""
529
+ self.stdout.write(f" • {name}{status}")
530
+
531
+ # Usage statistics
532
+ usage_stats = stats.get('usage_stats', {})
533
+ if usage_stats:
534
+ self.stdout.write("\nUsage Statistics:")
535
+ for serializer, data in usage_stats.items():
536
+ self.stdout.write(
537
+ f" {serializer}: {data['count']} uses, "
538
+ f"{data['total_objects']} objects serialized"
539
+ )
540
+
541
+ # Cache statistics
542
+ if HAS_CACHE_SYSTEM:
543
+ try:
544
+ cache_stats = get_cache_stats()
545
+ self.stdout.write(f"\nCache Statistics:")
546
+ self.stdout.write(f" Backend: {cache_stats.get('backend', 'unknown')}")
547
+ self.stdout.write(f" Hit Rate: {cache_stats.get('hit_rate', 0):.1%}")
548
+ self.stdout.write(f" Total Requests: {cache_stats.get('total_requests', 0)}")
549
+ self.stdout.write(f" Cache Size: {cache_stats.get('current_size', 0)}")
550
+ if cache_stats.get('max_size'):
551
+ utilization = cache_stats.get('current_size', 0) / cache_stats.get('max_size', 1)
552
+ self.stdout.write(f" Utilization: {utilization:.1%}")
553
+ except Exception as e:
554
+ self.stdout.write(f"\nCache Stats Error: {e}")
555
+
556
+ # Individual serializer stats
557
+ for key, value in stats.items():
558
+ if key.endswith('_stats') and isinstance(value, dict):
559
+ serializer_name = key.replace('_stats', '')
560
+ self.stdout.write(f"\n{serializer_name.title()} Stats:")
561
+ for stat_key, stat_value in value.items():
562
+ self.stdout.write(f" {stat_key}: {stat_value}")
563
+
564
+ def save_benchmark_results(self, results, filename):
565
+ """Save benchmark results to JSON file."""
566
+ try:
567
+ with open(filename, 'w') as f:
568
+ json.dump({
569
+ 'timestamp': time.time(),
570
+ 'results': results
571
+ }, f, indent=2)
572
+ self.stdout.write(
573
+ self.style.SUCCESS(f"Results saved to {filename}")
574
+ )
575
+ except Exception as e:
576
+ self.stdout.write(
577
+ self.style.ERROR(f"Failed to save results: {str(e)}")
578
+ )
579
+
580
+ def run_model_health_check(self, model_class):
581
+ """Run health check for specific model."""
582
+ self.stdout.write(f"Health check for {model_class.__name__}:")
583
+
584
+ # Check if model has RestMeta
585
+ if hasattr(model_class, 'RestMeta'):
586
+ self.stdout.write(self.style.SUCCESS(" ✓ RestMeta found"))
587
+
588
+ # Check graphs
589
+ if hasattr(model_class.RestMeta, 'GRAPHS'):
590
+ graphs = model_class.RestMeta.GRAPHS
591
+ self.stdout.write(f" ✓ {len(graphs)} graphs configured: {list(graphs.keys())}")
592
+ else:
593
+ self.stdout.write(self.style.WARNING(" ⚠ No GRAPHS configured"))
594
+ else:
595
+ self.stdout.write(self.style.WARNING(" ⚠ No RestMeta found"))
596
+
597
+ # Test serialization if instances exist
598
+ if model_class.objects.exists():
599
+ instance = model_class.objects.first()
600
+ manager = get_serializer_manager()
601
+
602
+ try:
603
+ serializer = manager.get_serializer(instance)
604
+ data = serializer.serialize()
605
+ self.stdout.write(self.style.SUCCESS(" ✓ Serialization successful"))
606
+ except Exception as e:
607
+ self.stdout.write(self.style.ERROR(f" ✗ Serialization failed: {str(e)}"))
608
+ else:
609
+ self.stdout.write(self.style.WARNING(" ⚠ No instances available for testing"))
610
+
611
+ def run_full_health_check(self):
612
+ """Run health check for all models."""
613
+ self.stdout.write("Running full serializer health check...")
614
+
615
+ # Import MojoModel to check for subclasses
616
+ try:
617
+ from mojo.models.rest import MojoModel
618
+
619
+ # Find all MojoModel subclasses
620
+ mojo_models = []
621
+ for app_config in apps.get_app_configs():
622
+ for model in app_config.get_models():
623
+ if hasattr(model, 'RestMeta') or 'MojoModel' in [c.__name__ for c in model.__mro__]:
624
+ mojo_models.append(model)
625
+
626
+ if not mojo_models:
627
+ self.stdout.write(self.style.WARNING("No MojoModel subclasses found"))
628
+ return
629
+
630
+ self.stdout.write(f"Found {len(mojo_models)} models to check\n")
631
+
632
+ for model in mojo_models:
633
+ self.run_model_health_check(model)
634
+ self.stdout.write("") # Empty line
635
+
636
+ except ImportError:
637
+ self.stdout.write(self.style.ERROR("Could not import MojoModel"))
638
+
639
+ def print_help(self):
640
+ """Print command help."""
641
+ self.stdout.write("MOJO Serializer Administration Command")
642
+ self.stdout.write("\nAvailable actions:")
643
+ self.stdout.write(" list - List registered serializers")
644
+ self.stdout.write(" benchmark - Benchmark serializer performance")
645
+ self.stdout.write(" set-default - Set default serializer")
646
+ self.stdout.write(" stats - Show performance statistics")
647
+ self.stdout.write(" clear-cache - Clear serializer caches")
648
+ self.stdout.write(" health - Run health checks")
649
+ self.stdout.write(" test - Test serializer functionality")
650
+ self.stdout.write(" cache-info - Show detailed cache information")
651
+ self.stdout.write("\nUse 'python manage.py serializer_admin <action> --help' for more details")
@@ -0,0 +1,20 @@
1
+ # Generated by Django 4.2.21 on 2025-06-09 05:37
2
+
3
+ from django.db import migrations, models
4
+ import django.db.models.deletion
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ dependencies = [
10
+ ('fileman', '0011_alter_filerendition_original_file'),
11
+ ('account', '0003_group_mojo_secrets_user_mojo_secrets'),
12
+ ]
13
+
14
+ operations = [
15
+ migrations.AddField(
16
+ model_name='user',
17
+ name='avatar',
18
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='fileman.file'),
19
+ ),
20
+ ]
@@ -0,0 +1,18 @@
1
+ # Generated by Django 4.2.21 on 2025-08-26 16:05
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('account', '0004_user_avatar'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AddField(
14
+ model_name='group',
15
+ name='last_activity',
16
+ field=models.DateTimeField(db_index=True, default=None, null=True),
17
+ ),
18
+ ]