django-nativemojo 0.1.10__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.10.dist-info/LICENSE +19 -0
- django_nativemojo-0.1.10.dist-info/METADATA +96 -0
- django_nativemojo-0.1.10.dist-info/NOTICE +8 -0
- django_nativemojo-0.1.10.dist-info/RECORD +194 -0
- django_nativemojo-0.1.10.dist-info/WHEEL +4 -0
- mojo/__init__.py +3 -0
- mojo/apps/account/__init__.py +1 -0
- mojo/apps/account/admin.py +91 -0
- mojo/apps/account/apps.py +16 -0
- mojo/apps/account/migrations/0001_initial.py +77 -0
- mojo/apps/account/migrations/0002_user_is_email_verified_user_is_phone_verified.py +23 -0
- mojo/apps/account/migrations/0003_group_mojo_secrets_user_mojo_secrets.py +23 -0
- mojo/apps/account/migrations/__init__.py +0 -0
- mojo/apps/account/models/__init__.py +3 -0
- mojo/apps/account/models/group.py +98 -0
- mojo/apps/account/models/member.py +95 -0
- mojo/apps/account/models/pkey.py +18 -0
- mojo/apps/account/models/user.py +211 -0
- mojo/apps/account/rest/__init__.py +3 -0
- mojo/apps/account/rest/group.py +25 -0
- mojo/apps/account/rest/user.py +47 -0
- mojo/apps/account/utils/__init__.py +0 -0
- mojo/apps/account/utils/jwtoken.py +72 -0
- mojo/apps/account/utils/passkeys.py +54 -0
- mojo/apps/fileman/README.md +549 -0
- mojo/apps/fileman/__init__.py +0 -0
- mojo/apps/fileman/apps.py +15 -0
- mojo/apps/fileman/backends/__init__.py +117 -0
- mojo/apps/fileman/backends/base.py +319 -0
- mojo/apps/fileman/backends/filesystem.py +397 -0
- mojo/apps/fileman/backends/s3.py +398 -0
- mojo/apps/fileman/examples/configurations.py +378 -0
- mojo/apps/fileman/examples/usage_example.py +665 -0
- mojo/apps/fileman/management/__init__.py +1 -0
- mojo/apps/fileman/management/commands/__init__.py +1 -0
- mojo/apps/fileman/management/commands/cleanup_expired_uploads.py +222 -0
- mojo/apps/fileman/models/__init__.py +7 -0
- mojo/apps/fileman/models/file.py +292 -0
- mojo/apps/fileman/models/manager.py +227 -0
- mojo/apps/fileman/models/render.py +0 -0
- mojo/apps/fileman/rest/__init__ +0 -0
- mojo/apps/fileman/rest/__init__.py +23 -0
- mojo/apps/fileman/rest/fileman.py +13 -0
- mojo/apps/fileman/rest/upload.py +92 -0
- mojo/apps/fileman/utils/__init__.py +19 -0
- mojo/apps/fileman/utils/upload.py +616 -0
- mojo/apps/incident/__init__.py +1 -0
- mojo/apps/incident/handlers/__init__.py +3 -0
- mojo/apps/incident/handlers/event_handlers.py +142 -0
- mojo/apps/incident/migrations/0001_initial.py +83 -0
- mojo/apps/incident/migrations/0002_rename_bundle_ruleset_bundle_minutes_event_hostname_and_more.py +44 -0
- mojo/apps/incident/migrations/0003_alter_event_model_id.py +18 -0
- mojo/apps/incident/migrations/0004_alter_incident_model_id.py +18 -0
- mojo/apps/incident/migrations/__init__.py +0 -0
- mojo/apps/incident/models/__init__.py +3 -0
- mojo/apps/incident/models/event.py +135 -0
- mojo/apps/incident/models/incident.py +33 -0
- mojo/apps/incident/models/rule.py +247 -0
- mojo/apps/incident/parsers/__init__.py +0 -0
- mojo/apps/incident/parsers/ossec/__init__.py +1 -0
- mojo/apps/incident/parsers/ossec/core.py +82 -0
- mojo/apps/incident/parsers/ossec/parsed.py +23 -0
- mojo/apps/incident/parsers/ossec/rules.py +124 -0
- mojo/apps/incident/parsers/ossec/utils.py +169 -0
- mojo/apps/incident/reporter.py +42 -0
- mojo/apps/incident/rest/__init__.py +2 -0
- mojo/apps/incident/rest/event.py +23 -0
- mojo/apps/incident/rest/ossec.py +22 -0
- mojo/apps/logit/__init__.py +0 -0
- mojo/apps/logit/admin.py +37 -0
- mojo/apps/logit/migrations/0001_initial.py +32 -0
- mojo/apps/logit/migrations/0002_log_duid_log_payload_log_username.py +28 -0
- mojo/apps/logit/migrations/0003_log_level.py +18 -0
- mojo/apps/logit/migrations/__init__.py +0 -0
- mojo/apps/logit/models/__init__.py +1 -0
- mojo/apps/logit/models/log.py +57 -0
- mojo/apps/logit/rest.py +9 -0
- mojo/apps/metrics/README.md +79 -0
- mojo/apps/metrics/__init__.py +12 -0
- mojo/apps/metrics/redis_metrics.py +331 -0
- mojo/apps/metrics/rest/__init__.py +1 -0
- mojo/apps/metrics/rest/base.py +152 -0
- mojo/apps/metrics/rest/db.py +0 -0
- mojo/apps/metrics/utils.py +227 -0
- mojo/apps/notify/README.md +91 -0
- mojo/apps/notify/README_NOTIFICATIONS.md +566 -0
- mojo/apps/notify/__init__.py +0 -0
- mojo/apps/notify/admin.py +52 -0
- mojo/apps/notify/handlers/__init__.py +0 -0
- mojo/apps/notify/handlers/example_handlers.py +516 -0
- mojo/apps/notify/handlers/ses/__init__.py +25 -0
- mojo/apps/notify/handlers/ses/bounce.py +0 -0
- mojo/apps/notify/handlers/ses/complaint.py +25 -0
- mojo/apps/notify/handlers/ses/message.py +86 -0
- mojo/apps/notify/management/__init__.py +0 -0
- mojo/apps/notify/management/commands/__init__.py +1 -0
- mojo/apps/notify/management/commands/process_notifications.py +370 -0
- mojo/apps/notify/mod +0 -0
- mojo/apps/notify/models/__init__.py +12 -0
- mojo/apps/notify/models/account.py +128 -0
- mojo/apps/notify/models/attachment.py +24 -0
- mojo/apps/notify/models/bounce.py +68 -0
- mojo/apps/notify/models/complaint.py +40 -0
- mojo/apps/notify/models/inbox.py +113 -0
- mojo/apps/notify/models/inbox_message.py +173 -0
- mojo/apps/notify/models/outbox.py +129 -0
- mojo/apps/notify/models/outbox_message.py +288 -0
- mojo/apps/notify/models/template.py +30 -0
- mojo/apps/notify/providers/__init__.py +0 -0
- mojo/apps/notify/providers/aws.py +73 -0
- mojo/apps/notify/rest/__init__.py +0 -0
- mojo/apps/notify/rest/ses.py +0 -0
- mojo/apps/notify/utils/__init__.py +2 -0
- mojo/apps/notify/utils/notifications.py +404 -0
- mojo/apps/notify/utils/parsing.py +202 -0
- mojo/apps/notify/utils/render.py +144 -0
- mojo/apps/tasks/README.md +118 -0
- mojo/apps/tasks/__init__.py +11 -0
- mojo/apps/tasks/manager.py +489 -0
- mojo/apps/tasks/rest/__init__.py +2 -0
- mojo/apps/tasks/rest/hooks.py +0 -0
- mojo/apps/tasks/rest/tasks.py +62 -0
- mojo/apps/tasks/runner.py +174 -0
- mojo/apps/tasks/tq_handlers.py +14 -0
- mojo/decorators/__init__.py +3 -0
- mojo/decorators/auth.py +25 -0
- mojo/decorators/cron.py +31 -0
- mojo/decorators/http.py +132 -0
- mojo/decorators/validate.py +14 -0
- mojo/errors.py +88 -0
- mojo/helpers/__init__.py +0 -0
- mojo/helpers/aws/__init__.py +0 -0
- mojo/helpers/aws/client.py +8 -0
- mojo/helpers/aws/s3.py +268 -0
- mojo/helpers/aws/setup_email.py +0 -0
- mojo/helpers/cron.py +79 -0
- mojo/helpers/crypto/__init__.py +4 -0
- mojo/helpers/crypto/aes.py +60 -0
- mojo/helpers/crypto/hash.py +59 -0
- mojo/helpers/crypto/privpub/__init__.py +1 -0
- mojo/helpers/crypto/privpub/hybrid.py +97 -0
- mojo/helpers/crypto/privpub/rsa.py +104 -0
- mojo/helpers/crypto/sign.py +36 -0
- mojo/helpers/crypto/too.l.py +25 -0
- mojo/helpers/crypto/utils.py +26 -0
- mojo/helpers/daemon.py +94 -0
- mojo/helpers/dates.py +69 -0
- mojo/helpers/dns/__init__.py +0 -0
- mojo/helpers/dns/godaddy.py +62 -0
- mojo/helpers/filetypes.py +128 -0
- mojo/helpers/logit.py +310 -0
- mojo/helpers/modules.py +95 -0
- mojo/helpers/paths.py +63 -0
- mojo/helpers/redis.py +10 -0
- mojo/helpers/request.py +89 -0
- mojo/helpers/request_parser.py +269 -0
- mojo/helpers/response.py +14 -0
- mojo/helpers/settings.py +146 -0
- mojo/helpers/sysinfo.py +140 -0
- mojo/helpers/ua.py +0 -0
- mojo/middleware/__init__.py +0 -0
- mojo/middleware/auth.py +26 -0
- mojo/middleware/logging.py +55 -0
- mojo/middleware/mojo.py +21 -0
- mojo/migrations/0001_initial.py +32 -0
- mojo/migrations/__init__.py +0 -0
- mojo/models/__init__.py +2 -0
- mojo/models/meta.py +262 -0
- mojo/models/rest.py +538 -0
- mojo/models/secrets.py +59 -0
- mojo/rest/__init__.py +1 -0
- mojo/rest/info.py +26 -0
- mojo/serializers/__init__.py +0 -0
- mojo/serializers/models.py +165 -0
- mojo/serializers/openapi.py +188 -0
- mojo/urls.py +38 -0
- mojo/ws4redis/README.md +174 -0
- mojo/ws4redis/__init__.py +2 -0
- mojo/ws4redis/client.py +283 -0
- mojo/ws4redis/connection.py +327 -0
- mojo/ws4redis/exceptions.py +32 -0
- mojo/ws4redis/redis.py +183 -0
- mojo/ws4redis/servers/__init__.py +0 -0
- mojo/ws4redis/servers/base.py +86 -0
- mojo/ws4redis/servers/django.py +171 -0
- mojo/ws4redis/servers/uwsgi.py +63 -0
- mojo/ws4redis/settings.py +45 -0
- mojo/ws4redis/utf8validator.py +128 -0
- mojo/ws4redis/websocket.py +403 -0
- testit/__init__.py +0 -0
- testit/client.py +147 -0
- testit/faker.py +20 -0
- testit/helpers.py +198 -0
- testit/runner.py +262 -0
mojo/models/rest.py
ADDED
@@ -0,0 +1,538 @@
|
|
1
|
+
# from django.http import JsonResponse
|
2
|
+
from mojo.helpers.response import JsonResponse
|
3
|
+
from mojo.serializers.models import GraphSerializer
|
4
|
+
from mojo.helpers import modules
|
5
|
+
from mojo.helpers.settings import settings
|
6
|
+
from django.core.exceptions import ObjectDoesNotExist
|
7
|
+
from django.db import transaction, models as dm
|
8
|
+
import objict
|
9
|
+
from mojo.helpers import dates, logit
|
10
|
+
|
11
|
+
|
12
|
+
logger = logit.get_logger("debug", "debug.log")
|
13
|
+
ACTIVE_REQUEST = None
|
14
|
+
LOGGING_CLASS = None
|
15
|
+
MOJO_APP_STATUS_200_ON_ERROR = settings.MOJO_APP_STATUS_200_ON_ERROR
|
16
|
+
|
17
|
+
class MojoModel:
|
18
|
+
"""Base model class for REST operations with GraphSerializer integration."""
|
19
|
+
|
20
|
+
@property
|
21
|
+
def active_request(self):
|
22
|
+
"""Returns the active request being processed."""
|
23
|
+
return ACTIVE_REQUEST
|
24
|
+
|
25
|
+
@classmethod
|
26
|
+
def get_rest_meta_prop(cls, name, default=None):
|
27
|
+
"""
|
28
|
+
Retrieve a property from the RestMeta class if it exists.
|
29
|
+
|
30
|
+
Args:
|
31
|
+
name (str or list): Name of the property to retrieve.
|
32
|
+
default: Default value to return if the property does not exist.
|
33
|
+
|
34
|
+
Returns:
|
35
|
+
The value of the requested property or the default value.
|
36
|
+
"""
|
37
|
+
if getattr(cls, "RestMeta", None) is None:
|
38
|
+
return default
|
39
|
+
if isinstance(name, list):
|
40
|
+
for n in name:
|
41
|
+
res = getattr(cls.RestMeta, n, None)
|
42
|
+
if res is not None:
|
43
|
+
return res
|
44
|
+
return default
|
45
|
+
return getattr(cls.RestMeta, name, default)
|
46
|
+
|
47
|
+
@classmethod
|
48
|
+
def rest_error_response(cls, request, status=500, **kwargs):
|
49
|
+
"""
|
50
|
+
Create a JsonResponse for an error.
|
51
|
+
|
52
|
+
Args:
|
53
|
+
request: Django HTTP request object.
|
54
|
+
status (int): HTTP status code for the response.
|
55
|
+
kwargs: Additional data to include in the response.
|
56
|
+
|
57
|
+
Returns:
|
58
|
+
JsonResponse representing the error.
|
59
|
+
"""
|
60
|
+
payload = dict(kwargs)
|
61
|
+
payload["is_authenticated"] = request.user.is_authenticated
|
62
|
+
payload["status"] = False
|
63
|
+
if "code" not in payload:
|
64
|
+
payload["code"] = status
|
65
|
+
if MOJO_APP_STATUS_200_ON_ERROR:
|
66
|
+
status = 200
|
67
|
+
return JsonResponse(payload, status=status)
|
68
|
+
|
69
|
+
@classmethod
|
70
|
+
def on_rest_request(cls, request, pk=None):
|
71
|
+
"""
|
72
|
+
Handle REST requests dynamically based on HTTP method.
|
73
|
+
|
74
|
+
Args:
|
75
|
+
request: Django HTTP request object.
|
76
|
+
pk: Primary key of the object, if available.
|
77
|
+
|
78
|
+
Returns:
|
79
|
+
JsonResponse representing the result of the request.
|
80
|
+
"""
|
81
|
+
cls.__rest_field_names__ = [f.name for f in cls._meta.get_fields()]
|
82
|
+
if pk:
|
83
|
+
instance = cls.get_instance_or_404(pk)
|
84
|
+
if isinstance(instance, dict): # If it's a response, return early
|
85
|
+
return instance
|
86
|
+
|
87
|
+
if request.method == 'GET':
|
88
|
+
return cls.on_rest_handle_get(request, instance)
|
89
|
+
|
90
|
+
elif request.method in ['POST', 'PUT']:
|
91
|
+
return cls.on_rest_handle_save(request, instance)
|
92
|
+
|
93
|
+
elif request.method == 'DELETE':
|
94
|
+
return cls.on_rest_handle_delete(request, instance)
|
95
|
+
else:
|
96
|
+
return cls.on_handle_list_or_create(request)
|
97
|
+
|
98
|
+
return cls.rest_error_response(request, 500, error=f"{cls.__name__} not found")
|
99
|
+
|
100
|
+
@classmethod
|
101
|
+
def get_instance_or_404(cls, pk):
|
102
|
+
"""
|
103
|
+
Helper method to get an instance or return a 404 response.
|
104
|
+
|
105
|
+
Args:
|
106
|
+
pk: Primary key of the instance to retrieve.
|
107
|
+
|
108
|
+
Returns:
|
109
|
+
The requested instance or a JsonResponse for a 404 error.
|
110
|
+
"""
|
111
|
+
try:
|
112
|
+
return cls.objects.get(pk=pk)
|
113
|
+
except ObjectDoesNotExist:
|
114
|
+
return cls.rest_error_response(None, 404, error=f"{cls.__name__} not found")
|
115
|
+
|
116
|
+
@classmethod
|
117
|
+
def rest_check_permission(cls, request, permission_keys, instance=None):
|
118
|
+
"""
|
119
|
+
Check permissions for a given request.
|
120
|
+
|
121
|
+
Args:
|
122
|
+
request: Django HTTP request object.
|
123
|
+
permission_keys: Keys to check for permissions.
|
124
|
+
instance: Optional instance to check instance-specific permissions.
|
125
|
+
|
126
|
+
Returns:
|
127
|
+
True if the request has the necessary permissions, otherwise False.
|
128
|
+
"""
|
129
|
+
perms = cls.get_rest_meta_prop(permission_keys, [])
|
130
|
+
if perms is None or len(perms) == 0:
|
131
|
+
return True
|
132
|
+
if "all" not in perms:
|
133
|
+
if request.user is None or not request.user.is_authenticated:
|
134
|
+
return False
|
135
|
+
if instance is not None:
|
136
|
+
if hasattr(instance, "check_edit_permission"):
|
137
|
+
return instance.check_edit_permission(perms, request)
|
138
|
+
if "owner" in perms and getattr(instance, "user", None) is not None:
|
139
|
+
if instance.user.id == request.user.id:
|
140
|
+
return True
|
141
|
+
if request.group and hasattr(cls, "group"):
|
142
|
+
# lets check our group member permissions
|
143
|
+
# this will now force any queries to include the group
|
144
|
+
return request.group.member_has_permission(request.user, perms)
|
145
|
+
return request.user.has_permission(perms)
|
146
|
+
|
147
|
+
@classmethod
|
148
|
+
def on_rest_handle_get(cls, request, instance):
|
149
|
+
"""
|
150
|
+
Handle GET requests with permission checks.
|
151
|
+
|
152
|
+
Args:
|
153
|
+
request: Django HTTP request object.
|
154
|
+
instance: The instance to retrieve.
|
155
|
+
|
156
|
+
Returns:
|
157
|
+
JsonResponse representing the result of the GET request.
|
158
|
+
"""
|
159
|
+
if cls.rest_check_permission(request, "VIEW_PERMS", instance):
|
160
|
+
return instance.on_rest_get(request)
|
161
|
+
return cls.rest_error_response(request, 403, error=f"GET permission denied: {cls.__name__}")
|
162
|
+
|
163
|
+
@classmethod
|
164
|
+
def on_rest_handle_save(cls, request, instance):
|
165
|
+
"""
|
166
|
+
Handle POST and PUT requests with permission checks.
|
167
|
+
|
168
|
+
Args:
|
169
|
+
request: Django HTTP request object.
|
170
|
+
instance: The instance to save or update.
|
171
|
+
|
172
|
+
Returns:
|
173
|
+
JsonResponse representing the result of the save operation.
|
174
|
+
"""
|
175
|
+
if cls.rest_check_permission(request, ["SAVE_PERMS", "VIEW_PERMS"], instance):
|
176
|
+
return instance.on_rest_save_and_respond(request)
|
177
|
+
return cls.rest_error_response(request, 403, error=f"{request.method} permission denied: {cls.__name__}")
|
178
|
+
|
179
|
+
@classmethod
|
180
|
+
def on_rest_handle_delete(cls, request, instance):
|
181
|
+
"""
|
182
|
+
Handle DELETE requests with permission checks.
|
183
|
+
|
184
|
+
Args:
|
185
|
+
request: Django HTTP request object.
|
186
|
+
instance: The instance to delete.
|
187
|
+
|
188
|
+
Returns:
|
189
|
+
JsonResponse representing the result of the delete operation.
|
190
|
+
"""
|
191
|
+
if not cls.get_rest_meta_prop("CAN_DELETE", False):
|
192
|
+
return cls.rest_error_response(request, 403, error=f"DELETE not allowed: {cls.__name__}")
|
193
|
+
|
194
|
+
if cls.rest_check_permission(request, ["DELETE_PERMS", "SAVE_PERMS", "VIEW_PERMS"], instance):
|
195
|
+
return instance.on_rest_delete(request)
|
196
|
+
return cls.rest_error_response(request, 403, error=f"DELETE permission denied: {cls.__name__}")
|
197
|
+
|
198
|
+
@classmethod
|
199
|
+
def on_rest_handle_list(cls, request):
|
200
|
+
"""
|
201
|
+
Handle GET requests for listing resources with permission checks.
|
202
|
+
|
203
|
+
Args:
|
204
|
+
request: Django HTTP request object.
|
205
|
+
|
206
|
+
Returns:
|
207
|
+
JsonResponse representing the list of resources.
|
208
|
+
"""
|
209
|
+
if cls.rest_check_permission(request, "VIEW_PERMS"):
|
210
|
+
return cls.on_rest_list(request)
|
211
|
+
return cls.rest_error_response(request, 403, error=f"GET permission denied: {cls.__name__}")
|
212
|
+
|
213
|
+
@classmethod
|
214
|
+
def on_rest_handle_create(cls, request):
|
215
|
+
"""
|
216
|
+
Handle POST and PUT requests for creating resources with permission checks.
|
217
|
+
|
218
|
+
Args:
|
219
|
+
request: Django HTTP request object.
|
220
|
+
|
221
|
+
Returns:
|
222
|
+
JsonResponse representing the result of the create operation.
|
223
|
+
"""
|
224
|
+
if cls.rest_check_permission(request, ["SAVE_PERMS", "VIEW_PERMS"]):
|
225
|
+
instance = cls()
|
226
|
+
return instance.on_rest_save_and_respond(request)
|
227
|
+
return cls.rest_error_response(request, 403, error=f"CREATE permission denied: {cls.__name__}")
|
228
|
+
|
229
|
+
@classmethod
|
230
|
+
def on_handle_list_or_create(cls, request):
|
231
|
+
"""
|
232
|
+
Handle listing (GET without pk) and creating (POST/PUT without pk) operations.
|
233
|
+
|
234
|
+
Args:
|
235
|
+
request: Django HTTP request object.
|
236
|
+
|
237
|
+
Returns:
|
238
|
+
JsonResponse representing the result of the operation.
|
239
|
+
"""
|
240
|
+
if request.method == 'GET':
|
241
|
+
return cls.on_rest_handle_list(request)
|
242
|
+
elif request.method in ['POST', 'PUT']:
|
243
|
+
return cls.on_rest_handle_create(request)
|
244
|
+
|
245
|
+
@classmethod
|
246
|
+
def on_rest_list(cls, request, queryset=None):
|
247
|
+
"""
|
248
|
+
List objects with filtering, sorting, and pagination.
|
249
|
+
|
250
|
+
Args:
|
251
|
+
request: Django HTTP request object.
|
252
|
+
queryset: Optional initial queryset to use.
|
253
|
+
|
254
|
+
Returns:
|
255
|
+
JsonResponse representing the paginated and serialized list of objects.
|
256
|
+
"""
|
257
|
+
if queryset is None:
|
258
|
+
queryset = cls.objects.all()
|
259
|
+
if request.group is not None and hasattr(cls, "group"):
|
260
|
+
if "group" in request.DATA:
|
261
|
+
del request.DATA["group"]
|
262
|
+
queryset = queryset.filter(group=request.group)
|
263
|
+
queryset = cls.on_rest_list_filter(request, queryset)
|
264
|
+
queryset = cls.on_rest_list_date_range_filter(request, queryset)
|
265
|
+
queryset = cls.on_rest_list_sort(request, queryset)
|
266
|
+
return cls.on_rest_list_response(request, queryset)
|
267
|
+
|
268
|
+
@classmethod
|
269
|
+
def on_rest_list_response(cls, request, queryset):
|
270
|
+
# Implement pagination
|
271
|
+
page_size = request.DATA.get_typed("size", 10, int)
|
272
|
+
page_start = request.DATA.get_typed("start", 0, int)
|
273
|
+
page_end = page_start+page_size
|
274
|
+
paged_queryset = queryset[page_start:page_end]
|
275
|
+
graph = request.DATA.get("graph", "list")
|
276
|
+
serializer = GraphSerializer(paged_queryset, graph=graph, many=True)
|
277
|
+
return serializer.to_response(request, count=queryset.count(), start=page_start, size=page_size)
|
278
|
+
|
279
|
+
@classmethod
|
280
|
+
def on_rest_list_date_range_filter(cls, request, queryset):
|
281
|
+
"""
|
282
|
+
Filter queryset based on a date range provided in the request.
|
283
|
+
|
284
|
+
Args:
|
285
|
+
request: Django HTTP request object.
|
286
|
+
queryset: The queryset to filter.
|
287
|
+
|
288
|
+
Returns:
|
289
|
+
The filtered queryset.
|
290
|
+
"""
|
291
|
+
dr_field = request.DATA.get("dr_field", "created")
|
292
|
+
dr_start = request.DATA.get("dr_start")
|
293
|
+
dr_end = request.DATA.get("dr_end")
|
294
|
+
|
295
|
+
if dr_start:
|
296
|
+
dr_start = dates.parse_datetime(dr_start)
|
297
|
+
if request.group:
|
298
|
+
dr_start = request.group.get_local_time(dr_start)
|
299
|
+
queryset = queryset.filter(**{f"{dr_field}__gte": dr_start})
|
300
|
+
|
301
|
+
if dr_end:
|
302
|
+
dr_end = dates.parse_datetime(dr_end)
|
303
|
+
if request.group:
|
304
|
+
dr_end = request.group.get_local_time(dr_end)
|
305
|
+
queryset = queryset.filter(**{f"{dr_field}__lte": dr_end})
|
306
|
+
return queryset
|
307
|
+
|
308
|
+
@classmethod
|
309
|
+
def on_rest_list_filter(cls, request, queryset):
|
310
|
+
"""
|
311
|
+
Apply filtering logic based on request parameters, including foreign key fields.
|
312
|
+
|
313
|
+
Args:
|
314
|
+
request: Django HTTP request object.
|
315
|
+
queryset: The queryset to filter.
|
316
|
+
|
317
|
+
Returns:
|
318
|
+
The filtered queryset.
|
319
|
+
"""
|
320
|
+
filters = {}
|
321
|
+
for key, value in request.GET.items():
|
322
|
+
# Split key to check for foreign key relationships
|
323
|
+
key_parts = key.split('__')
|
324
|
+
field_name = key_parts[0]
|
325
|
+
if hasattr(cls, field_name):
|
326
|
+
filters[key] = value
|
327
|
+
elif field_name in cls.__rest_field_names__ and cls._meta.get_field(field_name).is_relation:
|
328
|
+
filters[key] = value
|
329
|
+
# logger.info("filters", filters)
|
330
|
+
queryset = cls.on_rest_list_search(request, queryset)
|
331
|
+
return queryset.filter(**filters)
|
332
|
+
|
333
|
+
@classmethod
|
334
|
+
def on_rest_list_search(cls, request, queryset):
|
335
|
+
"""
|
336
|
+
Search queryset based on 'q' param in the request for fields defined in 'SEARCH_FIELDS'.
|
337
|
+
|
338
|
+
Args:
|
339
|
+
request: Django HTTP request object.
|
340
|
+
queryset: The queryset to search.
|
341
|
+
|
342
|
+
Returns:
|
343
|
+
The filtered queryset based on the search criteria.
|
344
|
+
"""
|
345
|
+
search_query = request.GET.get('q', None)
|
346
|
+
if not search_query:
|
347
|
+
return queryset
|
348
|
+
|
349
|
+
search_fields = getattr(cls.RestMeta, 'SEARCH_FIELDS', None)
|
350
|
+
if search_fields is None:
|
351
|
+
search_fields = [
|
352
|
+
field.name for field in cls._meta.get_fields()
|
353
|
+
if field.get_internal_type() in ["CharField", "TextField"]
|
354
|
+
]
|
355
|
+
|
356
|
+
query_filters = dm.Q()
|
357
|
+
for field in search_fields:
|
358
|
+
query_filters |= dm.Q(**{f"{field}__icontains": search_query})
|
359
|
+
|
360
|
+
logger.info("search_filters", query_filters)
|
361
|
+
return queryset.filter(query_filters)
|
362
|
+
|
363
|
+
@classmethod
|
364
|
+
def on_rest_list_sort(cls, request, queryset):
|
365
|
+
"""
|
366
|
+
Apply sorting to the queryset.
|
367
|
+
|
368
|
+
Args:
|
369
|
+
request: Django HTTP request object.
|
370
|
+
queryset: The queryset to sort.
|
371
|
+
|
372
|
+
Returns:
|
373
|
+
The sorted queryset.
|
374
|
+
"""
|
375
|
+
sort_field = request.DATA.pop("sort", "-id")
|
376
|
+
if sort_field.lstrip('-') in cls.__rest_field_names__:
|
377
|
+
return queryset.order_by(sort_field)
|
378
|
+
return queryset
|
379
|
+
|
380
|
+
@classmethod
|
381
|
+
def return_rest_response(cls, data, flat=False):
|
382
|
+
"""
|
383
|
+
Return the passed in data as a JSONResponse with root values of status=True and data=.
|
384
|
+
|
385
|
+
Args:
|
386
|
+
data: Data to include in the response.
|
387
|
+
|
388
|
+
Returns:
|
389
|
+
JsonResponse representing the data.
|
390
|
+
"""
|
391
|
+
if flat:
|
392
|
+
response_payload = data
|
393
|
+
else:
|
394
|
+
response_payload = {
|
395
|
+
"status": True,
|
396
|
+
"data": data
|
397
|
+
}
|
398
|
+
return JsonResponse(response_payload)
|
399
|
+
|
400
|
+
@classmethod
|
401
|
+
def on_rest_create(cls, request):
|
402
|
+
"""
|
403
|
+
Handle the creation of an object.
|
404
|
+
|
405
|
+
Args:
|
406
|
+
request: Django HTTP request object.
|
407
|
+
|
408
|
+
Returns:
|
409
|
+
JsonResponse representing the newly created object.
|
410
|
+
"""
|
411
|
+
instance = cls()
|
412
|
+
return instance.on_rest_save_and_respond(request)
|
413
|
+
|
414
|
+
def on_rest_get(self, request):
|
415
|
+
"""
|
416
|
+
Handle the retrieval of a single object.
|
417
|
+
|
418
|
+
Args:
|
419
|
+
request: Django HTTP request object.
|
420
|
+
|
421
|
+
Returns:
|
422
|
+
JsonResponse representing the object.
|
423
|
+
"""
|
424
|
+
graph = request.GET.get("graph", "default")
|
425
|
+
serializer = GraphSerializer(self, graph=graph)
|
426
|
+
return serializer.to_response(request)
|
427
|
+
|
428
|
+
def on_rest_save(self, request, data_dict):
|
429
|
+
"""
|
430
|
+
Create a model instance from a dictionary.
|
431
|
+
|
432
|
+
Args:
|
433
|
+
request: Django HTTP request object.
|
434
|
+
|
435
|
+
Returns:
|
436
|
+
None
|
437
|
+
"""
|
438
|
+
for field in self._meta.get_fields():
|
439
|
+
field_name = field.name
|
440
|
+
if field_name in data_dict:
|
441
|
+
field_value = data_dict[field_name]
|
442
|
+
set_field_method = getattr(self, f'set_{field_name}', None)
|
443
|
+
if callable(set_field_method):
|
444
|
+
set_field_method(field_value, request)
|
445
|
+
elif field.is_relation and hasattr(field, 'related_model'):
|
446
|
+
self.on_rest_save_related_field(field, field_value, request)
|
447
|
+
elif field.get_internal_type() == "JSONField":
|
448
|
+
self.on_rest_update_jsonfield(field_name, field_value)
|
449
|
+
else:
|
450
|
+
setattr(self, field_name, field_value)
|
451
|
+
self.atomic_save()
|
452
|
+
|
453
|
+
def on_rest_save_and_respond(self, request):
|
454
|
+
self.on_rest_save(request, request.DATA)
|
455
|
+
return self.on_rest_get(request)
|
456
|
+
|
457
|
+
def on_rest_save_related_field(self, field, field_value, request):
|
458
|
+
if isinstance(field_value, dict):
|
459
|
+
# we want to check if we have an existing field and if so we will update it after security
|
460
|
+
related_instance = getattr(self, field.name)
|
461
|
+
if related_instance is None:
|
462
|
+
# skip None fields for now
|
463
|
+
# FUTURE look at creating a new instance
|
464
|
+
return
|
465
|
+
if hasattr(field.related_model, "rest_check_permission"):
|
466
|
+
if field.related_model.rest_check_permission(request, ["SAVE_PERMS", "VIEW_PERMS"], related_instance):
|
467
|
+
related_instance.on_rest_save(request, field_value)
|
468
|
+
return
|
469
|
+
try:
|
470
|
+
related_instance = field.related_model.objects.get(pk=field_value)
|
471
|
+
setattr(self, field.name, related_instance)
|
472
|
+
except field.related_model.DoesNotExist:
|
473
|
+
pass # Skip invalid related instances
|
474
|
+
|
475
|
+
def on_rest_update_jsonfield(self, field_name, field_value):
|
476
|
+
"""helper to update jsonfield by merge in changes"""
|
477
|
+
existing_value = getattr(self, field_name, {})
|
478
|
+
# logger.info("JSONField", existing_value, "New Value", field_value)
|
479
|
+
if isinstance(field_value, dict) and isinstance(existing_value, dict):
|
480
|
+
merged_value = objict.merge_dicts(existing_value, field_value)
|
481
|
+
setattr(self, field_name, merged_value)
|
482
|
+
|
483
|
+
def jsonfield_as_objict(self, field_name):
|
484
|
+
existing_value = getattr(self, field_name, {})
|
485
|
+
if not isinstance(existing_value, objict.objict):
|
486
|
+
existing_value = objict.objict.fromdict(existing_value)
|
487
|
+
setattr(self, field_name, existing_value)
|
488
|
+
return existing_value
|
489
|
+
|
490
|
+
def on_rest_delete(self, request):
|
491
|
+
"""
|
492
|
+
Handle the deletion of an object.
|
493
|
+
|
494
|
+
Args:
|
495
|
+
request: Django HTTP request object.
|
496
|
+
|
497
|
+
Returns:
|
498
|
+
JsonResponse representing the result of the delete operation.
|
499
|
+
"""
|
500
|
+
try:
|
501
|
+
with transaction.atomic():
|
502
|
+
self.delete()
|
503
|
+
return JsonResponse({"status": "deleted"}, status=204)
|
504
|
+
except Exception as e:
|
505
|
+
return JsonResponse({"error": str(e)}, status=400)
|
506
|
+
|
507
|
+
def to_dict(self, graph="default"):
|
508
|
+
serializer = GraphSerializer(self, graph=graph)
|
509
|
+
return serializer.serialize()
|
510
|
+
|
511
|
+
@classmethod
|
512
|
+
def queryset_to_dict(cls, qset, graph="default"):
|
513
|
+
serializer = GraphSerializer(qset, graph=graph)
|
514
|
+
return serializer.serialize()
|
515
|
+
|
516
|
+
def atomic_save(self):
|
517
|
+
"""
|
518
|
+
Save the object atomically to the database.
|
519
|
+
"""
|
520
|
+
with transaction.atomic():
|
521
|
+
self.save()
|
522
|
+
|
523
|
+
def report_incident(self, description, kind="error", level="critical", **kwargs):
|
524
|
+
self.model_logit(ACTIVE_REQUEST, description, kind=kind, level=level)
|
525
|
+
Event = modules.get_model("incidents", "Event")
|
526
|
+
if Event is None:
|
527
|
+
Event.report(description, kind, **kwargs)
|
528
|
+
|
529
|
+
def log(self, log, kind="model_log", level="info", **kwargs):
|
530
|
+
return self.class_logit(ACTIVE_REQUEST, log, kind, self.id, level, **kwargs)
|
531
|
+
|
532
|
+
def model_logit(self, request, log, kind="model_log", level="info", **kwargs):
|
533
|
+
return self.class_logit(request, log, kind, self.id, level, **kwargs)
|
534
|
+
|
535
|
+
@classmethod
|
536
|
+
def class_logit(cls, request, log, kind="cls_log", model_id=0, level="info", **kwargs):
|
537
|
+
from mojo.apps.logit.models import Log
|
538
|
+
return Log.logit(request, log, kind, cls.__name__, model_id, level, **kwargs)
|
mojo/models/secrets.py
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
from django.db import models
|
2
|
+
from mojo.helpers import crypto
|
3
|
+
from mojo.helpers.settings import settings
|
4
|
+
from objict import objict, merge_dicts
|
5
|
+
|
6
|
+
|
7
|
+
class MojoSecrets(models.Model):
|
8
|
+
"""Base model class for adding secrets to a model"""
|
9
|
+
class Meta:
|
10
|
+
abstract = True
|
11
|
+
|
12
|
+
mojo_secrets = models.TextField(blank=True, null=True, default=None)
|
13
|
+
_exposed_secrets = None
|
14
|
+
|
15
|
+
def set_secrets(self, value):
|
16
|
+
self._exposed_secrets = merge_dicts(self.secrets, value)
|
17
|
+
|
18
|
+
def set_secret(self, key, value):
|
19
|
+
self.secrets[key] = value
|
20
|
+
|
21
|
+
def get_secret(self, key, default=None):
|
22
|
+
return self.secrets.get(key, default)
|
23
|
+
|
24
|
+
def clear_secrets(self):
|
25
|
+
self.mojo_secrets = None
|
26
|
+
self._exposed_secrets = objict()
|
27
|
+
|
28
|
+
@property
|
29
|
+
def secrets(self):
|
30
|
+
if self._exposed_secrets is not None:
|
31
|
+
return self._exposed_secrets
|
32
|
+
if self.mojo_secrets is None:
|
33
|
+
self._exposed_secrets = objict()
|
34
|
+
return self._exposed_secrets
|
35
|
+
if self._exposed_secrets is None:
|
36
|
+
self._exposed_secrets = crypto.decrypt(self.mojo_secrets, self._get_secrets_password(), False)
|
37
|
+
return self._exposed_secrets
|
38
|
+
|
39
|
+
def _get_secrets_password(self):
|
40
|
+
# override this to create your own secrets password
|
41
|
+
salt = f"{self.pk}{self.__class__.__name__}"
|
42
|
+
if hasattr(self, 'created'):
|
43
|
+
return f"{self.created}{salt}"
|
44
|
+
return salt
|
45
|
+
|
46
|
+
def save_secrets(self):
|
47
|
+
if self._exposed_secrets:
|
48
|
+
self.mojo_secrets = crypto.encrypt( self._exposed_secrets, self._get_secrets_password())
|
49
|
+
else:
|
50
|
+
self.mojo_secrets = None
|
51
|
+
|
52
|
+
def save(self, *args, **kwargs):
|
53
|
+
if self.pk is not None:
|
54
|
+
self.save_secrets()
|
55
|
+
super().save(*args, **kwargs)
|
56
|
+
else:
|
57
|
+
super().save(*args, **kwargs)
|
58
|
+
self.save_secrets()
|
59
|
+
super().save()
|
mojo/rest/__init__.py
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
from .info import *
|
mojo/rest/info.py
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
from mojo import decorators as md
|
2
|
+
# from django.http import JsonResponse
|
3
|
+
from mojo.helpers.response import JsonResponse
|
4
|
+
from mojo.helpers.settings import settings
|
5
|
+
import mojo
|
6
|
+
import django
|
7
|
+
|
8
|
+
@md.GET('version')
|
9
|
+
def rest_version(request):
|
10
|
+
return JsonResponse(dict(status=True, version=settings.VERSION, ip=request.ip))
|
11
|
+
|
12
|
+
|
13
|
+
@md.GET('versions')
|
14
|
+
def rest_versions(request):
|
15
|
+
import sys
|
16
|
+
return JsonResponse(dict(status=True, version={
|
17
|
+
"mojo": mojo.__version__,
|
18
|
+
"project": settings.VERSION,
|
19
|
+
"django": django.__version__,
|
20
|
+
"python": sys.version.split(' ')[0]
|
21
|
+
}))
|
22
|
+
|
23
|
+
|
24
|
+
@md.GET('myip')
|
25
|
+
def rest_my_ip(request):
|
26
|
+
return JsonResponse(dict(status=True, ip=request.ip))
|
File without changes
|