django-restit 4.0.12__py3-none-any.whl → 4.1.1__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_restit-4.0.12.dist-info → django_restit-4.1.1.dist-info}/METADATA +1 -1
- {django_restit-4.0.12.dist-info → django_restit-4.1.1.dist-info}/RECORD +20 -16
- metrics/rpc.py +1 -1
- pushit/models.py +1 -1
- rest/__init__.py +1 -1
- rest/crypto/util.py +20 -0
- rest/decorators.py +9 -9
- rest/errors.py +20 -0
- rest/helpers.py +24 -0
- rest/models/__init__.py +3 -0
- rest/{models.py → models/base.py} +79 -454
- rest/models/cacher.py +19 -0
- rest/models/metadata.py +302 -0
- rest/serializers/collection.py +7 -1
- rest/serializers/legacy.py +3 -3
- rest/serializers/model.py +23 -0
- ws4redis/connection.py +3 -0
- ws4redis/redis.py +1 -1
- {django_restit-4.0.12.dist-info → django_restit-4.1.1.dist-info}/LICENSE.md +0 -0
- {django_restit-4.0.12.dist-info → django_restit-4.1.1.dist-info}/WHEEL +0 -0
@@ -1,381 +1,28 @@
|
|
1
|
-
import os
|
2
|
-
from django.conf import settings
|
3
|
-
from django.core.exceptions import FieldDoesNotExist
|
4
|
-
from hashids import Hashids
|
5
|
-
import hashlib
|
6
|
-
import string
|
7
|
-
from io import StringIO
|
8
|
-
|
9
1
|
from datetime import datetime, date, timedelta
|
10
2
|
from decimal import Decimal
|
11
|
-
|
3
|
+
from objict import objict
|
4
|
+
import importlib
|
12
5
|
|
13
|
-
from django.db import models
|
14
|
-
from django.apps import apps
|
6
|
+
from django.db import models as dm
|
15
7
|
from django.db.transaction import atomic
|
16
|
-
|
17
|
-
|
18
|
-
from django.http import Http404
|
19
|
-
from django.core.exceptions import ValidationError
|
20
|
-
|
21
|
-
import threading
|
8
|
+
from django.apps import apps
|
22
9
|
|
23
|
-
from rest import helpers as
|
24
|
-
from rest
|
25
|
-
from rest import search
|
10
|
+
from rest import helpers as rh
|
11
|
+
from rest import errors as re
|
26
12
|
from rest.encryption import ENCRYPTER, DECRYPTER
|
13
|
+
from rest import crypto
|
14
|
+
from rest import settings
|
15
|
+
from rest import search
|
16
|
+
from .metadata import MetaDataBase, MetaDataModel
|
27
17
|
|
28
|
-
|
18
|
+
DB_ROUTING_MAPS = settings.get("DB_ROUTING_MAPS", {})
|
19
|
+
TWO_DECIMAL_PLACES = Decimal(10) ** -2
|
29
20
|
|
30
|
-
GRAPH_HELPERS =
|
21
|
+
GRAPH_HELPERS = objict()
|
31
22
|
GRAPH_HELPERS.restGet = None
|
32
23
|
GRAPH_HELPERS.get_request = None
|
33
24
|
GRAPH_HELPERS.views = None
|
34
25
|
|
35
|
-
NOTFOUND = "311!@#$%^&*"
|
36
|
-
|
37
|
-
DB_ROUTING_MAPS = getattr(settings, "DB_ROUTING_MAPS", {})
|
38
|
-
|
39
|
-
|
40
|
-
class RestError(Exception):
|
41
|
-
def __init__(self, reason, code=None):
|
42
|
-
self.reason = reason
|
43
|
-
self.code = code
|
44
|
-
|
45
|
-
def __repr__(self):
|
46
|
-
return self.reason
|
47
|
-
|
48
|
-
|
49
|
-
class PermissionDeniedException(RestError):
|
50
|
-
def __init__(self, reason="permission denied", code=401):
|
51
|
-
self.reason = reason
|
52
|
-
self.code = code
|
53
|
-
|
54
|
-
|
55
|
-
class RestValidationError(RestError):
|
56
|
-
def __init__(self, reason="rest data is not valid", code=123):
|
57
|
-
self.reason = reason
|
58
|
-
self.code = code
|
59
|
-
|
60
|
-
|
61
|
-
def requestHasPerms(request, perms, group=None):
|
62
|
-
# third party auth models
|
63
|
-
if request.auth_model is not None:
|
64
|
-
if request.auth_model.hasPerm(perms):
|
65
|
-
return True, None, None
|
66
|
-
|
67
|
-
if not request.user.is_authenticated:
|
68
|
-
return False, "auth required", 401
|
69
|
-
|
70
|
-
if request.member.hasPerm(perms):
|
71
|
-
return True, None, None
|
72
|
-
if group is None and hasattr(request, "group"):
|
73
|
-
group = request.group
|
74
|
-
ms_perms = None
|
75
|
-
if group:
|
76
|
-
ms = request.member.getMembershipFor(group)
|
77
|
-
if ms and ms.hasPerm(perms):
|
78
|
-
return True, None, None
|
79
|
-
if ms:
|
80
|
-
ms_perms = ms.getPermissions()
|
81
|
-
|
82
|
-
# rest_helpers.log_error(F"PERMISSION DENIED for {request.member.username}, {perms}", str(group), ms_perms)
|
83
|
-
return False, "permission denied", 402
|
84
|
-
|
85
|
-
|
86
|
-
class MetaDataBase(models.Model):
|
87
|
-
class Meta:
|
88
|
-
abstract = True
|
89
|
-
|
90
|
-
category = models.CharField(db_index=True, max_length=32, default=None, null=True, blank=True)
|
91
|
-
|
92
|
-
key = models.CharField(db_index=True, max_length=80)
|
93
|
-
|
94
|
-
value_format = models.CharField(max_length=16)
|
95
|
-
value = models.TextField()
|
96
|
-
|
97
|
-
int_value = models.IntegerField(default=None, null=True, blank=True)
|
98
|
-
float_value = models.IntegerField(default=None, null=True, blank=True)
|
99
|
-
|
100
|
-
def setValue(self, value):
|
101
|
-
self.value = "{}".format(value)
|
102
|
-
if type(value) is int or self.value in ["0", "1"]:
|
103
|
-
if type(value) is int and value > 2147483647:
|
104
|
-
self.value_format = "S"
|
105
|
-
return
|
106
|
-
self.value_format = "I"
|
107
|
-
self.int_value = value
|
108
|
-
elif type(value) is float:
|
109
|
-
self.value_format = "F"
|
110
|
-
self.float_value = value
|
111
|
-
elif isinstance(value, list):
|
112
|
-
self.value_format = "L"
|
113
|
-
# self.value = ",".join(value)
|
114
|
-
elif isinstance(value, dict):
|
115
|
-
self.value_format = "O"
|
116
|
-
elif type(value) in [str, str] and len(value) < 9 and value.isdigit():
|
117
|
-
self.value_format = "I"
|
118
|
-
self.int_value = value
|
119
|
-
elif value in ["True", "true", "False", "false"]:
|
120
|
-
self.value_format = "I"
|
121
|
-
if value in ["True", "true"]:
|
122
|
-
self.int_value = 1
|
123
|
-
else:
|
124
|
-
self.int_value = 0
|
125
|
-
elif isinstance(value, bool):
|
126
|
-
self.value_format = "I"
|
127
|
-
if value:
|
128
|
-
self.int_value = 1
|
129
|
-
else:
|
130
|
-
self.int_value = 0
|
131
|
-
else:
|
132
|
-
self.value_format = "S"
|
133
|
-
|
134
|
-
def getStrictType(self, field_type):
|
135
|
-
if type(self.value) is field_type:
|
136
|
-
return self.value
|
137
|
-
if field_type in [int, str, float, str]:
|
138
|
-
return field_type(self.value)
|
139
|
-
elif field_type is bool:
|
140
|
-
if self.value_format == 'I':
|
141
|
-
return self.int_value != 0
|
142
|
-
return self.value in [True, 1, '1', 'y', 'Y', 'Yes', 'true', 'True']
|
143
|
-
elif field_type in [date, datetime]:
|
144
|
-
return rest_helpers.parseDate(self.value)
|
145
|
-
return self.value
|
146
|
-
|
147
|
-
def getValue(self, field_type=None):
|
148
|
-
if field_type:
|
149
|
-
return self.getStrictType(field_type)
|
150
|
-
elif self.value_format == 'I':
|
151
|
-
return self.int_value
|
152
|
-
elif self.value_format == 'F':
|
153
|
-
return self.float_value
|
154
|
-
elif self.value_format in ["L", "O"] and self.value:
|
155
|
-
try:
|
156
|
-
return eval(self.value)
|
157
|
-
except Exception:
|
158
|
-
pass
|
159
|
-
return self.value
|
160
|
-
|
161
|
-
def __unicode__(self):
|
162
|
-
if self.category:
|
163
|
-
return "{}.{}={}".format(self.category, self.key, self.value)
|
164
|
-
return "{}={}".format(self.key, self.value)
|
165
|
-
|
166
|
-
def __str__(self):
|
167
|
-
if self.category:
|
168
|
-
return "{}.{}={}".format(self.category, self.key, self.value)
|
169
|
-
return "{}={}".format(self.key, self.value)
|
170
|
-
|
171
|
-
|
172
|
-
class MetaDataModel(object):
|
173
|
-
def set_metadata(self, request, values=None):
|
174
|
-
# this may get called before the model is saved
|
175
|
-
if not self.id:
|
176
|
-
self.save()
|
177
|
-
|
178
|
-
if values is None:
|
179
|
-
values = request
|
180
|
-
request = None
|
181
|
-
|
182
|
-
if not isinstance(values, dict):
|
183
|
-
raise Exception("invalid metadata: {}".format(values))
|
184
|
-
|
185
|
-
for key, value in list(values.items()):
|
186
|
-
cat = None
|
187
|
-
if "." in key:
|
188
|
-
cat, key = key.split('.')
|
189
|
-
self.setProperty(key, value, cat, request=request)
|
190
|
-
|
191
|
-
def metadata(self):
|
192
|
-
return self.getProperties()
|
193
|
-
|
194
|
-
def removeProperties(self, category=None):
|
195
|
-
# this will remove all properties
|
196
|
-
# if category is not it will remove all properties
|
197
|
-
self.properties.filter(category=category).delete()
|
198
|
-
|
199
|
-
def getProperties(self, category=None):
|
200
|
-
ret = {}
|
201
|
-
for p in self.properties.all():
|
202
|
-
if p.category:
|
203
|
-
props = self.getFieldProps(p.category)
|
204
|
-
if props.hidden:
|
205
|
-
continue
|
206
|
-
if p.category not in ret or not isinstance(ret.get(p.category, None), dict):
|
207
|
-
ret[p.category] = {}
|
208
|
-
props = self.getFieldProps("{}.{}".format(p.category, p.key))
|
209
|
-
if props.hidden:
|
210
|
-
continue
|
211
|
-
ret[p.category][p.key] = p.getValue()
|
212
|
-
else:
|
213
|
-
props = self.getFieldProps(p.key)
|
214
|
-
if props.hidden:
|
215
|
-
continue
|
216
|
-
ret[p.key] = p.getValue()
|
217
|
-
if category is not None:
|
218
|
-
if category in ret:
|
219
|
-
return ret[category]
|
220
|
-
return {}
|
221
|
-
return ret
|
222
|
-
|
223
|
-
def __initFieldProps(self):
|
224
|
-
if not hasattr(self, "__field_props"):
|
225
|
-
if hasattr(self.RestMeta, "METADATA_FIELD_PROPERTIES"):
|
226
|
-
# this provides extra protection for metadata fields
|
227
|
-
self.__field_props = self.RestMeta.METADATA_FIELD_PROPERTIES
|
228
|
-
else:
|
229
|
-
self.__field_props = None
|
230
|
-
|
231
|
-
def getFieldProps(self, key):
|
232
|
-
self.__initFieldProps()
|
233
|
-
full_key = key
|
234
|
-
category = None
|
235
|
-
if "." in key:
|
236
|
-
category, key = key.split('.')
|
237
|
-
props = UberDict()
|
238
|
-
if self.__field_props:
|
239
|
-
if category and self.__field_props.get(category, None):
|
240
|
-
cat_props = self.__field_props.get(category, None)
|
241
|
-
if cat_props:
|
242
|
-
props.notify = cat_props.get("notify", None)
|
243
|
-
props.requires = cat_props.get("requires", None)
|
244
|
-
props.on_change_name = cat_props.get("on_change", None)
|
245
|
-
props.hidden = cat_props.get("hidden", False)
|
246
|
-
if props.on_change_name:
|
247
|
-
props.on_change = getattr(self, props.on_change_name, None)
|
248
|
-
field_props = self.__field_props.get(full_key, None)
|
249
|
-
if field_props:
|
250
|
-
props.notify = field_props.get("notify", props.notify)
|
251
|
-
props.requires = field_props.get("requires", None)
|
252
|
-
props.hidden = field_props.get("hidden", False)
|
253
|
-
on_change_name = field_props.get("on_change", None)
|
254
|
-
if on_change_name:
|
255
|
-
on_change = getattr(self, on_change_name, None)
|
256
|
-
if on_change:
|
257
|
-
props.on_change = on_change
|
258
|
-
return props
|
259
|
-
|
260
|
-
def checkFieldPerms(self, full_key, props, request=None):
|
261
|
-
if not props.requires:
|
262
|
-
return True
|
263
|
-
if not request or not request.member:
|
264
|
-
return False
|
265
|
-
if request.member.hasPermission(props.requires) or request.user.is_superuser:
|
266
|
-
return True
|
267
|
-
|
268
|
-
# this a unauthorized attempt to change field, log and throw exception
|
269
|
-
if props.notify and request.member:
|
270
|
-
subject = "permission denied changing protected '{}' field".format(full_key)
|
271
|
-
msg = "permission denied changing protected field '{}'\nby user: {}\nfor: {}".format(
|
272
|
-
full_key,
|
273
|
-
request.user.username,
|
274
|
-
self
|
275
|
-
)
|
276
|
-
request.member.notifyWithPermission(props.notify, subject, msg, email_only=True)
|
277
|
-
raise PermissionDeniedException(subject)
|
278
|
-
|
279
|
-
def setProperties(self, data, category=None, request=None, using=None):
|
280
|
-
for k, v in data.items():
|
281
|
-
self.setProperty(k, v, category, request=request, using=using)
|
282
|
-
|
283
|
-
def setProperty(self, key, value, category=None, request=None, using=None, ascii_only=False, encrypted=False):
|
284
|
-
# rest_helpers.log_print("{}:{} ({})".format(key, value, type(value)))
|
285
|
-
if ascii_only and isinstance(value, str):
|
286
|
-
value = "".join([x for x in value if x in string.printable])
|
287
|
-
if encrypted:
|
288
|
-
value = ENCRYPTER.encrypt(value)
|
289
|
-
on_change = None
|
290
|
-
if not using:
|
291
|
-
using = getattr(self.RestMeta, "DATABASE", using)
|
292
|
-
if not request:
|
293
|
-
request = RestModel.getActiveRequest()
|
294
|
-
self.__initFieldProps()
|
295
|
-
|
296
|
-
if isinstance(value, dict):
|
297
|
-
return self.setProperties(value, key)
|
298
|
-
username = "root"
|
299
|
-
if request and request.member:
|
300
|
-
username = request.member.username
|
301
|
-
prop = None
|
302
|
-
|
303
|
-
if "." in key:
|
304
|
-
category, key = key.split('.')
|
305
|
-
if category:
|
306
|
-
# delete any keys with this category name
|
307
|
-
full_key = "{}.{}".format(category, key)
|
308
|
-
# this deletes anything with the key that matches the category
|
309
|
-
# this works because the category is stored not in key but category field
|
310
|
-
# rest_helpers.log_print("deleting key={}".format(category))
|
311
|
-
self.properties.filter(key=category).delete()
|
312
|
-
else:
|
313
|
-
full_key = key
|
314
|
-
|
315
|
-
field_props = self.getFieldProps(full_key)
|
316
|
-
if not self.checkFieldPerms(full_key, field_props, request):
|
317
|
-
return False
|
318
|
-
|
319
|
-
check_value = "{}".format(value)
|
320
|
-
has_changed = False
|
321
|
-
prop = self.properties.filter(category=category, key=key).last()
|
322
|
-
old_value = None
|
323
|
-
if prop:
|
324
|
-
# existing property we need to make sure we delete
|
325
|
-
old_value = prop.getValue()
|
326
|
-
if value is None or value == "":
|
327
|
-
prop.delete()
|
328
|
-
has_changed = True
|
329
|
-
else:
|
330
|
-
has_changed = check_value != prop.value
|
331
|
-
if not has_changed:
|
332
|
-
return
|
333
|
-
prop.setValue(value)
|
334
|
-
prop.save(using=using)
|
335
|
-
if field_props.on_change:
|
336
|
-
field_props.on_change(key, value, old_value, category)
|
337
|
-
elif value is None or value == "":
|
338
|
-
# do not create none or empty property
|
339
|
-
return False
|
340
|
-
else:
|
341
|
-
has_changed = True
|
342
|
-
PropClass = self.get_fk_model("properties")
|
343
|
-
prop = PropClass(parent=self, key=key, category=category)
|
344
|
-
prop.setValue(value)
|
345
|
-
# rest_helpers.log_print(u"saving {}.{}".format(category, key))
|
346
|
-
# rest_helpers.log_print(u"saving {} : {}".format(full_key, value))
|
347
|
-
prop.save(using=using)
|
348
|
-
|
349
|
-
if hasattr(self, "_recordRestChange"):
|
350
|
-
self._recordRestChange("metadata.{}".format(full_key), old_value)
|
351
|
-
|
352
|
-
if field_props.notify and request and request.member:
|
353
|
-
notify = field_props.get("notify")
|
354
|
-
if value:
|
355
|
-
value = str(value)
|
356
|
-
if len(value) > 5:
|
357
|
-
value = "***"
|
358
|
-
msg = "protected field '{}' changed to '{}'\nby user: {}\nfor: {}".format(
|
359
|
-
full_key,
|
360
|
-
value,
|
361
|
-
username,
|
362
|
-
self
|
363
|
-
)
|
364
|
-
request.member.notifyWithPermission(notify, "protected '{}' field changed".format(full_key), msg, email_only=True)
|
365
|
-
return has_changed
|
366
|
-
|
367
|
-
def getProperty(self, key, default=None, category=None, field_type=None, decrypted=False):
|
368
|
-
try:
|
369
|
-
if "." in key:
|
370
|
-
category, key = key.split('.')
|
371
|
-
value = self.properties.get(category=category, key=key).getValue(field_type)
|
372
|
-
if decrypted and value is not None:
|
373
|
-
return DECRYPTER.decrypt(value)
|
374
|
-
return value
|
375
|
-
except Exception:
|
376
|
-
pass
|
377
|
-
return default
|
378
|
-
|
379
26
|
|
380
27
|
class RestModel(object):
|
381
28
|
class __RestMeta__:
|
@@ -390,23 +37,7 @@ class RestModel(object):
|
|
390
37
|
|
391
38
|
@staticmethod
|
392
39
|
def generateUUID(*args, **kwargs):
|
393
|
-
|
394
|
-
max_length = kwargs.get("max_length", None)
|
395
|
-
uuid = ""
|
396
|
-
for key in args:
|
397
|
-
if isinstance(key, float):
|
398
|
-
key = str(float)
|
399
|
-
if isinstance(key, int):
|
400
|
-
uuid += Hashids().encrypt(key)
|
401
|
-
if isinstance(key, str):
|
402
|
-
uuid += rest_helpers.toString(hashlib.md5(rest_helpers.toBytes(key)).hexdigest())
|
403
|
-
if len(uuid) > 125:
|
404
|
-
uuid = uuid[:125]
|
405
|
-
if max_length != None:
|
406
|
-
uuid = uuid[:max_length]
|
407
|
-
if upper:
|
408
|
-
return uuid.upper()
|
409
|
-
return uuid
|
40
|
+
return crypto.generateUUID(*args, **kwargs)
|
410
41
|
|
411
42
|
@classmethod
|
412
43
|
def buildGraph(cls, name):
|
@@ -457,8 +88,6 @@ class RestModel(object):
|
|
457
88
|
continue
|
458
89
|
|
459
90
|
if isinstance(gname, dict):
|
460
|
-
size = gname.get("size")
|
461
|
-
sort = gname.get("sort")
|
462
91
|
fm_name = gname.get("model")
|
463
92
|
gname = gname.get("graph")
|
464
93
|
if not gname:
|
@@ -482,7 +111,7 @@ class RestModel(object):
|
|
482
111
|
if not ForeignModel:
|
483
112
|
ForeignModel = cls.get_fk_model(field)
|
484
113
|
if not ForeignModel:
|
485
|
-
|
114
|
+
rh.log_print("no foreignkey: {0}".format(field))
|
486
115
|
continue
|
487
116
|
|
488
117
|
if field not in graph["recurse_into"]:
|
@@ -591,7 +220,7 @@ class RestModel(object):
|
|
591
220
|
|
592
221
|
@classmethod
|
593
222
|
def getActiveLogger(cls):
|
594
|
-
return
|
223
|
+
return rh.getLogger(cls.getActiveRequest())
|
595
224
|
|
596
225
|
@classmethod
|
597
226
|
def getActiveMember(cls):
|
@@ -602,10 +231,7 @@ class RestModel(object):
|
|
602
231
|
|
603
232
|
@classmethod
|
604
233
|
def getActiveRequest(cls):
|
605
|
-
|
606
|
-
mw = importlib.import_module("rest.middleware")
|
607
|
-
GRAPH_HELPERS.get_request = mw.get_request
|
608
|
-
return GRAPH_HELPERS.get_request()
|
234
|
+
return rh.getActiveRequest()
|
609
235
|
|
610
236
|
@classmethod
|
611
237
|
def getFromRequest(cls, request):
|
@@ -661,7 +287,7 @@ class RestModel(object):
|
|
661
287
|
# called by the rest module to magically parse
|
662
288
|
# a component that is marked genericr elation in a graph
|
663
289
|
if not hasattr(self, field):
|
664
|
-
|
290
|
+
rh.log_print("model has no field: {0}".format(field))
|
665
291
|
return None
|
666
292
|
|
667
293
|
name = getattr(self, field)
|
@@ -670,7 +296,7 @@ class RestModel(object):
|
|
670
296
|
a_name, m_name = name.split(".")
|
671
297
|
model = RestModel.getModel(a_name, m_name)
|
672
298
|
if not model:
|
673
|
-
|
299
|
+
rh.log_print("GENERIC MODEL DOES NOT EXIST: {0}".format(name))
|
674
300
|
return model
|
675
301
|
|
676
302
|
def restGetGenericRelation(self, field):
|
@@ -704,7 +330,7 @@ class RestModel(object):
|
|
704
330
|
"""
|
705
331
|
Helper method to save a list of fields
|
706
332
|
"""
|
707
|
-
self._changed__ =
|
333
|
+
self._changed__ = objict()
|
708
334
|
for key, value in list(kwargs.items()):
|
709
335
|
if value is None and not allow_null:
|
710
336
|
continue
|
@@ -714,7 +340,7 @@ class RestModel(object):
|
|
714
340
|
|
715
341
|
def restSaveField(self, fieldname, value, has_fields=False, has_no_fields=False, using=None):
|
716
342
|
if not hasattr(self, "_changed__"):
|
717
|
-
self._changed__ =
|
343
|
+
self._changed__ = objict()
|
718
344
|
|
719
345
|
if fieldname.startswith("_"):
|
720
346
|
return
|
@@ -728,7 +354,7 @@ class RestModel(object):
|
|
728
354
|
if has_fields and fieldname not in self.RestMeta.SAVE_FIELDS:
|
729
355
|
return
|
730
356
|
if fieldname.endswith("_id") and not self.get_field_type(fieldname):
|
731
|
-
# django will have ForeignKeys with _id, we don't want that, on_delete=
|
357
|
+
# django will have ForeignKeys with _id, we don't want that, on_delete=dm.CASCADE
|
732
358
|
fieldname = fieldname[:-3]
|
733
359
|
setter = F"set_{fieldname}"
|
734
360
|
if hasattr(self, setter):
|
@@ -737,7 +363,7 @@ class RestModel(object):
|
|
737
363
|
|
738
364
|
if fieldname in self._field_names__:
|
739
365
|
# TODO check if it is a function
|
740
|
-
if isinstance(value,
|
366
|
+
if isinstance(value, dm.Model):
|
741
367
|
setattr(self, fieldname, value)
|
742
368
|
self._changed__[fieldname] = True
|
743
369
|
return
|
@@ -749,8 +375,8 @@ class RestModel(object):
|
|
749
375
|
if using is None:
|
750
376
|
using = self.restGetModelDB()
|
751
377
|
obj.saveFromDict(None, value, using=using)
|
752
|
-
#
|
753
|
-
#
|
378
|
+
# rh.log_print("{} vs {}".format(self._state.db, obj._state.db))
|
379
|
+
# rh.log_print("saving FK to {} ({}.{}) - {}".format(fieldname, using, obj.pk, type(obj)), value)
|
754
380
|
setattr(self, fieldname, obj)
|
755
381
|
self._changed__[fieldname] = True
|
756
382
|
return
|
@@ -780,16 +406,16 @@ class RestModel(object):
|
|
780
406
|
if field_model and value != None:
|
781
407
|
field_model_name = field_model.__class__.__name__
|
782
408
|
if field_model_name == "DateTimeField":
|
783
|
-
value =
|
409
|
+
value = rh.parseDateTime(value)
|
784
410
|
# value = datetime.fromtimestamp(float(value))
|
785
411
|
elif field_model_name == "DateField":
|
786
|
-
value =
|
412
|
+
value = rh.parseDate(value, as_date=True)
|
787
413
|
elif field_model_name == "IntegerField":
|
788
414
|
value = int(value)
|
789
415
|
elif field_model_name == "FloatField":
|
790
416
|
value = float(value)
|
791
417
|
elif field_model_name == "CurrencyField":
|
792
|
-
value = Decimal(value).quantize(
|
418
|
+
value = Decimal(value).quantize(TWO_DECIMAL_PLACES)
|
793
419
|
elif field_model_name == "BooleanField":
|
794
420
|
if value in [True, 1, 'True', 'true', '1', 't', 'y', 'yes']:
|
795
421
|
value = True
|
@@ -810,13 +436,13 @@ class RestModel(object):
|
|
810
436
|
|
811
437
|
def _recordRestChange(self, fieldname, old_value):
|
812
438
|
if not hasattr(self, "_changed__"):
|
813
|
-
self._changed__ =
|
439
|
+
self._changed__ = objict()
|
814
440
|
if "." in fieldname:
|
815
441
|
fields = fieldname.split('.')
|
816
442
|
root = self._changed__
|
817
443
|
for f in fields[:-1]:
|
818
444
|
if f not in root:
|
819
|
-
root[f] =
|
445
|
+
root[f] = objict()
|
820
446
|
root = root[f]
|
821
447
|
root[fields[-1]] = old_value
|
822
448
|
else:
|
@@ -831,7 +457,7 @@ class RestModel(object):
|
|
831
457
|
if request is None:
|
832
458
|
request = RestModel.getActiveRequest()
|
833
459
|
if request is None:
|
834
|
-
request =
|
460
|
+
request = objict(member=None, FILES=[], DATA=objict(), is_authenticated=False, is_local=True)
|
835
461
|
self.onRestCanSave(request)
|
836
462
|
is_new = self.id is None
|
837
463
|
has_fields = hasattr(self.RestMeta, "SAVE_FIELDS") and len(self.RestMeta.SAVE_FIELDS)
|
@@ -841,9 +467,9 @@ class RestModel(object):
|
|
841
467
|
self._state.db = kwargs.get("using", self.restGetModelDB("default"))
|
842
468
|
auto_save_fields = getattr(self.RestMeta, "AUTO_SAVE", None)
|
843
469
|
if auto_save_fields:
|
844
|
-
#
|
470
|
+
# rh.log_print(auto_save_fields)
|
845
471
|
for field in auto_save_fields:
|
846
|
-
#
|
472
|
+
# rh.log_print(field)
|
847
473
|
if isinstance(field, tuple):
|
848
474
|
m_field, req_field = field
|
849
475
|
else:
|
@@ -852,8 +478,8 @@ class RestModel(object):
|
|
852
478
|
req_value = getattr(request, req_field, None)
|
853
479
|
if request and req_value:
|
854
480
|
data[m_field] = req_value
|
855
|
-
#
|
856
|
-
self._changed__ =
|
481
|
+
# rh.log_print(data)
|
482
|
+
self._changed__ = objict()
|
857
483
|
if hasattr(self.RestMeta, "POST_SAVE_FIELDS"):
|
858
484
|
post_save_fields = self.RestMeta.POST_SAVE_FIELDS
|
859
485
|
else:
|
@@ -920,7 +546,7 @@ class RestModel(object):
|
|
920
546
|
else:
|
921
547
|
ForeignModel = self.get_fk_model(name)
|
922
548
|
if ForeignModel and ForeignModel.__name__ == "MediaItem":
|
923
|
-
#
|
549
|
+
# rh.log_print("saving media file: {}".format(name))
|
924
550
|
self.saveMediaFile(files[name], name)
|
925
551
|
else:
|
926
552
|
remaining_files[name] = files[name]
|
@@ -929,7 +555,7 @@ class RestModel(object):
|
|
929
555
|
def restSaveMediaFiles(self, files=None):
|
930
556
|
MediaItemRef = RestModel.getModel("medialib", "MediaItemRef")
|
931
557
|
component = self.get_class_name(True)
|
932
|
-
#
|
558
|
+
# rh.log_print("saving media files refs: {}".format(component))
|
933
559
|
for name in files:
|
934
560
|
mi = self.saveMediaFile(files[name], name, is_local=False)
|
935
561
|
mr = None
|
@@ -937,7 +563,7 @@ class RestModel(object):
|
|
937
563
|
# use existing reference
|
938
564
|
mr = MediaItemRef.objects.filter(component=component, component_id=self.id, media__name=name).last()
|
939
565
|
if mr:
|
940
|
-
|
566
|
+
rh.log_print(F"existing media files refs: {name}.{component}.{self.id}")
|
941
567
|
mr.media = mi
|
942
568
|
mr.save()
|
943
569
|
if mr is None:
|
@@ -979,13 +605,13 @@ class RestModel(object):
|
|
979
605
|
if field_model_name == "DateTimeField":
|
980
606
|
value = datetime.fromtimestamp(float(value))
|
981
607
|
elif field_model_name == "DateField":
|
982
|
-
value =
|
608
|
+
value = rh.parseDate(value)
|
983
609
|
elif field_model_name == "IntegerField":
|
984
610
|
value = int(value)
|
985
611
|
elif field_model_name == "FloatField":
|
986
612
|
value = float(value)
|
987
613
|
elif field_model_name == "CurrencyField":
|
988
|
-
value = Decimal(value).quantize(
|
614
|
+
value = Decimal(value).quantize(TWO_DECIMAL_PLACES)
|
989
615
|
if hasattr(self, key) and getattr(self, key) != value:
|
990
616
|
deltas.append(key)
|
991
617
|
except:
|
@@ -1017,7 +643,7 @@ class RestModel(object):
|
|
1017
643
|
mi = MediaItem(name=file_name, group=group, newfile=file)
|
1018
644
|
else:
|
1019
645
|
mi = MediaItem(name=file_name, newfile=file, group=group)
|
1020
|
-
#
|
646
|
+
# rh.log_print(F"saving media file: {name}")
|
1021
647
|
mi.save()
|
1022
648
|
if is_local:
|
1023
649
|
setattr(self, name, mi)
|
@@ -1027,7 +653,7 @@ class RestModel(object):
|
|
1027
653
|
if not request:
|
1028
654
|
request = self.getActiveRequest()
|
1029
655
|
if not request or not hasattr(request, "setLogModel"):
|
1030
|
-
|
656
|
+
rh.log_print("request does not support setLogModel")
|
1031
657
|
return
|
1032
658
|
if not self.id:
|
1033
659
|
self.save()
|
@@ -1049,9 +675,9 @@ class RestModel(object):
|
|
1049
675
|
return True
|
1050
676
|
# we need to check if this user has permission
|
1051
677
|
group_field = getattr(self.RestMeta, "GROUP_FIELD", "group")
|
1052
|
-
status, error, code = requestHasPerms(request, perms, getattr(self, group_field, None))
|
678
|
+
status, error, code = rh.requestHasPerms(request, perms, getattr(self, group_field, None))
|
1053
679
|
if not status:
|
1054
|
-
raise PermissionDeniedException(error, code)
|
680
|
+
raise re.PermissionDeniedException(error, code)
|
1055
681
|
return True
|
1056
682
|
|
1057
683
|
def on_rest_get(self, request):
|
@@ -1079,7 +705,7 @@ class RestModel(object):
|
|
1079
705
|
return True
|
1080
706
|
# we need to check if this user has permission
|
1081
707
|
group_field = getattr(self.RestMeta, "GROUP_FIELD", "group")
|
1082
|
-
#
|
708
|
+
# rh.log_error(F"group field={group_field}")
|
1083
709
|
if self.pk is None:
|
1084
710
|
group = request.DATA.get(group_field)
|
1085
711
|
elif "__" in group_field and hasattr(self, "group"):
|
@@ -1093,12 +719,12 @@ class RestModel(object):
|
|
1093
719
|
break
|
1094
720
|
else:
|
1095
721
|
group = getattr(self, group_field, None)
|
1096
|
-
status, error, code = requestHasPerms(request, perms, group)
|
722
|
+
status, error, code = rh.requestHasPerms(request, perms, group)
|
1097
723
|
if not status:
|
1098
724
|
if self.pk is None and code == 402:
|
1099
725
|
code = 435
|
1100
|
-
|
1101
|
-
raise PermissionDeniedException(error, code)
|
726
|
+
rh.log_error(F"onRestCheckSavePerms: {request.member} {group} permission denied: status={status}, error={error}, code={code}", perms)
|
727
|
+
raise re.PermissionDeniedException(error, code)
|
1102
728
|
|
1103
729
|
def on_rest_post(self, request):
|
1104
730
|
self.saveFromRequest(request)
|
@@ -1198,7 +824,7 @@ class RestModel(object):
|
|
1198
824
|
member_field = getattr(cls.RestMeta, "VIEW_PERMS_MEMBER_FIELD", None)
|
1199
825
|
if member_field is None:
|
1200
826
|
return cls.objects.none()
|
1201
|
-
#
|
827
|
+
# rh.log_error(dict(**{member_field: request.member}))
|
1202
828
|
return qset.filter(**{member_field: request.member})
|
1203
829
|
|
1204
830
|
@classmethod
|
@@ -1231,7 +857,7 @@ class RestModel(object):
|
|
1231
857
|
|
1232
858
|
@classmethod
|
1233
859
|
def on_rest_filter_children(cls, request, qset=None):
|
1234
|
-
#
|
860
|
+
# rh.log_error("filter by group?")
|
1235
861
|
group_field = getattr(cls.RestMeta, "GROUP_FIELD", "group")
|
1236
862
|
if group_field is None or ("__" not in group_field and not cls.has_model_field_name(group_field)):
|
1237
863
|
return qset
|
@@ -1251,14 +877,14 @@ class RestModel(object):
|
|
1251
877
|
q = {}
|
1252
878
|
q[group_field] = request.group.id
|
1253
879
|
qset = qset.filter(**q)
|
1254
|
-
#
|
880
|
+
# rh.log_error(q, qset.count())
|
1255
881
|
return qset
|
1256
882
|
|
1257
883
|
@classmethod
|
1258
884
|
def on_rest_list_ready(cls, request, qset=None):
|
1259
885
|
# override on do any post filters
|
1260
|
-
#
|
1261
|
-
#
|
886
|
+
# rh.log_error("RAW SQL")
|
887
|
+
# rh.log_error(qset.query)
|
1262
888
|
return qset
|
1263
889
|
|
1264
890
|
@classmethod
|
@@ -1276,7 +902,7 @@ class RestModel(object):
|
|
1276
902
|
if dr_tz is not None:
|
1277
903
|
dr_eod = request.DATA.get("dr_eod", 0, field_type=int)
|
1278
904
|
dr_granularity = request.DATA.get("granularity", "day")
|
1279
|
-
dr_start, dr_end =
|
905
|
+
dr_start, dr_end = rh.getDateRangeZ(dr_start, dr_end, dr_granularity, dr_tz, hour=dr_eod)
|
1280
906
|
else:
|
1281
907
|
dr_offset = request.DATA.get("dr_offset", 0, field_type=int)
|
1282
908
|
if dr_offset > 0:
|
@@ -1288,8 +914,8 @@ class RestModel(object):
|
|
1288
914
|
q["{}__lte".format(dr_field)] = dr_end
|
1289
915
|
qset = qset.filter(**q)
|
1290
916
|
request.response_extra = dict(
|
1291
|
-
dr_start=
|
1292
|
-
dr_end=
|
917
|
+
dr_start=rh.convertToEpoch(dr_start),
|
918
|
+
dr_end=rh.convertToEpoch(dr_end))
|
1293
919
|
return qset
|
1294
920
|
|
1295
921
|
@classmethod
|
@@ -1343,7 +969,7 @@ class RestModel(object):
|
|
1343
969
|
if format in ["summary", "summary_only"]:
|
1344
970
|
return cls.on_rest_list_summary(request, qset)
|
1345
971
|
if hasattr(cls.RestMeta, "FORMATS"):
|
1346
|
-
fields =
|
972
|
+
fields = cls.RestMeta.FORMATS.get(format)
|
1347
973
|
else:
|
1348
974
|
no_show_fields = RestModel.__RestMeta__.NO_SHOW_FIELDS
|
1349
975
|
if hasattr(cls.RestMeta, "NO_SHOW_FIELDS"):
|
@@ -1381,21 +1007,21 @@ class RestModel(object):
|
|
1381
1007
|
def on_rest_list_summary(cls, request, qset):
|
1382
1008
|
cls._boundRest()
|
1383
1009
|
summary_info = getattr(cls.RestMeta, "SUMMARY_FIELDS", {})
|
1384
|
-
output =
|
1010
|
+
output = objict()
|
1385
1011
|
output.count = qset.count()
|
1386
1012
|
for key, value in summary_info.items():
|
1387
1013
|
if key == "sum":
|
1388
|
-
res =
|
1014
|
+
res = rh.getSum(qset, *value)
|
1389
1015
|
if isinstance(res, dict):
|
1390
1016
|
output.update(res)
|
1391
1017
|
else:
|
1392
1018
|
output[value[0]] = res
|
1393
1019
|
elif key == "avg":
|
1394
1020
|
for f in value:
|
1395
|
-
output["avg_{}".format(f)] =
|
1021
|
+
output["avg_{}".format(f)] = rh.getAverage(qset, f)
|
1396
1022
|
elif key == "max":
|
1397
1023
|
for f in value:
|
1398
|
-
output["max_{}".format(f)] =
|
1024
|
+
output["max_{}".format(f)] = rh.getMax(qset, f)
|
1399
1025
|
elif isinstance(value, dict):
|
1400
1026
|
if "|" in key:
|
1401
1027
|
fields = key.split("|")
|
@@ -1413,11 +1039,11 @@ class RestModel(object):
|
|
1413
1039
|
if action == "count":
|
1414
1040
|
output[lbl] = act_qset.count()
|
1415
1041
|
elif action == "sum":
|
1416
|
-
output[lbl] =
|
1042
|
+
output[lbl] = rh.getSum(act_qset, field)
|
1417
1043
|
elif action == "avg":
|
1418
|
-
output[lbl] =
|
1044
|
+
output[lbl] = rh.getAverage(act_qset, field)
|
1419
1045
|
elif action == "max":
|
1420
|
-
output[lbl] =
|
1046
|
+
output[lbl] = rh.getMax(act_qset, field)
|
1421
1047
|
return GRAPH_HELPERS.restGet(request, output)
|
1422
1048
|
|
1423
1049
|
@classmethod
|
@@ -1450,14 +1076,13 @@ class RestModel(object):
|
|
1450
1076
|
elif action == "create":
|
1451
1077
|
batch_data = request.DATA.getlist("batch_data", [])
|
1452
1078
|
items = []
|
1453
|
-
exist = []
|
1454
1079
|
for item in batch_data:
|
1455
1080
|
try:
|
1456
1081
|
obj = cls.ro_objects().filter(**item).last()
|
1457
1082
|
if not obj:
|
1458
1083
|
obj.saveFromDict(request, item)
|
1459
1084
|
items.append(obj)
|
1460
|
-
except:
|
1085
|
+
except Exception:
|
1461
1086
|
pass
|
1462
1087
|
return GRAPH_HELPERS.restList(request, items)
|
1463
1088
|
return GRAPH_HELPERS.restStatus(request, False, error="not implemented")
|
@@ -1474,7 +1099,7 @@ class RestModel(object):
|
|
1474
1099
|
for k, v in list(cls.RestMeta.KEY_TO_FIELD_MAP.items()):
|
1475
1100
|
if hasattr(request, k):
|
1476
1101
|
value = getattr(request, k)
|
1477
|
-
if value
|
1102
|
+
if value is not None:
|
1478
1103
|
kv[v] = value
|
1479
1104
|
obj = cls.createFromRequest(request, **kv)
|
1480
1105
|
else:
|
@@ -1501,7 +1126,7 @@ class RestModel(object):
|
|
1501
1126
|
|
1502
1127
|
@classmethod
|
1503
1128
|
def get_rest_help(cls):
|
1504
|
-
output =
|
1129
|
+
output = objict()
|
1505
1130
|
if cls.__doc__:
|
1506
1131
|
output.doc = cls.__doc__.rstrip()
|
1507
1132
|
else:
|
@@ -1626,13 +1251,13 @@ class RestModel(object):
|
|
1626
1251
|
default_filters = getattr(cls.RestMeta, "LIST_DEFAULT_FILTERS", None)
|
1627
1252
|
if default_filters:
|
1628
1253
|
# make a copy so we can remove filters until the end
|
1629
|
-
request.default_rest_filters =
|
1254
|
+
request.default_rest_filters = objict.fromdict(default_filters)
|
1630
1255
|
|
1631
1256
|
@classmethod
|
1632
1257
|
def filterFromRequest(cls, request, qset):
|
1633
1258
|
'''returns None if not foreignkey, otherswise the relevant model'''
|
1634
1259
|
filter = request.DATA.pop("filter")
|
1635
|
-
#
|
1260
|
+
# rh.debug("filterFromRequest")
|
1636
1261
|
cls.on_rest_set_query_defaults(request)
|
1637
1262
|
if not filter:
|
1638
1263
|
return qset
|
@@ -1698,14 +1323,14 @@ class RestModel(object):
|
|
1698
1323
|
value = 0
|
1699
1324
|
if isinstance(value, str) and "(" in value and ")" in value:
|
1700
1325
|
# this is a special function call
|
1701
|
-
#
|
1326
|
+
# rh.log_print(value)
|
1702
1327
|
if value.startswith("days("):
|
1703
1328
|
spos = value.find("(")+1
|
1704
1329
|
epos = value.find(")")
|
1705
|
-
#
|
1330
|
+
# rh.log_print(int(value[spos:epos]))
|
1706
1331
|
value = now + timedelta(days=int(value[spos:epos]))
|
1707
|
-
#
|
1708
|
-
#
|
1332
|
+
# rh.log_print(now)
|
1333
|
+
# rh.log_print(value)
|
1709
1334
|
elif value.startswith("hours("):
|
1710
1335
|
spos = value.find("(")+1
|
1711
1336
|
epos = value.find(")")
|
@@ -1723,9 +1348,9 @@ class RestModel(object):
|
|
1723
1348
|
if key.count('__') <= 4:
|
1724
1349
|
q[key] = value
|
1725
1350
|
else:
|
1726
|
-
|
1351
|
+
rh.log_print("filterFromRequest: invalid field: {} or {}".format(name, key))
|
1727
1352
|
if q:
|
1728
|
-
#
|
1353
|
+
# rh.debug("filtering...", q)
|
1729
1354
|
qset = qset.filter(**q)
|
1730
1355
|
return qset
|
1731
1356
|
|
@@ -1782,7 +1407,7 @@ class RestModel(object):
|
|
1782
1407
|
query_keys = request.DATA.keys()
|
1783
1408
|
special_keys = getattr(cls.RestMeta, "QUERY_FIELDS_SPECIAL", {})
|
1784
1409
|
cls.on_rest_set_query_defaults(request)
|
1785
|
-
#
|
1410
|
+
# rh.debug("queryFromRequest.key", query_keys, special_keys, field_names)
|
1786
1411
|
for key in query_keys:
|
1787
1412
|
# remove the key from defaults
|
1788
1413
|
if request.default_rest_filters:
|
@@ -1854,10 +1479,10 @@ class RestModel(object):
|
|
1854
1479
|
value = True
|
1855
1480
|
ft = cls.get_field_type(fn)
|
1856
1481
|
if ft == "DateTimeField":
|
1857
|
-
value =
|
1482
|
+
value = rh.parseDateTime(value)
|
1858
1483
|
q[key] = value
|
1859
1484
|
if bool(q):
|
1860
|
-
#
|
1485
|
+
# rh.debug("queryFromRequest", q, request.default_rest_filters)
|
1861
1486
|
qset = qset.filter(**q)
|
1862
1487
|
return qset
|
1863
1488
|
|
@@ -1922,5 +1547,5 @@ class RestModel(object):
|
|
1922
1547
|
try:
|
1923
1548
|
field = cls._meta.get_field(fieldname)
|
1924
1549
|
return field.related_model
|
1925
|
-
except:
|
1550
|
+
except Exception:
|
1926
1551
|
return None
|