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.
- django_nativemojo-0.1.15.dist-info/METADATA +136 -0
- {django_nativemojo-0.1.10.dist-info → django_nativemojo-0.1.15.dist-info}/RECORD +105 -65
- mojo/__init__.py +1 -1
- mojo/apps/account/management/__init__.py +5 -0
- mojo/apps/account/management/commands/__init__.py +6 -0
- mojo/apps/account/management/commands/serializer_admin.py +531 -0
- mojo/apps/account/migrations/0004_user_avatar.py +20 -0
- mojo/apps/account/migrations/0005_group_last_activity.py +18 -0
- mojo/apps/account/models/group.py +25 -7
- mojo/apps/account/models/member.py +15 -4
- mojo/apps/account/models/user.py +197 -20
- mojo/apps/account/rest/group.py +1 -0
- mojo/apps/account/rest/user.py +6 -2
- mojo/apps/aws/rest/__init__.py +1 -0
- mojo/apps/aws/rest/s3.py +64 -0
- mojo/apps/fileman/README.md +8 -8
- mojo/apps/fileman/backends/base.py +76 -70
- mojo/apps/fileman/backends/filesystem.py +86 -86
- mojo/apps/fileman/backends/s3.py +200 -108
- mojo/apps/fileman/migrations/0001_initial.py +106 -0
- mojo/apps/fileman/migrations/0002_filemanager_parent_alter_filemanager_max_file_size.py +24 -0
- mojo/apps/fileman/migrations/0003_remove_file_fileman_fil_upload__c4bc35_idx_and_more.py +25 -0
- mojo/apps/fileman/migrations/0004_remove_file_original_filename_and_more.py +39 -0
- mojo/apps/fileman/migrations/0005_alter_file_upload_token.py +18 -0
- mojo/apps/fileman/migrations/0006_file_download_url_filemanager_forever_urls.py +23 -0
- mojo/apps/fileman/migrations/0007_remove_filemanager_forever_urls_and_more.py +22 -0
- mojo/apps/fileman/migrations/0008_file_category.py +18 -0
- mojo/apps/fileman/migrations/0009_rename_file_path_file_storage_file_path.py +18 -0
- mojo/apps/fileman/migrations/0010_filerendition.py +33 -0
- mojo/apps/fileman/migrations/0011_alter_filerendition_original_file.py +19 -0
- mojo/apps/fileman/models/__init__.py +1 -5
- mojo/apps/fileman/models/file.py +204 -58
- mojo/apps/fileman/models/manager.py +161 -31
- mojo/apps/fileman/models/rendition.py +118 -0
- mojo/apps/fileman/renderer/__init__.py +111 -0
- mojo/apps/fileman/renderer/audio.py +403 -0
- mojo/apps/fileman/renderer/base.py +205 -0
- mojo/apps/fileman/renderer/document.py +404 -0
- mojo/apps/fileman/renderer/image.py +222 -0
- mojo/apps/fileman/renderer/utils.py +297 -0
- mojo/apps/fileman/renderer/video.py +304 -0
- mojo/apps/fileman/rest/__init__.py +1 -18
- mojo/apps/fileman/rest/upload.py +22 -32
- mojo/apps/fileman/signals.py +58 -0
- mojo/apps/fileman/tasks.py +254 -0
- mojo/apps/fileman/utils/__init__.py +40 -16
- mojo/apps/incident/migrations/0005_incidenthistory.py +39 -0
- mojo/apps/incident/migrations/0006_alter_incident_state.py +18 -0
- mojo/apps/incident/models/__init__.py +1 -0
- mojo/apps/incident/models/history.py +36 -0
- mojo/apps/incident/models/incident.py +1 -1
- mojo/apps/incident/reporter.py +3 -1
- mojo/apps/incident/rest/event.py +7 -1
- mojo/apps/logit/migrations/0004_alter_log_level.py +18 -0
- mojo/apps/logit/models/log.py +4 -1
- mojo/apps/metrics/utils.py +2 -2
- mojo/apps/notify/handlers/ses/message.py +1 -1
- mojo/apps/notify/providers/aws.py +2 -2
- mojo/apps/tasks/__init__.py +34 -1
- mojo/apps/tasks/manager.py +200 -45
- mojo/apps/tasks/rest/tasks.py +24 -10
- mojo/apps/tasks/runner.py +283 -18
- mojo/apps/tasks/task.py +99 -0
- mojo/apps/tasks/tq_handlers.py +118 -0
- mojo/decorators/auth.py +6 -1
- mojo/decorators/http.py +7 -2
- mojo/helpers/aws/__init__.py +41 -0
- mojo/helpers/aws/ec2.py +804 -0
- mojo/helpers/aws/iam.py +748 -0
- mojo/helpers/aws/s3.py +451 -11
- mojo/helpers/aws/ses.py +483 -0
- mojo/helpers/aws/sns.py +461 -0
- mojo/helpers/crypto/__pycache__/hash.cpython-310.pyc +0 -0
- mojo/helpers/crypto/__pycache__/sign.cpython-310.pyc +0 -0
- mojo/helpers/crypto/__pycache__/utils.cpython-310.pyc +0 -0
- mojo/helpers/dates.py +18 -0
- mojo/helpers/response.py +6 -2
- mojo/helpers/settings/__init__.py +2 -0
- mojo/helpers/{settings.py → settings/helper.py} +1 -37
- mojo/helpers/settings/parser.py +132 -0
- mojo/middleware/logging.py +1 -1
- mojo/middleware/mojo.py +5 -0
- mojo/models/rest.py +261 -46
- mojo/models/secrets.py +13 -4
- mojo/serializers/__init__.py +100 -0
- mojo/serializers/advanced/README.md +363 -0
- mojo/serializers/advanced/__init__.py +247 -0
- mojo/serializers/advanced/formats/__init__.py +28 -0
- mojo/serializers/advanced/formats/csv.py +416 -0
- mojo/serializers/advanced/formats/excel.py +516 -0
- mojo/serializers/advanced/formats/json.py +239 -0
- mojo/serializers/advanced/formats/localizers.py +509 -0
- mojo/serializers/advanced/formats/response.py +485 -0
- mojo/serializers/advanced/serializer.py +568 -0
- mojo/serializers/manager.py +501 -0
- mojo/serializers/optimized.py +618 -0
- mojo/serializers/settings_example.py +322 -0
- mojo/serializers/{models.py → simple.py} +38 -15
- testit/helpers.py +21 -4
- django_nativemojo-0.1.10.dist-info/METADATA +0 -96
- mojo/apps/metrics/rest/db.py +0 -0
- mojo/helpers/aws/setup_email.py +0 -0
- mojo/ws4redis/README.md +0 -174
- mojo/ws4redis/__init__.py +0 -2
- mojo/ws4redis/client.py +0 -283
- mojo/ws4redis/connection.py +0 -327
- mojo/ws4redis/exceptions.py +0 -32
- mojo/ws4redis/redis.py +0 -183
- mojo/ws4redis/servers/base.py +0 -86
- mojo/ws4redis/servers/django.py +0 -171
- mojo/ws4redis/servers/uwsgi.py +0 -63
- mojo/ws4redis/settings.py +0 -45
- mojo/ws4redis/utf8validator.py +0 -128
- mojo/ws4redis/websocket.py +0 -403
- {django_nativemojo-0.1.10.dist-info → django_nativemojo-0.1.15.dist-info}/LICENSE +0 -0
- {django_nativemojo-0.1.10.dist-info → django_nativemojo-0.1.15.dist-info}/NOTICE +0 -0
- {django_nativemojo-0.1.10.dist-info → django_nativemojo-0.1.15.dist-info}/WHEEL +0 -0
- /mojo/{ws4redis/servers → apps/aws}/__init__.py +0 -0
- /mojo/apps/{fileman/models/render.py → aws/models/__init__.py} +0 -0
- /mojo/apps/fileman/{rest/__init__ → migrations/__init__.py} +0 -0
mojo/middleware/logging.py
CHANGED
@@ -44,7 +44,7 @@ class LoggerMiddleware:
|
|
44
44
|
if LOGIT_DB_ALL:
|
45
45
|
request.request_log = Log.logit(request, request.DATA.to_json(as_string=True), "request")
|
46
46
|
if LOGIT_FILE_ALL:
|
47
|
-
LOGGER.info(f"REQUEST - {request.method} - {request.ip} - {request.path}", request.
|
47
|
+
LOGGER.info(f"REQUEST - {request.method} - {request.ip} - {request.path}", request._raw_body)
|
48
48
|
|
49
49
|
def log_response(self, request, response):
|
50
50
|
if not self.can_log(request):
|
mojo/middleware/mojo.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
from mojo.helpers import request as rhelper
|
2
2
|
import time
|
3
3
|
from objict import objict
|
4
|
+
from mojo.helpers.settings import settings
|
4
5
|
|
5
6
|
ANONYMOUS_USER = objict(is_authenticated=False)
|
6
7
|
|
@@ -17,5 +18,9 @@ class MojoMiddleware:
|
|
17
18
|
request.ip = rhelper.get_remote_ip(request)
|
18
19
|
request.user_agent = rhelper.get_user_agent(request)
|
19
20
|
request.duid = rhelper.get_device_id(request)
|
21
|
+
if settings.LOGIT_REQUEST_BODY:
|
22
|
+
request._raw_body = str(request.body)
|
23
|
+
else:
|
24
|
+
request._raw_body = None
|
20
25
|
request.DATA = rhelper.parse_request_data(request)
|
21
26
|
return self.get_response(request)
|
mojo/models/rest.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# from django.http import JsonResponse
|
2
2
|
from mojo.helpers.response import JsonResponse
|
3
|
-
from mojo.serializers.
|
3
|
+
from mojo.serializers.simple import GraphSerializer
|
4
|
+
from mojo.serializers.manager import get_serializer_manager
|
4
5
|
from mojo.helpers import modules
|
5
6
|
from mojo.helpers.settings import settings
|
6
7
|
from django.core.exceptions import ObjectDoesNotExist
|
@@ -22,6 +23,13 @@ class MojoModel:
|
|
22
23
|
"""Returns the active request being processed."""
|
23
24
|
return ACTIVE_REQUEST
|
24
25
|
|
26
|
+
@property
|
27
|
+
def active_user(self):
|
28
|
+
"""Returns the active user being processed."""
|
29
|
+
if ACTIVE_REQUEST:
|
30
|
+
return ACTIVE_REQUEST.user
|
31
|
+
return None
|
32
|
+
|
25
33
|
@classmethod
|
26
34
|
def get_rest_meta_prop(cls, name, default=None):
|
27
35
|
"""
|
@@ -116,7 +124,7 @@ class MojoModel:
|
|
116
124
|
@classmethod
|
117
125
|
def rest_check_permission(cls, request, permission_keys, instance=None):
|
118
126
|
"""
|
119
|
-
Check permissions for a given request.
|
127
|
+
Check permissions for a given request. Reports granular denied feedback to incident/event system.
|
120
128
|
|
121
129
|
Args:
|
122
130
|
request: Django HTTP request object.
|
@@ -129,20 +137,69 @@ class MojoModel:
|
|
129
137
|
perms = cls.get_rest_meta_prop(permission_keys, [])
|
130
138
|
if perms is None or len(perms) == 0:
|
131
139
|
return True
|
140
|
+
|
132
141
|
if "all" not in perms:
|
133
142
|
if request.user is None or not request.user.is_authenticated:
|
143
|
+
cls.class_report_incident(
|
144
|
+
details="Permission denied: unauthenticated user",
|
145
|
+
event_type="unauthenticated",
|
146
|
+
request=request,
|
147
|
+
perms=perms,
|
148
|
+
permission_keys=permission_keys,
|
149
|
+
branch="unauthenticated",
|
150
|
+
instance=repr(instance) if instance else None,
|
151
|
+
request_path=getattr(request, "path", None),
|
152
|
+
)
|
134
153
|
return False
|
154
|
+
|
135
155
|
if instance is not None:
|
136
156
|
if hasattr(instance, "check_edit_permission"):
|
137
|
-
|
157
|
+
allowed = instance.check_edit_permission(perms, request)
|
158
|
+
if not allowed:
|
159
|
+
cls.class_report_incident(
|
160
|
+
details="Permission denied: edit_permission_denied",
|
161
|
+
event_type="edit_permission_denied",
|
162
|
+
request=request,
|
163
|
+
perms=perms,
|
164
|
+
permission_keys=permission_keys,
|
165
|
+
branch="instance.check_edit_permission",
|
166
|
+
instance=repr(instance),
|
167
|
+
request_path=getattr(request, "path", None),
|
168
|
+
)
|
169
|
+
return allowed
|
138
170
|
if "owner" in perms and getattr(instance, "user", None) is not None:
|
139
171
|
if instance.user.id == request.user.id:
|
140
172
|
return True
|
173
|
+
|
141
174
|
if request.group and hasattr(cls, "group"):
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
175
|
+
allowed = request.group.member_has_permission(request.user, perms)
|
176
|
+
if not allowed:
|
177
|
+
cls.class_report_incident(
|
178
|
+
details="Permission denied: group_member_permission_denied",
|
179
|
+
event_type="group_member_permission_denied",
|
180
|
+
request=request,
|
181
|
+
perms=perms,
|
182
|
+
permission_keys=permission_keys,
|
183
|
+
group=getattr(request, "group", None),
|
184
|
+
branch="group.member_has_permission",
|
185
|
+
instance=repr(instance) if instance else None,
|
186
|
+
request_path=getattr(request, "path", None),
|
187
|
+
)
|
188
|
+
return allowed
|
189
|
+
|
190
|
+
allowed = request.user.has_permission(perms)
|
191
|
+
if not allowed:
|
192
|
+
cls.class_report_incident(
|
193
|
+
details="Permission denied: user_permission_denied",
|
194
|
+
event_type="user_permission_denied",
|
195
|
+
request=request,
|
196
|
+
perms=perms,
|
197
|
+
permission_keys=permission_keys,
|
198
|
+
branch="user.has_permission",
|
199
|
+
instance=repr(instance) if instance else None,
|
200
|
+
request_path=getattr(request, "path", None),
|
201
|
+
)
|
202
|
+
return allowed
|
146
203
|
|
147
204
|
@classmethod
|
148
205
|
def on_rest_handle_get(cls, request, instance):
|
@@ -206,6 +263,7 @@ class MojoModel:
|
|
206
263
|
Returns:
|
207
264
|
JsonResponse representing the list of resources.
|
208
265
|
"""
|
266
|
+
cls.debug("on_rest_handle_list")
|
209
267
|
if cls.rest_check_permission(request, "VIEW_PERMS"):
|
210
268
|
return cls.on_rest_list(request)
|
211
269
|
return cls.rest_error_response(request, 403, error=f"GET permission denied: {cls.__name__}")
|
@@ -223,7 +281,9 @@ class MojoModel:
|
|
223
281
|
"""
|
224
282
|
if cls.rest_check_permission(request, ["SAVE_PERMS", "VIEW_PERMS"]):
|
225
283
|
instance = cls()
|
226
|
-
|
284
|
+
instance.on_rest_save(request, request.DATA)
|
285
|
+
instance.on_rest_created()
|
286
|
+
return instance.on_rest_get(request)
|
227
287
|
return cls.rest_error_response(request, 403, error=f"CREATE permission denied: {cls.__name__}")
|
228
288
|
|
229
289
|
@classmethod
|
@@ -254,6 +314,7 @@ class MojoModel:
|
|
254
314
|
Returns:
|
255
315
|
JsonResponse representing the paginated and serialized list of objects.
|
256
316
|
"""
|
317
|
+
cls.debug("on_rest_list:start")
|
257
318
|
if queryset is None:
|
258
319
|
queryset = cls.objects.all()
|
259
320
|
if request.group is not None and hasattr(cls, "group"):
|
@@ -263,6 +324,7 @@ class MojoModel:
|
|
263
324
|
queryset = cls.on_rest_list_filter(request, queryset)
|
264
325
|
queryset = cls.on_rest_list_date_range_filter(request, queryset)
|
265
326
|
queryset = cls.on_rest_list_sort(request, queryset)
|
327
|
+
cls.debug("on_rest_list:end")
|
266
328
|
return cls.on_rest_list_response(request, queryset)
|
267
329
|
|
268
330
|
@classmethod
|
@@ -273,7 +335,9 @@ class MojoModel:
|
|
273
335
|
page_end = page_start+page_size
|
274
336
|
paged_queryset = queryset[page_start:page_end]
|
275
337
|
graph = request.DATA.get("graph", "list")
|
276
|
-
serializer
|
338
|
+
# Use serializer manager for optimal performance
|
339
|
+
manager = get_serializer_manager()
|
340
|
+
serializer = manager.get_serializer(paged_queryset, graph=graph, many=True)
|
277
341
|
return serializer.to_response(request, count=queryset.count(), start=page_start, size=page_size)
|
278
342
|
|
279
343
|
@classmethod
|
@@ -333,7 +397,7 @@ class MojoModel:
|
|
333
397
|
@classmethod
|
334
398
|
def on_rest_list_search(cls, request, queryset):
|
335
399
|
"""
|
336
|
-
Search queryset based on '
|
400
|
+
Search queryset based on 'search' param in the request for fields defined in 'SEARCH_FIELDS'.
|
337
401
|
|
338
402
|
Args:
|
339
403
|
request: Django HTTP request object.
|
@@ -342,7 +406,7 @@ class MojoModel:
|
|
342
406
|
Returns:
|
343
407
|
The filtered queryset based on the search criteria.
|
344
408
|
"""
|
345
|
-
search_query = request.GET.get('
|
409
|
+
search_query = request.GET.get('search', None)
|
346
410
|
if not search_query:
|
347
411
|
return queryset
|
348
412
|
|
@@ -397,8 +461,7 @@ class MojoModel:
|
|
397
461
|
}
|
398
462
|
return JsonResponse(response_payload)
|
399
463
|
|
400
|
-
|
401
|
-
def on_rest_create(cls, request):
|
464
|
+
def on_rest_created(self):
|
402
465
|
"""
|
403
466
|
Handle the creation of an object.
|
404
467
|
|
@@ -406,12 +469,38 @@ class MojoModel:
|
|
406
469
|
request: Django HTTP request object.
|
407
470
|
|
408
471
|
Returns:
|
409
|
-
|
472
|
+
None
|
410
473
|
"""
|
411
|
-
|
412
|
-
|
474
|
+
# Perform any additional actions after object creation
|
475
|
+
pass
|
413
476
|
|
414
|
-
def
|
477
|
+
def on_rest_pre_save(self, changed_fields, created):
|
478
|
+
"""
|
479
|
+
Handle the pre-save of an object.
|
480
|
+
|
481
|
+
Args:
|
482
|
+
created: Boolean indicating whether the object is being created.
|
483
|
+
changed_fields: Dictionary of fields that have changed.
|
484
|
+
Returns:
|
485
|
+
None
|
486
|
+
"""
|
487
|
+
# Perform any additional actions before object save
|
488
|
+
pass
|
489
|
+
|
490
|
+
def on_rest_saved(self, changed_fields, created):
|
491
|
+
"""
|
492
|
+
Handle the saving of an object.
|
493
|
+
|
494
|
+
Args:
|
495
|
+
created: Boolean indicating whether the object is being created.
|
496
|
+
changed_fields: Dictionary of fields that have changed.
|
497
|
+
Returns:
|
498
|
+
None
|
499
|
+
"""
|
500
|
+
# Perform any additional actions after object creation
|
501
|
+
pass
|
502
|
+
|
503
|
+
def on_rest_get(self, request, graph="default"):
|
415
504
|
"""
|
416
505
|
Handle the retrieval of a single object.
|
417
506
|
|
@@ -421,34 +510,93 @@ class MojoModel:
|
|
421
510
|
Returns:
|
422
511
|
JsonResponse representing the object.
|
423
512
|
"""
|
424
|
-
graph = request.GET.get("graph",
|
425
|
-
serializer
|
513
|
+
graph = request.GET.get("graph", graph)
|
514
|
+
# Use serializer manager for optimal performance
|
515
|
+
manager = get_serializer_manager()
|
516
|
+
serializer = manager.get_serializer(self, graph=graph)
|
426
517
|
return serializer.to_response(request)
|
427
518
|
|
519
|
+
def _set_field_change(self, key, old_value=None, new_value=None):
|
520
|
+
if not hasattr(self, "__changed_fields__"):
|
521
|
+
self.__changed_fields__ = objict.objict()
|
522
|
+
if old_value != new_value:
|
523
|
+
self.__changed_fields__[key] = old_value
|
524
|
+
|
525
|
+
def has_field_changed(self, key):
|
526
|
+
return key in self.__changed_fields__
|
527
|
+
|
428
528
|
def on_rest_save(self, request, data_dict):
|
429
529
|
"""
|
430
530
|
Create a model instance from a dictionary.
|
431
531
|
|
432
532
|
Args:
|
433
533
|
request: Django HTTP request object.
|
534
|
+
data_dict: Dictionary containing the data to save.
|
434
535
|
|
435
536
|
Returns:
|
436
537
|
None
|
437
538
|
"""
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
539
|
+
self.__changed_fields__ = objict.objict()
|
540
|
+
# Get fields that should not be saved
|
541
|
+
no_save_fields = self.get_rest_meta_prop("NO_SAVE_FIELDS", ["id", "pk", "created", "uuid"])
|
542
|
+
|
543
|
+
# Iterate through data_dict keys instead of model fields
|
544
|
+
for key, value in data_dict.items():
|
545
|
+
# Skip fields that shouldn't be saved
|
546
|
+
if key in no_save_fields:
|
547
|
+
continue
|
548
|
+
|
549
|
+
# First check for custom setter method
|
550
|
+
set_field_method = getattr(self, f'set_{key}', None)
|
551
|
+
if callable(set_field_method):
|
552
|
+
old_value = getattr(self, key, None)
|
553
|
+
set_field_method(value)
|
554
|
+
new_value = getattr(self, key, None)
|
555
|
+
self._set_field_change(key, old_value, new_value)
|
556
|
+
continue
|
557
|
+
|
558
|
+
# Check if this is a model field
|
559
|
+
field = self.get_model_field(key)
|
560
|
+
if field is None:
|
561
|
+
continue
|
562
|
+
if field.get_internal_type() == "ForeignKey":
|
563
|
+
self.on_rest_save_related_field(field, value, request)
|
564
|
+
elif field.get_internal_type() == "JSONField":
|
565
|
+
self.on_rest_update_jsonfield(key, value)
|
566
|
+
else:
|
567
|
+
self._set_field_change(key, getattr(self, key), value)
|
568
|
+
setattr(self, key, value)
|
569
|
+
|
570
|
+
created = self.pk is None
|
571
|
+
if created:
|
572
|
+
if request.user.is_authenticated and self.get_model_field("user"):
|
573
|
+
if getattr(self, "user", None) is None:
|
574
|
+
self.user = request.user
|
575
|
+
if request.group and self.get_model_field("group"):
|
576
|
+
if getattr(self, "group", None) is None:
|
577
|
+
self.group = request.group
|
578
|
+
self.on_rest_pre_save(self.__changed_fields__, created)
|
579
|
+
if "files" in data_dict:
|
580
|
+
self.on_rest_save_files(data_dict["files"])
|
451
581
|
self.atomic_save()
|
582
|
+
self.on_rest_saved(self.__changed_fields__, created)
|
583
|
+
|
584
|
+
def on_rest_save_files(self, files):
|
585
|
+
for name, file in files.items():
|
586
|
+
self.on_rest_save_file(name, file)
|
587
|
+
|
588
|
+
def on_rest_save_file(self, name, file):
|
589
|
+
# Implement file saving logic here
|
590
|
+
self.debug("Finding file for field: %s", name)
|
591
|
+
field = self.get_model_field(name)
|
592
|
+
if field is None:
|
593
|
+
return
|
594
|
+
self.debug("Saving file for field: %s", name)
|
595
|
+
if field.related_model and hasattr(field.related_model, "create_from_file"):
|
596
|
+
self.debug("Found file for field: %s", name)
|
597
|
+
related_model = field.related_model
|
598
|
+
instance = related_model.create_from_file(file, name)
|
599
|
+
setattr(self, name, instance)
|
452
600
|
|
453
601
|
def on_rest_save_and_respond(self, request):
|
454
602
|
self.on_rest_save(request, request.DATA)
|
@@ -466,11 +614,21 @@ class MojoModel:
|
|
466
614
|
if field.related_model.rest_check_permission(request, ["SAVE_PERMS", "VIEW_PERMS"], related_instance):
|
467
615
|
related_instance.on_rest_save(request, field_value)
|
468
616
|
return
|
469
|
-
|
617
|
+
if hasattr(field.related_model, "on_rest_related_save"):
|
618
|
+
related_instance = getattr(self, field.name)
|
619
|
+
field.related_model.on_rest_related_save(self, field.name, field_value, related_instance)
|
620
|
+
elif isinstance(field_value, int) or (isinstance(field_value, str)):
|
621
|
+
# self.debug(f"Related Model: {field.related_model.__name__}, Field Value: {field_value}")
|
622
|
+
if not bool(field_value):
|
623
|
+
# None, "", 0 will set it to None
|
624
|
+
setattr(self, field.name, None)
|
625
|
+
return
|
626
|
+
field_value = int(field_value)
|
627
|
+
if (self.pk == field_value):
|
628
|
+
self.debug("Skipping self-reference")
|
629
|
+
return
|
470
630
|
related_instance = field.related_model.objects.get(pk=field_value)
|
471
631
|
setattr(self, field.name, related_instance)
|
472
|
-
except field.related_model.DoesNotExist:
|
473
|
-
pass # Skip invalid related instances
|
474
632
|
|
475
633
|
def on_rest_update_jsonfield(self, field_name, field_value):
|
476
634
|
"""helper to update jsonfield by merge in changes"""
|
@@ -487,6 +645,18 @@ class MojoModel:
|
|
487
645
|
setattr(self, field_name, existing_value)
|
488
646
|
return existing_value
|
489
647
|
|
648
|
+
def on_rest_pre_delete(self):
|
649
|
+
"""
|
650
|
+
Handle the pre-deletion of an object.
|
651
|
+
|
652
|
+
Args:
|
653
|
+
request: Django HTTP request object.
|
654
|
+
|
655
|
+
Returns:
|
656
|
+
JsonResponse representing the result of the pre-delete operation.
|
657
|
+
"""
|
658
|
+
pass
|
659
|
+
|
490
660
|
def on_rest_delete(self, request):
|
491
661
|
"""
|
492
662
|
Handle the deletion of an object.
|
@@ -498,20 +668,23 @@ class MojoModel:
|
|
498
668
|
JsonResponse representing the result of the delete operation.
|
499
669
|
"""
|
500
670
|
try:
|
671
|
+
self.on_rest_pre_delete()
|
501
672
|
with transaction.atomic():
|
502
673
|
self.delete()
|
503
|
-
return JsonResponse({"status": "deleted"}, status=
|
674
|
+
return JsonResponse({"status": "deleted"}, status=200)
|
504
675
|
except Exception as e:
|
505
676
|
return JsonResponse({"error": str(e)}, status=400)
|
506
677
|
|
507
678
|
def to_dict(self, graph="default"):
|
508
|
-
serializer
|
509
|
-
|
679
|
+
# Use serializer manager for optimal performance
|
680
|
+
manager = get_serializer_manager()
|
681
|
+
return manager.serialize(self, graph=graph)
|
510
682
|
|
511
683
|
@classmethod
|
512
684
|
def queryset_to_dict(cls, qset, graph="default"):
|
513
|
-
serializer
|
514
|
-
|
685
|
+
# Use serializer manager for optimal performance
|
686
|
+
manager = get_serializer_manager()
|
687
|
+
return manager.serialize(qset, graph=graph, many=True)
|
515
688
|
|
516
689
|
def atomic_save(self):
|
517
690
|
"""
|
@@ -520,19 +693,61 @@ class MojoModel:
|
|
520
693
|
with transaction.atomic():
|
521
694
|
self.save()
|
522
695
|
|
523
|
-
def report_incident(self,
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
696
|
+
def report_incident(self, details, event_type="info", level=1, request=None, **context):
|
697
|
+
"""
|
698
|
+
Instance-level audit/event reporting. Automatically includes model+id.
|
699
|
+
"""
|
700
|
+
context = dict(context)
|
701
|
+
context.setdefault("model_name", self.__class__.__name__)
|
702
|
+
if hasattr(self, 'id'):
|
703
|
+
context.setdefault("model_id", self.id)
|
704
|
+
self.__class__.class_report_incident(
|
705
|
+
details, event_type=event_type, level=level, request=request, **context
|
706
|
+
)
|
528
707
|
|
529
|
-
|
708
|
+
@classmethod
|
709
|
+
def class_report_incident(cls, details, event_type="info", level=1, request=None, **context):
|
710
|
+
"""
|
711
|
+
Class-level audit/event reporting.
|
712
|
+
details: Human description.
|
713
|
+
event_type: Category/kind (e.g. "permission_denied", "security_alert").
|
714
|
+
level: Numeric severity.
|
715
|
+
request: Optional HTTP request or actor.
|
716
|
+
**context: Any additional context.
|
717
|
+
"""
|
718
|
+
from mojo.apps import incident
|
719
|
+
context = dict(context)
|
720
|
+
context.setdefault("model_name", cls.__name__)
|
721
|
+
incident.report_event(
|
722
|
+
details,
|
723
|
+
title=details[:80],
|
724
|
+
category=event_type,
|
725
|
+
level=level,
|
726
|
+
request=request,
|
727
|
+
**context
|
728
|
+
)
|
729
|
+
|
730
|
+
def log(self, log="", kind="model_log", level="info", **kwargs):
|
530
731
|
return self.class_logit(ACTIVE_REQUEST, log, kind, self.id, level, **kwargs)
|
531
732
|
|
532
733
|
def model_logit(self, request, log, kind="model_log", level="info", **kwargs):
|
533
734
|
return self.class_logit(request, log, kind, self.id, level, **kwargs)
|
534
735
|
|
736
|
+
@classmethod
|
737
|
+
def debug(cls, log, *args):
|
738
|
+
return logger.info(log, *args)
|
739
|
+
|
535
740
|
@classmethod
|
536
741
|
def class_logit(cls, request, log, kind="cls_log", model_id=0, level="info", **kwargs):
|
537
742
|
from mojo.apps.logit.models import Log
|
538
743
|
return Log.logit(request, log, kind, cls.__name__, model_id, level, **kwargs)
|
744
|
+
|
745
|
+
@classmethod
|
746
|
+
def get_model_field(cls, field_name):
|
747
|
+
"""
|
748
|
+
Get a model field by name.
|
749
|
+
"""
|
750
|
+
try:
|
751
|
+
return cls._meta.get_field(field_name)
|
752
|
+
except Exception:
|
753
|
+
return None
|
mojo/models/secrets.py
CHANGED
@@ -11,12 +11,18 @@ class MojoSecrets(models.Model):
|
|
11
11
|
|
12
12
|
mojo_secrets = models.TextField(blank=True, null=True, default=None)
|
13
13
|
_exposed_secrets = None
|
14
|
+
_secrets_changed = False
|
14
15
|
|
15
16
|
def set_secrets(self, value):
|
17
|
+
self.debug("Setting secrets", repr(value))
|
18
|
+
if isinstance(value, str):
|
19
|
+
value = objict.from_json(value)
|
16
20
|
self._exposed_secrets = merge_dicts(self.secrets, value)
|
21
|
+
self._secrets_changed = True
|
17
22
|
|
18
23
|
def set_secret(self, key, value):
|
19
24
|
self.secrets[key] = value
|
25
|
+
self._secrets_changed = True
|
20
26
|
|
21
27
|
def get_secret(self, key, default=None):
|
22
28
|
return self.secrets.get(key, default)
|
@@ -24,6 +30,7 @@ class MojoSecrets(models.Model):
|
|
24
30
|
def clear_secrets(self):
|
25
31
|
self.mojo_secrets = None
|
26
32
|
self._exposed_secrets = objict()
|
33
|
+
self._secrets_changed = True
|
27
34
|
|
28
35
|
@property
|
29
36
|
def secrets(self):
|
@@ -44,10 +51,12 @@ class MojoSecrets(models.Model):
|
|
44
51
|
return salt
|
45
52
|
|
46
53
|
def save_secrets(self):
|
47
|
-
if self.
|
48
|
-
|
49
|
-
|
50
|
-
|
54
|
+
if self._secrets_changed:
|
55
|
+
if self._exposed_secrets:
|
56
|
+
self.mojo_secrets = crypto.encrypt( self._exposed_secrets, self._get_secrets_password())
|
57
|
+
else:
|
58
|
+
self.mojo_secrets = None
|
59
|
+
self._secrets_changed = False
|
51
60
|
|
52
61
|
def save(self, *args, **kwargs):
|
53
62
|
if self.pk is not None:
|
mojo/serializers/__init__.py
CHANGED
@@ -0,0 +1,100 @@
|
|
1
|
+
"""
|
2
|
+
Django-MOJO Serializers Package
|
3
|
+
|
4
|
+
Provides high-performance serialization for Django models with RestMeta.GRAPHS support.
|
5
|
+
Includes multiple serializer backends optimized for different use cases:
|
6
|
+
|
7
|
+
- OptimizedGraphSerializer: Ultra-fast with intelligent caching (default)
|
8
|
+
- GraphSerializer: Simple and reliable fallback
|
9
|
+
- AdvancedGraphSerializer: Feature-rich with multiple format support
|
10
|
+
|
11
|
+
Usage:
|
12
|
+
from mojo.serializers import serialize, to_json, to_response
|
13
|
+
|
14
|
+
# Quick serialization
|
15
|
+
data = serialize(instance, graph="detail")
|
16
|
+
json_str = to_json(queryset, graph="list")
|
17
|
+
response = to_response(instance, request, graph="default")
|
18
|
+
|
19
|
+
# Direct serializer access
|
20
|
+
from mojo.serializers import OptimizedGraphSerializer
|
21
|
+
serializer = OptimizedGraphSerializer(instance, graph="detail")
|
22
|
+
data = serializer.serialize()
|
23
|
+
"""
|
24
|
+
|
25
|
+
# Core serializer classes
|
26
|
+
from .simple import GraphSerializer
|
27
|
+
from .optimized import OptimizedGraphSerializer
|
28
|
+
|
29
|
+
# Advanced serializer (optional - may not be available)
|
30
|
+
try:
|
31
|
+
from .advanced import AdvancedGraphSerializer
|
32
|
+
except ImportError:
|
33
|
+
AdvancedGraphSerializer = None
|
34
|
+
|
35
|
+
# Manager and convenience functions
|
36
|
+
from .manager import (
|
37
|
+
SerializerManager,
|
38
|
+
get_serializer_manager,
|
39
|
+
register_serializer,
|
40
|
+
set_default_serializer,
|
41
|
+
serialize,
|
42
|
+
to_json,
|
43
|
+
to_response,
|
44
|
+
get_performance_stats,
|
45
|
+
clear_serializer_caches,
|
46
|
+
benchmark_serializers
|
47
|
+
)
|
48
|
+
|
49
|
+
# Version and metadata
|
50
|
+
__version__ = "2.0.0"
|
51
|
+
__author__ = "Django-MOJO Team"
|
52
|
+
|
53
|
+
# Default exports
|
54
|
+
__all__ = [
|
55
|
+
# Core serializer classes
|
56
|
+
'GraphSerializer',
|
57
|
+
'OptimizedGraphSerializer',
|
58
|
+
'AdvancedGraphSerializer',
|
59
|
+
|
60
|
+
# Manager
|
61
|
+
'SerializerManager',
|
62
|
+
'get_serializer_manager',
|
63
|
+
|
64
|
+
# Registration functions
|
65
|
+
'register_serializer',
|
66
|
+
'set_default_serializer',
|
67
|
+
|
68
|
+
# Convenience functions
|
69
|
+
'serialize',
|
70
|
+
'to_json',
|
71
|
+
'to_response',
|
72
|
+
|
73
|
+
# Performance monitoring
|
74
|
+
'get_performance_stats',
|
75
|
+
'clear_serializer_caches',
|
76
|
+
'benchmark_serializers',
|
77
|
+
]
|
78
|
+
|
79
|
+
# Initialize default manager on import
|
80
|
+
_manager = get_serializer_manager()
|
81
|
+
|
82
|
+
# Convenience shortcuts at package level
|
83
|
+
def get_serializer(instance, graph="default", many=None, serializer_type=None, **kwargs):
|
84
|
+
"""Get configured serializer instance."""
|
85
|
+
return _manager.get_serializer(instance, graph, many, serializer_type, **kwargs)
|
86
|
+
|
87
|
+
def list_serializers():
|
88
|
+
"""List all registered serializers."""
|
89
|
+
return _manager.registry.list_serializers()
|
90
|
+
|
91
|
+
def get_default_serializer():
|
92
|
+
"""Get the current default serializer name."""
|
93
|
+
return _manager.registry.get_default()
|
94
|
+
|
95
|
+
# Add shortcuts to exports
|
96
|
+
__all__.extend([
|
97
|
+
'get_serializer',
|
98
|
+
'list_serializers',
|
99
|
+
'get_default_serializer'
|
100
|
+
])
|