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.
- django_nativemojo-0.1.16.dist-info/METADATA +138 -0
- django_nativemojo-0.1.16.dist-info/RECORD +302 -0
- 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 +651 -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/migrations/0006_add_device_tracking_models.py +72 -0
- mojo/apps/account/migrations/0007_delete_userdevicelocation.py +16 -0
- mojo/apps/account/migrations/0008_userdevicelocation.py +33 -0
- mojo/apps/account/migrations/0009_geolocatedip_subnet.py +18 -0
- mojo/apps/account/migrations/0010_group_avatar.py +20 -0
- mojo/apps/account/migrations/0011_user_org_registereddevice_pushconfig_and_more.py +118 -0
- mojo/apps/account/migrations/0012_remove_pushconfig_apns_key_file_and_more.py +21 -0
- mojo/apps/account/migrations/0013_pushconfig_test_mode_alter_pushconfig_apns_enabled_and_more.py +28 -0
- mojo/apps/account/migrations/0014_notificationdelivery_data_payload_and_more.py +48 -0
- mojo/apps/account/models/__init__.py +2 -0
- mojo/apps/account/models/device.py +281 -0
- mojo/apps/account/models/group.py +319 -15
- mojo/apps/account/models/member.py +29 -5
- mojo/apps/account/models/push/__init__.py +4 -0
- mojo/apps/account/models/push/config.py +112 -0
- mojo/apps/account/models/push/delivery.py +93 -0
- mojo/apps/account/models/push/device.py +66 -0
- mojo/apps/account/models/push/template.py +99 -0
- mojo/apps/account/models/user.py +369 -19
- mojo/apps/account/rest/__init__.py +2 -0
- mojo/apps/account/rest/device.py +39 -0
- mojo/apps/account/rest/group.py +9 -0
- mojo/apps/account/rest/push.py +187 -0
- mojo/apps/account/rest/user.py +100 -6
- mojo/apps/account/services/__init__.py +1 -0
- mojo/apps/account/services/push.py +363 -0
- mojo/apps/aws/migrations/0001_initial.py +206 -0
- mojo/apps/aws/migrations/0002_emaildomain_can_recv_emaildomain_can_send_and_more.py +28 -0
- mojo/apps/aws/migrations/0003_mailbox_is_domain_default_mailbox_is_system_default_and_more.py +31 -0
- mojo/apps/aws/migrations/0004_s3bucket.py +39 -0
- mojo/apps/aws/migrations/0005_alter_emaildomain_region_delete_s3bucket.py +21 -0
- mojo/apps/aws/models/__init__.py +19 -0
- mojo/apps/aws/models/email_attachment.py +99 -0
- mojo/apps/aws/models/email_domain.py +218 -0
- mojo/apps/aws/models/email_template.py +132 -0
- mojo/apps/aws/models/incoming_email.py +197 -0
- mojo/apps/aws/models/mailbox.py +288 -0
- mojo/apps/aws/models/sent_message.py +175 -0
- mojo/apps/aws/rest/__init__.py +7 -0
- mojo/apps/aws/rest/email.py +33 -0
- mojo/apps/aws/rest/email_ops.py +183 -0
- mojo/apps/aws/rest/messages.py +32 -0
- mojo/apps/aws/rest/s3.py +64 -0
- mojo/apps/aws/rest/send.py +101 -0
- mojo/apps/aws/rest/sns.py +403 -0
- mojo/apps/aws/rest/templates.py +19 -0
- mojo/apps/aws/services/__init__.py +32 -0
- mojo/apps/aws/services/email.py +390 -0
- mojo/apps/aws/services/email_ops.py +548 -0
- mojo/apps/docit/__init__.py +6 -0
- mojo/apps/docit/markdown_plugins/syntax_highlight.py +25 -0
- mojo/apps/docit/markdown_plugins/toc.py +12 -0
- mojo/apps/docit/migrations/0001_initial.py +113 -0
- mojo/apps/docit/migrations/0002_alter_book_modified_by_alter_page_modified_by.py +26 -0
- mojo/apps/docit/migrations/0003_alter_book_group.py +20 -0
- mojo/apps/docit/models/__init__.py +17 -0
- mojo/apps/docit/models/asset.py +231 -0
- mojo/apps/docit/models/book.py +227 -0
- mojo/apps/docit/models/page.py +319 -0
- mojo/apps/docit/models/page_revision.py +203 -0
- mojo/apps/docit/rest/__init__.py +10 -0
- mojo/apps/docit/rest/asset.py +17 -0
- mojo/apps/docit/rest/book.py +22 -0
- mojo/apps/docit/rest/page.py +22 -0
- mojo/apps/docit/rest/page_revision.py +17 -0
- mojo/apps/docit/services/__init__.py +11 -0
- mojo/apps/docit/services/docit.py +315 -0
- mojo/apps/docit/services/markdown.py +44 -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 +409 -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 +240 -58
- mojo/apps/fileman/models/manager.py +427 -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/migrations/0007_event_uid.py +18 -0
- mojo/apps/incident/migrations/0008_ticket_ticketnote.py +55 -0
- mojo/apps/incident/migrations/0009_incident_status.py +18 -0
- mojo/apps/incident/migrations/0010_event_country_code.py +18 -0
- mojo/apps/incident/migrations/0011_incident_country_code.py +18 -0
- mojo/apps/incident/migrations/0012_alter_incident_status.py +18 -0
- mojo/apps/incident/models/__init__.py +2 -0
- mojo/apps/incident/models/event.py +35 -0
- mojo/apps/incident/models/history.py +36 -0
- mojo/apps/incident/models/incident.py +3 -1
- mojo/apps/incident/models/ticket.py +62 -0
- mojo/apps/incident/reporter.py +21 -1
- mojo/apps/incident/rest/__init__.py +1 -0
- mojo/apps/incident/rest/event.py +7 -1
- mojo/apps/incident/rest/ticket.py +43 -0
- mojo/apps/jobs/__init__.py +489 -0
- mojo/apps/jobs/adapters.py +24 -0
- mojo/apps/jobs/cli.py +616 -0
- mojo/apps/jobs/daemon.py +370 -0
- mojo/apps/jobs/examples/sample_jobs.py +376 -0
- mojo/apps/jobs/examples/webhook_examples.py +203 -0
- mojo/apps/jobs/handlers/__init__.py +5 -0
- mojo/apps/jobs/handlers/webhook.py +317 -0
- mojo/apps/jobs/job_engine.py +734 -0
- mojo/apps/jobs/keys.py +203 -0
- mojo/apps/jobs/local_queue.py +363 -0
- mojo/apps/jobs/management/__init__.py +3 -0
- mojo/apps/jobs/management/commands/__init__.py +3 -0
- mojo/apps/jobs/manager.py +1327 -0
- mojo/apps/jobs/migrations/0001_initial.py +97 -0
- mojo/apps/jobs/migrations/0002_alter_job_max_retries_joblog.py +39 -0
- mojo/apps/jobs/models/__init__.py +6 -0
- mojo/apps/jobs/models/job.py +441 -0
- mojo/apps/jobs/rest/__init__.py +2 -0
- mojo/apps/jobs/rest/control.py +466 -0
- mojo/apps/jobs/rest/jobs.py +421 -0
- mojo/apps/jobs/scheduler.py +571 -0
- mojo/apps/jobs/services/__init__.py +6 -0
- mojo/apps/jobs/services/job_actions.py +465 -0
- mojo/apps/jobs/settings.py +209 -0
- mojo/apps/logit/migrations/0004_alter_log_level.py +18 -0
- mojo/apps/logit/models/log.py +7 -1
- mojo/apps/metrics/__init__.py +8 -1
- mojo/apps/metrics/redis_metrics.py +198 -0
- mojo/apps/metrics/rest/__init__.py +3 -0
- mojo/apps/metrics/rest/categories.py +266 -0
- mojo/apps/metrics/rest/helpers.py +48 -0
- mojo/apps/metrics/rest/permissions.py +99 -0
- mojo/apps/metrics/rest/values.py +277 -0
- mojo/apps/metrics/utils.py +19 -2
- mojo/decorators/auth.py +6 -1
- mojo/decorators/http.py +47 -3
- mojo/helpers/aws/__init__.py +45 -0
- mojo/helpers/aws/ec2.py +804 -0
- mojo/helpers/aws/iam.py +748 -0
- mojo/helpers/aws/inbound_email.py +309 -0
- mojo/helpers/aws/kms.py +413 -0
- mojo/helpers/aws/s3.py +451 -11
- mojo/helpers/aws/ses.py +483 -0
- mojo/helpers/aws/ses_domain.py +959 -0
- mojo/helpers/aws/sns.py +461 -0
- mojo/helpers/crypto/__init__.py +1 -1
- mojo/helpers/crypto/utils.py +15 -0
- mojo/helpers/dates.py +18 -0
- mojo/helpers/location/__init__.py +2 -0
- mojo/helpers/location/countries.py +262 -0
- mojo/helpers/location/geolocation.py +196 -0
- mojo/helpers/logit.py +37 -0
- mojo/helpers/redis/__init__.py +2 -0
- mojo/helpers/redis/adapter.py +606 -0
- mojo/helpers/redis/client.py +48 -0
- mojo/helpers/redis/pool.py +225 -0
- mojo/helpers/request.py +8 -0
- mojo/helpers/response.py +14 -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/auth.py +1 -1
- mojo/middleware/cors.py +40 -0
- mojo/middleware/logging.py +131 -12
- mojo/middleware/mojo.py +10 -0
- mojo/models/rest.py +494 -65
- mojo/models/secrets.py +98 -3
- mojo/serializers/__init__.py +106 -0
- mojo/serializers/core/__init__.py +90 -0
- mojo/serializers/core/cache/__init__.py +121 -0
- mojo/serializers/core/cache/backends.py +518 -0
- mojo/serializers/core/cache/base.py +102 -0
- mojo/serializers/core/cache/disabled.py +181 -0
- mojo/serializers/core/cache/memory.py +287 -0
- mojo/serializers/core/cache/redis.py +533 -0
- mojo/serializers/core/cache/utils.py +454 -0
- mojo/serializers/core/manager.py +550 -0
- mojo/serializers/core/serializer.py +475 -0
- mojo/serializers/examples/settings.py +322 -0
- mojo/serializers/formats/csv.py +393 -0
- mojo/serializers/formats/localizers.py +509 -0
- mojo/serializers/{models.py → simple.py} +38 -15
- mojo/serializers/suggested_improvements.md +388 -0
- testit/client.py +1 -1
- testit/helpers.py +35 -4
- testit/runner.py +23 -6
- django_nativemojo-0.1.10.dist-info/METADATA +0 -96
- django_nativemojo-0.1.10.dist-info/RECORD +0 -194
- mojo/apps/metrics/rest/db.py +0 -0
- mojo/apps/notify/README.md +0 -91
- mojo/apps/notify/README_NOTIFICATIONS.md +0 -566
- mojo/apps/notify/admin.py +0 -52
- mojo/apps/notify/handlers/example_handlers.py +0 -516
- mojo/apps/notify/handlers/ses/__init__.py +0 -25
- mojo/apps/notify/handlers/ses/bounce.py +0 -0
- mojo/apps/notify/handlers/ses/complaint.py +0 -25
- mojo/apps/notify/handlers/ses/message.py +0 -86
- mojo/apps/notify/management/commands/__init__.py +0 -1
- mojo/apps/notify/management/commands/process_notifications.py +0 -370
- mojo/apps/notify/mod +0 -0
- mojo/apps/notify/models/__init__.py +0 -12
- mojo/apps/notify/models/account.py +0 -128
- mojo/apps/notify/models/attachment.py +0 -24
- mojo/apps/notify/models/bounce.py +0 -68
- mojo/apps/notify/models/complaint.py +0 -40
- mojo/apps/notify/models/inbox.py +0 -113
- mojo/apps/notify/models/inbox_message.py +0 -173
- mojo/apps/notify/models/outbox.py +0 -129
- mojo/apps/notify/models/outbox_message.py +0 -288
- mojo/apps/notify/models/template.py +0 -30
- mojo/apps/notify/providers/aws.py +0 -73
- mojo/apps/notify/rest/ses.py +0 -0
- mojo/apps/notify/utils/__init__.py +0 -2
- mojo/apps/notify/utils/notifications.py +0 -404
- mojo/apps/notify/utils/parsing.py +0 -202
- mojo/apps/notify/utils/render.py +0 -144
- mojo/apps/tasks/README.md +0 -118
- mojo/apps/tasks/__init__.py +0 -11
- mojo/apps/tasks/manager.py +0 -489
- mojo/apps/tasks/rest/__init__.py +0 -2
- mojo/apps/tasks/rest/hooks.py +0 -0
- mojo/apps/tasks/rest/tasks.py +0 -62
- mojo/apps/tasks/runner.py +0 -174
- mojo/apps/tasks/tq_handlers.py +0 -14
- mojo/helpers/aws/setup_email.py +0 -0
- mojo/helpers/redis.py +0 -10
- mojo/models/meta.py +0 -262
- 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.16.dist-info}/LICENSE +0 -0
- {django_nativemojo-0.1.10.dist-info → django_nativemojo-0.1.16.dist-info}/NOTICE +0 -0
- {django_nativemojo-0.1.10.dist-info → django_nativemojo-0.1.16.dist-info}/WHEEL +0 -0
- /mojo/apps/{notify → aws}/__init__.py +0 -0
- /mojo/apps/{notify/handlers → aws/migrations}/__init__.py +0 -0
- /mojo/apps/{notify/management → docit/markdown_plugins}/__init__.py +0 -0
- /mojo/apps/{notify/providers → docit/migrations}/__init__.py +0 -0
- /mojo/apps/{notify/rest → fileman/migrations}/__init__.py +0 -0
- /mojo/{ws4redis/servers → apps/jobs/examples}/__init__.py +0 -0
- /mojo/apps/{fileman/models/render.py → jobs/migrations/__init__.py} +0 -0
- /mojo/{serializers → rest}/openapi.py +0 -0
- /mojo/{apps/fileman/rest/__init__ → serializers/formats/__init__.py} +0 -0
mojo/models/rest.py
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
# from django.http import JsonResponse
|
2
2
|
from mojo.helpers.response import JsonResponse
|
3
|
-
from mojo.serializers
|
4
|
-
from mojo.helpers import modules
|
3
|
+
from mojo.serializers import get_serializer_manager
|
5
4
|
from mojo.helpers.settings import settings
|
5
|
+
from mojo import errors as me
|
6
6
|
from django.core.exceptions import ObjectDoesNotExist
|
7
7
|
from django.db import transaction, models as dm
|
8
8
|
import objict
|
9
|
+
import datetime
|
9
10
|
from mojo.helpers import dates, logit
|
10
11
|
|
11
12
|
|
@@ -13,6 +14,17 @@ logger = logit.get_logger("debug", "debug.log")
|
|
13
14
|
ACTIVE_REQUEST = None
|
14
15
|
LOGGING_CLASS = None
|
15
16
|
MOJO_APP_STATUS_200_ON_ERROR = settings.MOJO_APP_STATUS_200_ON_ERROR
|
17
|
+
MOJO_REST_LIST_PERM_DENY = settings.get("MOJO_REST_LIST_PERM_DENY", False)
|
18
|
+
|
19
|
+
# use this when there is no ACTIVE_REQUEST
|
20
|
+
SYSTEM_REQUEST = objict.objict()
|
21
|
+
SYSTEM_REQUEST.user = objict.objict()
|
22
|
+
SYSTEM_REQUEST.user.id = 1
|
23
|
+
SYSTEM_REQUEST.user.username = "system"
|
24
|
+
SYSTEM_REQUEST.user.email = ""
|
25
|
+
SYSTEM_REQUEST.user.is_authenticated = True
|
26
|
+
SYSTEM_REQUEST.user.has_permission = lambda perm: True
|
27
|
+
SYSTEM_REQUEST.DATA = objict.objict()
|
16
28
|
|
17
29
|
class MojoModel:
|
18
30
|
"""Base model class for REST operations with GraphSerializer integration."""
|
@@ -22,6 +34,13 @@ class MojoModel:
|
|
22
34
|
"""Returns the active request being processed."""
|
23
35
|
return ACTIVE_REQUEST
|
24
36
|
|
37
|
+
@property
|
38
|
+
def active_user(self):
|
39
|
+
"""Returns the active user being processed."""
|
40
|
+
if ACTIVE_REQUEST:
|
41
|
+
return ACTIVE_REQUEST.user
|
42
|
+
return None
|
43
|
+
|
25
44
|
@classmethod
|
26
45
|
def get_rest_meta_prop(cls, name, default=None):
|
27
46
|
"""
|
@@ -44,6 +63,17 @@ class MojoModel:
|
|
44
63
|
return default
|
45
64
|
return getattr(cls.RestMeta, name, default)
|
46
65
|
|
66
|
+
@classmethod
|
67
|
+
def get_rest_meta_graph(cls, graph_name):
|
68
|
+
graphs = cls.get_rest_meta_prop("GRAPHS", {})
|
69
|
+
if isinstance(graph_name, list):
|
70
|
+
for n in graph_name:
|
71
|
+
res = graphs.get(n, None)
|
72
|
+
if res is not None:
|
73
|
+
return res
|
74
|
+
return None
|
75
|
+
return graphs.get(graph_name, None)
|
76
|
+
|
47
77
|
@classmethod
|
48
78
|
def rest_error_response(cls, request, status=500, **kwargs):
|
49
79
|
"""
|
@@ -113,10 +143,20 @@ class MojoModel:
|
|
113
143
|
except ObjectDoesNotExist:
|
114
144
|
return cls.rest_error_response(None, 404, error=f"{cls.__name__} not found")
|
115
145
|
|
146
|
+
@classmethod
|
147
|
+
def get_instance_from_request(cls, request, field_name=None):
|
148
|
+
if field_name is None:
|
149
|
+
field_name = cls.__name__.lower()
|
150
|
+
if field_name not in request.DATA:
|
151
|
+
field_name = f"{field_name}_id"
|
152
|
+
if field_name not in request.DATA:
|
153
|
+
return None
|
154
|
+
return cls.objects.filter(pk=request.DATA.get(field_name)).last()
|
155
|
+
|
116
156
|
@classmethod
|
117
157
|
def rest_check_permission(cls, request, permission_keys, instance=None):
|
118
158
|
"""
|
119
|
-
Check permissions for a given request.
|
159
|
+
Check permissions for a given request. Reports granular denied feedback to incident/event system.
|
120
160
|
|
121
161
|
Args:
|
122
162
|
request: Django HTTP request object.
|
@@ -129,20 +169,94 @@ class MojoModel:
|
|
129
169
|
perms = cls.get_rest_meta_prop(permission_keys, [])
|
130
170
|
if perms is None or len(perms) == 0:
|
131
171
|
return True
|
172
|
+
|
132
173
|
if "all" not in perms:
|
133
174
|
if request.user is None or not request.user.is_authenticated:
|
175
|
+
cls.class_report_incident(
|
176
|
+
details="Permission denied: unauthenticated user",
|
177
|
+
event_type="unauthenticated",
|
178
|
+
request=request,
|
179
|
+
perms=perms,
|
180
|
+
permission_keys=permission_keys,
|
181
|
+
branch="unauthenticated",
|
182
|
+
instance=repr(instance) if instance else None,
|
183
|
+
request_path=getattr(request, "path", None),
|
184
|
+
)
|
134
185
|
return False
|
186
|
+
|
135
187
|
if instance is not None:
|
188
|
+
is_view = isinstance(permission_keys, list) and "VIEW_PERMS" in permission_keys
|
189
|
+
if not is_view and isinstance(permission_keys, str):
|
190
|
+
is_view = permission_keys == "VIEW_PERMS"
|
191
|
+
if is_view:
|
192
|
+
if hasattr(instance, "check_view_permission"):
|
193
|
+
allowed = instance.check_view_permission(perms, request)
|
194
|
+
if not allowed:
|
195
|
+
cls.class_report_incident(
|
196
|
+
details="Permission denied: view_permission_denied",
|
197
|
+
event_type="view_permission_denied",
|
198
|
+
request=request,
|
199
|
+
perms=perms,
|
200
|
+
permission_keys=permission_keys,
|
201
|
+
branch="instance.check_view_permission",
|
202
|
+
instance=repr(instance),
|
203
|
+
request_path=getattr(request, "path", None),
|
204
|
+
)
|
205
|
+
return allowed
|
206
|
+
|
136
207
|
if hasattr(instance, "check_edit_permission"):
|
137
|
-
|
138
|
-
|
139
|
-
|
208
|
+
allowed = instance.check_edit_permission(perms, request)
|
209
|
+
if not allowed:
|
210
|
+
cls.class_report_incident(
|
211
|
+
details="Permission denied: edit_permission_denied",
|
212
|
+
event_type="edit_permission_denied",
|
213
|
+
request=request,
|
214
|
+
perms=perms,
|
215
|
+
permission_keys=permission_keys,
|
216
|
+
branch="instance.check_edit_permission",
|
217
|
+
instance=repr(instance),
|
218
|
+
request_path=getattr(request, "path", None),
|
219
|
+
)
|
220
|
+
return allowed
|
221
|
+
|
222
|
+
if "owner" in perms:
|
223
|
+
owner_field = instance.get_rest_meta_prop("OWNER_FIELD", "user")
|
224
|
+
owner = getattr(instance, owner_field, None)
|
225
|
+
if owner is not None and owner.id == request.user.id:
|
140
226
|
return True
|
227
|
+
if hasattr(instance, "group"):
|
228
|
+
request.group = getattr(instance, "group", None)
|
229
|
+
|
141
230
|
if request.group and hasattr(cls, "group"):
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
231
|
+
allowed = request.group.user_has_permission(request.user, perms)
|
232
|
+
if not allowed:
|
233
|
+
cls.class_report_incident(
|
234
|
+
details="Permission denied: group_member_permission_denied",
|
235
|
+
event_type="group_member_permission_denied",
|
236
|
+
request=request,
|
237
|
+
perms=perms,
|
238
|
+
permission_keys=permission_keys,
|
239
|
+
group=getattr(request, "group", None),
|
240
|
+
branch="group.user_has_permission",
|
241
|
+
instance=repr(instance) if instance else None,
|
242
|
+
request_path=getattr(request, "path", None),
|
243
|
+
)
|
244
|
+
return allowed
|
245
|
+
if request.user is None or not request.user.is_authenticated:
|
246
|
+
return False
|
247
|
+
allowed = request.user.has_permission(perms)
|
248
|
+
if not allowed:
|
249
|
+
cls.class_report_incident(
|
250
|
+
details="Permission denied: user_permission_denied",
|
251
|
+
event_type="user_permission_denied",
|
252
|
+
request=request,
|
253
|
+
perms=perms,
|
254
|
+
permission_keys=permission_keys,
|
255
|
+
branch="user.has_permission",
|
256
|
+
instance=repr(instance) if instance else None,
|
257
|
+
request_path=getattr(request, "path", None),
|
258
|
+
)
|
259
|
+
return allowed
|
146
260
|
|
147
261
|
@classmethod
|
148
262
|
def on_rest_handle_get(cls, request, instance):
|
@@ -206,9 +320,41 @@ class MojoModel:
|
|
206
320
|
Returns:
|
207
321
|
JsonResponse representing the list of resources.
|
208
322
|
"""
|
323
|
+
# cls.debug("on_rest_handle_list")
|
209
324
|
if cls.rest_check_permission(request, "VIEW_PERMS"):
|
210
325
|
return cls.on_rest_list(request)
|
211
|
-
|
326
|
+
else:
|
327
|
+
perms = cls.get_rest_meta_prop("VIEW_PERMS", [])
|
328
|
+
if perms and "owner" in perms and request.user.is_authenticated:
|
329
|
+
owner_field = cls.get_rest_meta_prop("OWNER_FIELD", "user")
|
330
|
+
q = {owner_field: request.user}
|
331
|
+
return cls.on_rest_list(request, cls.objects.filter(**q))
|
332
|
+
if MOJO_REST_LIST_PERM_DENY:
|
333
|
+
return cls.rest_error_response(request, 403, error=f"GET permission denied: {cls.__name__}")
|
334
|
+
return cls.on_rest_list_response(request, cls.objects.none())
|
335
|
+
|
336
|
+
def update_from_dict(self, dict_data):
|
337
|
+
request = ACTIVE_REQUEST or SYSTEM_REQUEST
|
338
|
+
return self.on_rest_save(request, dict_data)
|
339
|
+
|
340
|
+
@classmethod
|
341
|
+
def create_from_dict(cls, dict_data, **kwargs):
|
342
|
+
request = kwargs.pop('request', ACTIVE_REQUEST or SYSTEM_REQUEST)
|
343
|
+
instance = cls(**kwargs)
|
344
|
+
instance.on_rest_save(request, dict_data)
|
345
|
+
instance.on_rest_created()
|
346
|
+
if cls.get_rest_meta_prop("LOG_CHANGES", False):
|
347
|
+
instance.log(kind="model:created", log=f"{request.user.username} created {instance.pk}")
|
348
|
+
return instance
|
349
|
+
|
350
|
+
@classmethod
|
351
|
+
def create_from_request(cls, request, **kwargs):
|
352
|
+
instance = cls(**kwargs)
|
353
|
+
instance.on_rest_save(request, request.DATA)
|
354
|
+
instance.on_rest_created()
|
355
|
+
if cls.get_rest_meta_prop("LOG_CHANGES", False):
|
356
|
+
instance.log(kind="model:created", log=f"{ACTIVE_REQUEST.user.username} created {instance.pk}")
|
357
|
+
return instance
|
212
358
|
|
213
359
|
@classmethod
|
214
360
|
def on_rest_handle_create(cls, request):
|
@@ -221,9 +367,9 @@ class MojoModel:
|
|
221
367
|
Returns:
|
222
368
|
JsonResponse representing the result of the create operation.
|
223
369
|
"""
|
224
|
-
if cls.rest_check_permission(request, ["SAVE_PERMS", "VIEW_PERMS"]):
|
225
|
-
instance = cls()
|
226
|
-
return instance.
|
370
|
+
if cls.rest_check_permission(request, ["CREATE_PERMS", "SAVE_PERMS", "VIEW_PERMS"]):
|
371
|
+
instance = cls.create_from_request(request)
|
372
|
+
return instance.on_rest_get(request)
|
227
373
|
return cls.rest_error_response(request, 403, error=f"CREATE permission denied: {cls.__name__}")
|
228
374
|
|
229
375
|
@classmethod
|
@@ -256,10 +402,18 @@ class MojoModel:
|
|
256
402
|
"""
|
257
403
|
if queryset is None:
|
258
404
|
queryset = cls.objects.all()
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
405
|
+
# for better query perfomance we want raw request GET data
|
406
|
+
request.QUERY_PARAMS = request.GET.copy()
|
407
|
+
if request.group is not None:
|
408
|
+
GROUP_FIELD = cls.get_rest_meta_prop("GROUP_FIELD", None)
|
409
|
+
if GROUP_FIELD or hasattr(cls, "group"):
|
410
|
+
if "group" in request.QUERY_PARAMS:
|
411
|
+
del request.QUERY_PARAMS["group"]
|
412
|
+
if GROUP_FIELD:
|
413
|
+
q = {GROUP_FIELD: request.group}
|
414
|
+
else:
|
415
|
+
q = {"group": request.group}
|
416
|
+
queryset = queryset.filter(**q)
|
263
417
|
queryset = cls.on_rest_list_filter(request, queryset)
|
264
418
|
queryset = cls.on_rest_list_date_range_filter(request, queryset)
|
265
419
|
queryset = cls.on_rest_list_sort(request, queryset)
|
@@ -268,13 +422,40 @@ class MojoModel:
|
|
268
422
|
@classmethod
|
269
423
|
def on_rest_list_response(cls, request, queryset):
|
270
424
|
# Implement pagination
|
271
|
-
page_size = request.DATA.get_typed("size", 10, int)
|
272
|
-
page_start = request.DATA.get_typed("start", 0, int)
|
425
|
+
page_size = request.DATA.get_typed(["size", "limit"], 10, int)
|
426
|
+
page_start = request.DATA.get_typed(["start", "offset"], 0, int)
|
273
427
|
page_end = page_start+page_size
|
274
428
|
paged_queryset = queryset[page_start:page_end]
|
275
429
|
graph = request.DATA.get("graph", "list")
|
276
|
-
|
277
|
-
|
430
|
+
format = request.DATA.get("download_format", "json")
|
431
|
+
count = queryset.count()
|
432
|
+
# Use serializer manager for optimal performance
|
433
|
+
manager = get_serializer_manager()
|
434
|
+
if format != "json":
|
435
|
+
format_key = format.split("_")[0]
|
436
|
+
serializer = manager.get_format_serializer(format_key)
|
437
|
+
formats = cls.get_rest_meta_prop("FORMATS")
|
438
|
+
if formats is not None and format in formats:
|
439
|
+
fields = formats[format]
|
440
|
+
else:
|
441
|
+
graph = cls.get_rest_meta_graph(["basic", "default"])
|
442
|
+
if not graph or not graph.get("fields"):
|
443
|
+
raise me.ValueException("No valid graph found")
|
444
|
+
fields = graph.get("fields")
|
445
|
+
logger.info(f"Serializing queryset with fields: {fields}")
|
446
|
+
return serializer.serialize_queryset(queryset, fields=fields, filename=request.DATA.get("filename", f"{cls.__name__}.csv"))
|
447
|
+
serializer = manager.get_serializer(paged_queryset, graph=graph, many=True)
|
448
|
+
resp = serializer.to_response(request, count=count, start=page_start, size=page_size)
|
449
|
+
resp.log_context = {
|
450
|
+
"endpoint": "list",
|
451
|
+
"model": cls.__name__,
|
452
|
+
"page_size": page_size,
|
453
|
+
"page_start": page_start,
|
454
|
+
"page_end": page_end,
|
455
|
+
"graph": graph,
|
456
|
+
"count": count
|
457
|
+
}
|
458
|
+
return resp
|
278
459
|
|
279
460
|
@classmethod
|
280
461
|
def on_rest_list_date_range_filter(cls, request, queryset):
|
@@ -305,6 +486,22 @@ class MojoModel:
|
|
305
486
|
queryset = queryset.filter(**{f"{dr_field}__lte": dr_end})
|
306
487
|
return queryset
|
307
488
|
|
489
|
+
@classmethod
|
490
|
+
def normalize_rest_value(cls, request, field_name, value):
|
491
|
+
field = cls.get_model_field(field_name)
|
492
|
+
if field.get_internal_type() == "BooleanField":
|
493
|
+
if isinstance(value, str):
|
494
|
+
value = value.lower() in ("true", "1")
|
495
|
+
elif isinstance(value, int):
|
496
|
+
value = bool(value)
|
497
|
+
else:
|
498
|
+
value = bool(value)
|
499
|
+
elif value is not None:
|
500
|
+
if field.get_internal_type() in ["DateTimeField", "DateField"]:
|
501
|
+
if not isinstance(value, (datetime.datetime, datetime.date)):
|
502
|
+
value = dates.parse_datetime(value)
|
503
|
+
return value
|
504
|
+
|
308
505
|
@classmethod
|
309
506
|
def on_rest_list_filter(cls, request, queryset):
|
310
507
|
"""
|
@@ -317,23 +514,29 @@ class MojoModel:
|
|
317
514
|
Returns:
|
318
515
|
The filtered queryset.
|
319
516
|
"""
|
517
|
+
reserved_keys = ["start", "size", "download_format", "dr_start", "dr_end", "limit", "offset"]
|
320
518
|
filters = {}
|
321
|
-
for key, value in request.
|
519
|
+
for key, value in request.QUERY_PARAMS.items():
|
322
520
|
# Split key to check for foreign key relationships
|
521
|
+
if "." in key:
|
522
|
+
key = key.replace(".", "__")
|
323
523
|
key_parts = key.split('__')
|
324
524
|
field_name = key_parts[0]
|
525
|
+
if field_name in reserved_keys:
|
526
|
+
continue
|
325
527
|
if hasattr(cls, field_name):
|
326
|
-
filters[key] = value
|
528
|
+
filters[key] = cls.normalize_rest_value(request, field_name, value)
|
327
529
|
elif field_name in cls.__rest_field_names__ and cls._meta.get_field(field_name).is_relation:
|
530
|
+
# TODO Normalize relation field values
|
328
531
|
filters[key] = value
|
329
|
-
|
532
|
+
logger.info("filters", filters)
|
330
533
|
queryset = cls.on_rest_list_search(request, queryset)
|
331
534
|
return queryset.filter(**filters)
|
332
535
|
|
333
536
|
@classmethod
|
334
537
|
def on_rest_list_search(cls, request, queryset):
|
335
538
|
"""
|
336
|
-
Search queryset based on '
|
539
|
+
Search queryset based on 'search' param in the request for fields defined in 'SEARCH_FIELDS'.
|
337
540
|
|
338
541
|
Args:
|
339
542
|
request: Django HTTP request object.
|
@@ -342,7 +545,7 @@ class MojoModel:
|
|
342
545
|
Returns:
|
343
546
|
The filtered queryset based on the search criteria.
|
344
547
|
"""
|
345
|
-
search_query = request.
|
548
|
+
search_query = request.DATA.get('search', None)
|
346
549
|
if not search_query:
|
347
550
|
return queryset
|
348
551
|
|
@@ -397,8 +600,7 @@ class MojoModel:
|
|
397
600
|
}
|
398
601
|
return JsonResponse(response_payload)
|
399
602
|
|
400
|
-
|
401
|
-
def on_rest_create(cls, request):
|
603
|
+
def on_rest_created(self):
|
402
604
|
"""
|
403
605
|
Handle the creation of an object.
|
404
606
|
|
@@ -406,12 +608,51 @@ class MojoModel:
|
|
406
608
|
request: Django HTTP request object.
|
407
609
|
|
408
610
|
Returns:
|
409
|
-
|
611
|
+
None
|
612
|
+
"""
|
613
|
+
# Perform any additional actions after object creation
|
614
|
+
pass
|
615
|
+
|
616
|
+
def on_rest_pre_save(self, changed_fields, created):
|
617
|
+
"""
|
618
|
+
Handle the pre-save of an object.
|
619
|
+
|
620
|
+
Args:
|
621
|
+
created: Boolean indicating whether the object is being created.
|
622
|
+
changed_fields: Dictionary of fields that have changed.
|
623
|
+
Returns:
|
624
|
+
None
|
625
|
+
"""
|
626
|
+
# Perform any additional actions before object save
|
627
|
+
pass
|
628
|
+
|
629
|
+
def on_rest_saved(self, changed_fields, created):
|
630
|
+
"""
|
631
|
+
Handle the saving of an object.
|
632
|
+
|
633
|
+
Args:
|
634
|
+
created: Boolean indicating whether the object is being created.
|
635
|
+
changed_fields: Dictionary of fields that have changed.
|
636
|
+
Returns:
|
637
|
+
None
|
410
638
|
"""
|
411
|
-
|
412
|
-
|
639
|
+
# Perform any additional actions after object creation
|
640
|
+
pass
|
413
641
|
|
414
|
-
def
|
642
|
+
def on_rest_response(self, request, graph="default"):
|
643
|
+
"""
|
644
|
+
Handle the response after a REST request.
|
645
|
+
|
646
|
+
Args:
|
647
|
+
request: Django HTTP request object.
|
648
|
+
graph: String representing the graph to use for serialization.
|
649
|
+
|
650
|
+
Returns:
|
651
|
+
JsonResponse representing the object.
|
652
|
+
"""
|
653
|
+
return self.on_rest_get(request, graph=graph)
|
654
|
+
|
655
|
+
def on_rest_get(self, request, graph="default"):
|
415
656
|
"""
|
416
657
|
Handle the retrieval of a single object.
|
417
658
|
|
@@ -421,38 +662,139 @@ class MojoModel:
|
|
421
662
|
Returns:
|
422
663
|
JsonResponse representing the object.
|
423
664
|
"""
|
424
|
-
graph = request.
|
425
|
-
serializer
|
665
|
+
graph = request.DATA.get("graph", graph)
|
666
|
+
# Use serializer manager for optimal performance
|
667
|
+
manager = get_serializer_manager()
|
668
|
+
serializer = manager.get_serializer(self, graph=graph)
|
426
669
|
return serializer.to_response(request)
|
427
670
|
|
671
|
+
def _set_field_change(self, key, old_value=None, new_value=None):
|
672
|
+
if not hasattr(self, "__changed_fields__"):
|
673
|
+
self.__changed_fields__ = objict.objict()
|
674
|
+
if old_value != new_value:
|
675
|
+
self.__changed_fields__[key] = old_value
|
676
|
+
|
677
|
+
def has_field_changed(self, key):
|
678
|
+
return key in self.__changed_fields__
|
679
|
+
|
680
|
+
def has_changed(self):
|
681
|
+
return bool(self.__changed_fields__)
|
682
|
+
|
683
|
+
def get_changes(self, data):
|
684
|
+
changes = {}
|
685
|
+
for key, value in self.__changed_fields__.items():
|
686
|
+
changes[key] = f"{value} -> {data.get(key, None)}"
|
687
|
+
return changes
|
688
|
+
|
428
689
|
def on_rest_save(self, request, data_dict):
|
429
690
|
"""
|
430
691
|
Create a model instance from a dictionary.
|
431
692
|
|
432
693
|
Args:
|
433
694
|
request: Django HTTP request object.
|
695
|
+
data_dict: Dictionary containing the data to save.
|
434
696
|
|
435
697
|
Returns:
|
436
698
|
None
|
437
699
|
"""
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
700
|
+
self.__changed_fields__ = objict.objict()
|
701
|
+
# Get fields that should not be saved
|
702
|
+
no_save_fields = self.get_rest_meta_prop("NO_SAVE_FIELDS", ["id", "pk", "created", "uuid"])
|
703
|
+
post_save_actions = self.get_rest_meta_prop("POST_SAVE_ACTIONS", ['action'])
|
704
|
+
post_save_data = {}
|
705
|
+
action_resp = None # an action may have a specific response
|
706
|
+
# Iterate through data_dict keys instead of model fields
|
707
|
+
for key, value in data_dict.items():
|
708
|
+
# Skip fields that shouldn't be saved
|
709
|
+
if key in no_save_fields:
|
710
|
+
continue
|
711
|
+
if key in post_save_actions:
|
712
|
+
post_save_data[key] = value
|
713
|
+
continue
|
714
|
+
self.on_rest_save_field(key, value, request)
|
715
|
+
|
716
|
+
created = self.pk is None
|
717
|
+
if created:
|
718
|
+
owner_field = self.get_rest_meta_prop("CREATED_BY_OWNER_FIELD", "user")
|
719
|
+
if request.user.is_authenticated and self.get_model_field(owner_field):
|
720
|
+
setattr(self, owner_field, request.user)
|
721
|
+
if request.group and self.get_model_field("group"):
|
722
|
+
if getattr(self, "group", None) is None:
|
723
|
+
self.group = request.group
|
724
|
+
else:
|
725
|
+
owner_field = self.get_rest_meta_prop("UPDATED_BY_OWNER_FIELD", "modified_by")
|
726
|
+
if request.user.is_authenticated and self.get_model_field(owner_field):
|
727
|
+
setattr(self, owner_field, request.user)
|
728
|
+
self.on_rest_pre_save(self.__changed_fields__, created)
|
729
|
+
if "files" in data_dict:
|
730
|
+
self.on_rest_save_files(data_dict["files"])
|
451
731
|
self.atomic_save()
|
732
|
+
self.on_rest_saved(self.__changed_fields__, created)
|
733
|
+
for key, value in post_save_data.items():
|
734
|
+
# post save fields can only be called via on_action_
|
735
|
+
handler = getattr(self, f'on_action_{key}', None)
|
736
|
+
if callable(handler):
|
737
|
+
action_resp = handler(value)
|
738
|
+
|
739
|
+
if self.get_rest_meta_prop("LOG_CHANGES", False) and self.has_changed():
|
740
|
+
self.log(kind="model:changed", log=self.get_changes(data_dict))
|
741
|
+
return action_resp
|
742
|
+
|
743
|
+
def on_rest_save_field(self, key, value, request):
|
744
|
+
# First check for custom setter method
|
745
|
+
set_field_method = getattr(self, f'set_{key}', None)
|
746
|
+
if callable(set_field_method):
|
747
|
+
old_value = getattr(self, key, None)
|
748
|
+
set_field_method(value)
|
749
|
+
new_value = getattr(self, key, None)
|
750
|
+
self._set_field_change(key, old_value, new_value)
|
751
|
+
return
|
752
|
+
|
753
|
+
# Check if this is a model field
|
754
|
+
field = self.get_model_field(key)
|
755
|
+
if field is None:
|
756
|
+
return
|
757
|
+
if field.get_internal_type() == "ForeignKey":
|
758
|
+
self.on_rest_save_related_field(field, value, request)
|
759
|
+
elif field.get_internal_type() == "JSONField":
|
760
|
+
self.on_rest_update_jsonfield(key, value)
|
761
|
+
else:
|
762
|
+
if field.get_internal_type() == "BooleanField":
|
763
|
+
if isinstance(value, str):
|
764
|
+
value = value.lower() in ("true", "1")
|
765
|
+
elif isinstance(value, int):
|
766
|
+
value = bool(value)
|
767
|
+
else:
|
768
|
+
value = bool(value)
|
769
|
+
elif value is not None:
|
770
|
+
if field.get_internal_type() in ["DateTimeField", "DateField"]:
|
771
|
+
if not isinstance(value, (datetime.datetime, datetime.date)):
|
772
|
+
value = dates.parse_datetime(value)
|
773
|
+
self._set_field_change(key, getattr(self, key), value)
|
774
|
+
setattr(self, key, value)
|
775
|
+
|
776
|
+
def on_rest_save_files(self, files):
|
777
|
+
for name, file in files.items():
|
778
|
+
self.on_rest_save_file(name, file)
|
779
|
+
|
780
|
+
def on_rest_save_file(self, name, file):
|
781
|
+
# Implement file saving logic here
|
782
|
+
self.debug("Finding file for field: %s", name)
|
783
|
+
field = self.get_model_field(name)
|
784
|
+
if field is None:
|
785
|
+
return
|
786
|
+
self.debug("Saving file for field: %s", name)
|
787
|
+
if field.related_model and hasattr(field.related_model, "create_from_file"):
|
788
|
+
self.debug("Found file for field: %s", name)
|
789
|
+
related_model = field.related_model
|
790
|
+
instance = related_model.create_from_file(file, name)
|
791
|
+
setattr(self, name, instance)
|
452
792
|
|
453
793
|
def on_rest_save_and_respond(self, request):
|
454
|
-
self.on_rest_save(request, request.DATA)
|
455
|
-
|
794
|
+
resp = self.on_rest_save(request, request.DATA)
|
795
|
+
if resp is None:
|
796
|
+
return self.on_rest_get(request)
|
797
|
+
return JsonResponse(resp)
|
456
798
|
|
457
799
|
def on_rest_save_related_field(self, field, field_value, request):
|
458
800
|
if isinstance(field_value, dict):
|
@@ -466,11 +808,23 @@ class MojoModel:
|
|
466
808
|
if field.related_model.rest_check_permission(request, ["SAVE_PERMS", "VIEW_PERMS"], related_instance):
|
467
809
|
related_instance.on_rest_save(request, field_value)
|
468
810
|
return
|
469
|
-
|
811
|
+
if hasattr(field.related_model, "on_rest_related_save"):
|
812
|
+
related_instance = getattr(self, field.name)
|
813
|
+
field.related_model.on_rest_related_save(self, field.name, field_value, related_instance)
|
814
|
+
elif isinstance(field_value, int) or (isinstance(field_value, str)):
|
815
|
+
# self.debug(f"Related Model: {field.related_model.__name__}, Field Value: {field_value}")
|
816
|
+
field_value = int(field_value)
|
817
|
+
if not bool(field_value):
|
818
|
+
# None, "", 0 will set it to None
|
819
|
+
logger.info(f"Setting field {field.name} to None")
|
820
|
+
setattr(self, field.name, None)
|
821
|
+
return
|
822
|
+
field_value = int(field_value)
|
823
|
+
if (self.pk == field_value):
|
824
|
+
self.debug("Skipping self-reference")
|
825
|
+
return
|
470
826
|
related_instance = field.related_model.objects.get(pk=field_value)
|
471
827
|
setattr(self, field.name, related_instance)
|
472
|
-
except field.related_model.DoesNotExist:
|
473
|
-
pass # Skip invalid related instances
|
474
828
|
|
475
829
|
def on_rest_update_jsonfield(self, field_name, field_value):
|
476
830
|
"""helper to update jsonfield by merge in changes"""
|
@@ -487,6 +841,18 @@ class MojoModel:
|
|
487
841
|
setattr(self, field_name, existing_value)
|
488
842
|
return existing_value
|
489
843
|
|
844
|
+
def on_rest_pre_delete(self):
|
845
|
+
"""
|
846
|
+
Handle the pre-deletion of an object.
|
847
|
+
|
848
|
+
Args:
|
849
|
+
request: Django HTTP request object.
|
850
|
+
|
851
|
+
Returns:
|
852
|
+
JsonResponse representing the result of the pre-delete operation.
|
853
|
+
"""
|
854
|
+
pass
|
855
|
+
|
490
856
|
def on_rest_delete(self, request):
|
491
857
|
"""
|
492
858
|
Handle the deletion of an object.
|
@@ -498,20 +864,23 @@ class MojoModel:
|
|
498
864
|
JsonResponse representing the result of the delete operation.
|
499
865
|
"""
|
500
866
|
try:
|
867
|
+
self.on_rest_pre_delete()
|
501
868
|
with transaction.atomic():
|
502
869
|
self.delete()
|
503
|
-
return JsonResponse({"status": "deleted"}, status=
|
870
|
+
return JsonResponse({"status": "deleted"}, status=200)
|
504
871
|
except Exception as e:
|
505
872
|
return JsonResponse({"error": str(e)}, status=400)
|
506
873
|
|
507
874
|
def to_dict(self, graph="default"):
|
508
|
-
serializer
|
509
|
-
|
875
|
+
# Use serializer manager for optimal performance
|
876
|
+
manager = get_serializer_manager()
|
877
|
+
return manager.serialize(self, graph=graph)
|
510
878
|
|
511
879
|
@classmethod
|
512
880
|
def queryset_to_dict(cls, qset, graph="default"):
|
513
|
-
serializer
|
514
|
-
|
881
|
+
# Use serializer manager for optimal performance
|
882
|
+
manager = get_serializer_manager()
|
883
|
+
return manager.serialize(qset, graph=graph, many=True)
|
515
884
|
|
516
885
|
def atomic_save(self):
|
517
886
|
"""
|
@@ -520,19 +889,79 @@ class MojoModel:
|
|
520
889
|
with transaction.atomic():
|
521
890
|
self.save()
|
522
891
|
|
523
|
-
def report_incident(self,
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
892
|
+
def report_incident(self, details, event_type="info", level=1, request=None, **context):
|
893
|
+
"""
|
894
|
+
Instance-level audit/event reporting. Automatically includes model+id.
|
895
|
+
"""
|
896
|
+
context = dict(context)
|
897
|
+
context.setdefault("model_name", self.__class__.get_model_string())
|
898
|
+
if hasattr(self, 'id') and self.id is not None:
|
899
|
+
context.setdefault("model_id", self.id)
|
900
|
+
self.__class__.class_report_incident(
|
901
|
+
details, event_type=event_type, level=level, request=request, **context
|
902
|
+
)
|
903
|
+
|
904
|
+
@classmethod
|
905
|
+
def class_report_incident_for_user(cls, details, event_type="info", level=1, request=None, **context):
|
906
|
+
"""
|
907
|
+
Class-level audit/event reporting for a specific user.
|
908
|
+
details: Human description.
|
909
|
+
event_type: Category/kind (e.g. "permission_denied", "security_alert").
|
910
|
+
level: Numeric severity.
|
911
|
+
request: Optional HTTP request or actor.
|
912
|
+
**context: Any additional context.
|
913
|
+
"""
|
914
|
+
if ACTIVE_REQUEST and ACTIVE_REQUEST.user.is_authenticated:
|
915
|
+
return request.user.report_incident(details, event_type=event_type, level=level, request=request, **context)
|
916
|
+
return cls.class_report_incident(details, event_type=event_type, level=level, request=request, **context)
|
528
917
|
|
529
|
-
|
918
|
+
@classmethod
|
919
|
+
def class_report_incident(cls, details, event_type="info", level=1, request=None, **context):
|
920
|
+
"""
|
921
|
+
Class-level audit/event reporting.
|
922
|
+
details: Human description.
|
923
|
+
event_type: Category/kind (e.g. "permission_denied", "security_alert").
|
924
|
+
level: Numeric severity.
|
925
|
+
request: Optional HTTP request or actor.
|
926
|
+
**context: Any additional context.
|
927
|
+
"""
|
928
|
+
from mojo.apps import incident
|
929
|
+
context = dict(context)
|
930
|
+
context.setdefault("model_name", cls.__name__)
|
931
|
+
incident.report_event(
|
932
|
+
details,
|
933
|
+
title=details[:80],
|
934
|
+
category=event_type,
|
935
|
+
level=level,
|
936
|
+
request=request,
|
937
|
+
**context
|
938
|
+
)
|
939
|
+
|
940
|
+
def log(self, log="", kind="model_log", level="info", **kwargs):
|
530
941
|
return self.class_logit(ACTIVE_REQUEST, log, kind, self.id, level, **kwargs)
|
531
942
|
|
532
943
|
def model_logit(self, request, log, kind="model_log", level="info", **kwargs):
|
533
944
|
return self.class_logit(request, log, kind, self.id, level, **kwargs)
|
534
945
|
|
946
|
+
@classmethod
|
947
|
+
def debug(cls, log, *args):
|
948
|
+
return logger.info(log, *args)
|
949
|
+
|
950
|
+
@classmethod
|
951
|
+
def get_model_string(cls):
|
952
|
+
return f"{ cls._meta.app_label.lower()}.{cls.__name__}"
|
953
|
+
|
535
954
|
@classmethod
|
536
955
|
def class_logit(cls, request, log, kind="cls_log", model_id=0, level="info", **kwargs):
|
537
956
|
from mojo.apps.logit.models import Log
|
538
|
-
return Log.logit(request, log, kind, cls.
|
957
|
+
return Log.logit(request, log, kind, cls.get_model_string(), model_id, level, **kwargs)
|
958
|
+
|
959
|
+
@classmethod
|
960
|
+
def get_model_field(cls, field_name):
|
961
|
+
"""
|
962
|
+
Get a model field by name.
|
963
|
+
"""
|
964
|
+
try:
|
965
|
+
return cls._meta.get_field(field_name)
|
966
|
+
except Exception:
|
967
|
+
return None
|