django-restit 4.0.13__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.13.dist-info → django_restit-4.1.1.dist-info}/METADATA +1 -1
- {django_restit-4.0.13.dist-info → django_restit-4.1.1.dist-info}/RECORD +18 -14
- 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
- {django_restit-4.0.13.dist-info → django_restit-4.1.1.dist-info}/LICENSE.md +0 -0
- {django_restit-4.0.13.dist-info → django_restit-4.1.1.dist-info}/WHEEL +0 -0
rest/models/cacher.py
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
from django.db import models as dm
|
2
|
+
from rest import fields as rf
|
3
|
+
from rest import helpers as rh
|
4
|
+
from objict import objict
|
5
|
+
|
6
|
+
|
7
|
+
class ModelCache(dm.Model):
|
8
|
+
class Meta:
|
9
|
+
abstract = True
|
10
|
+
|
11
|
+
modified = dm.DateTimeField(auto_now=True, editable=True, db_index=True)
|
12
|
+
|
13
|
+
component = dm.SlugField(max_length=200, null=True, blank=True, default=None, db_index=True)
|
14
|
+
component_id = dm.IntegerField(null=True, blank=True, default=None, db_index=True)
|
15
|
+
|
16
|
+
query = dm.CharField(max_length=255, null=True, blank=True, default=None, db_index=True)
|
17
|
+
|
18
|
+
cache = rf.JSONField()
|
19
|
+
|
rest/models/metadata.py
ADDED
@@ -0,0 +1,302 @@
|
|
1
|
+
from datetime import datetime, date
|
2
|
+
from objict import objict
|
3
|
+
from django.db import models as dm
|
4
|
+
import string
|
5
|
+
|
6
|
+
from rest import helpers as rh
|
7
|
+
from rest import errors as re
|
8
|
+
from rest.encryption import ENCRYPTER, DECRYPTER
|
9
|
+
|
10
|
+
|
11
|
+
class MetaDataBase(dm.Model):
|
12
|
+
class Meta:
|
13
|
+
abstract = True
|
14
|
+
|
15
|
+
category = dm.CharField(db_index=True, max_length=32, default=None, null=True, blank=True)
|
16
|
+
|
17
|
+
key = dm.CharField(db_index=True, max_length=80)
|
18
|
+
|
19
|
+
value_format = dm.CharField(max_length=16)
|
20
|
+
value = dm.TextField()
|
21
|
+
|
22
|
+
int_value = dm.IntegerField(default=None, null=True, blank=True)
|
23
|
+
float_value = dm.IntegerField(default=None, null=True, blank=True)
|
24
|
+
|
25
|
+
def setValue(self, value):
|
26
|
+
self.value = "{}".format(value)
|
27
|
+
if type(value) is int or self.value in ["0", "1"]:
|
28
|
+
if type(value) is int and value > 2147483647:
|
29
|
+
self.value_format = "S"
|
30
|
+
return
|
31
|
+
self.value_format = "I"
|
32
|
+
self.int_value = value
|
33
|
+
elif type(value) is float:
|
34
|
+
self.value_format = "F"
|
35
|
+
self.float_value = value
|
36
|
+
elif isinstance(value, list):
|
37
|
+
self.value_format = "L"
|
38
|
+
# self.value = ",".join(value)
|
39
|
+
elif isinstance(value, dict):
|
40
|
+
self.value_format = "O"
|
41
|
+
elif type(value) in [str, str] and len(value) < 9 and value.isdigit():
|
42
|
+
self.value_format = "I"
|
43
|
+
self.int_value = value
|
44
|
+
elif value in ["True", "true", "False", "false"]:
|
45
|
+
self.value_format = "I"
|
46
|
+
if value in ["True", "true"]:
|
47
|
+
self.int_value = 1
|
48
|
+
else:
|
49
|
+
self.int_value = 0
|
50
|
+
elif isinstance(value, bool):
|
51
|
+
self.value_format = "I"
|
52
|
+
if value:
|
53
|
+
self.int_value = 1
|
54
|
+
else:
|
55
|
+
self.int_value = 0
|
56
|
+
else:
|
57
|
+
self.value_format = "S"
|
58
|
+
|
59
|
+
def getStrictType(self, field_type):
|
60
|
+
if type(self.value) is field_type:
|
61
|
+
return self.value
|
62
|
+
if field_type in [int, str, float, str]:
|
63
|
+
return field_type(self.value)
|
64
|
+
elif field_type is bool:
|
65
|
+
if self.value_format == 'I':
|
66
|
+
return self.int_value != 0
|
67
|
+
return self.value in [True, 1, '1', 'y', 'Y', 'Yes', 'true', 'True']
|
68
|
+
elif field_type in [date, datetime]:
|
69
|
+
return rh.parseDate(self.value)
|
70
|
+
return self.value
|
71
|
+
|
72
|
+
def getValue(self, field_type=None):
|
73
|
+
if field_type:
|
74
|
+
return self.getStrictType(field_type)
|
75
|
+
elif self.value_format == 'I':
|
76
|
+
return self.int_value
|
77
|
+
elif self.value_format == 'F':
|
78
|
+
return self.float_value
|
79
|
+
elif self.value_format in ["L", "O"] and self.value:
|
80
|
+
try:
|
81
|
+
return eval(self.value)
|
82
|
+
except Exception:
|
83
|
+
pass
|
84
|
+
return self.value
|
85
|
+
|
86
|
+
def __unicode__(self):
|
87
|
+
if self.category:
|
88
|
+
return "{}.{}={}".format(self.category, self.key, self.value)
|
89
|
+
return "{}={}".format(self.key, self.value)
|
90
|
+
|
91
|
+
def __str__(self):
|
92
|
+
if self.category:
|
93
|
+
return "{}.{}={}".format(self.category, self.key, self.value)
|
94
|
+
return "{}={}".format(self.key, self.value)
|
95
|
+
|
96
|
+
|
97
|
+
class MetaDataModel(object):
|
98
|
+
def set_metadata(self, request, values=None):
|
99
|
+
# this may get called before the model is saved
|
100
|
+
if not self.id:
|
101
|
+
self.save()
|
102
|
+
|
103
|
+
if values is None:
|
104
|
+
values = request
|
105
|
+
request = None
|
106
|
+
|
107
|
+
if not isinstance(values, dict):
|
108
|
+
raise Exception("invalid metadata: {}".format(values))
|
109
|
+
|
110
|
+
for key, value in list(values.items()):
|
111
|
+
cat = None
|
112
|
+
if "." in key:
|
113
|
+
cat, key = key.split('.')
|
114
|
+
self.setProperty(key, value, cat, request=request)
|
115
|
+
|
116
|
+
def metadata(self):
|
117
|
+
return self.getProperties()
|
118
|
+
|
119
|
+
def removeProperties(self, category=None):
|
120
|
+
# this will remove all properties
|
121
|
+
# if category is not it will remove all properties
|
122
|
+
self.properties.filter(category=category).delete()
|
123
|
+
|
124
|
+
def getProperties(self, category=None):
|
125
|
+
ret = {}
|
126
|
+
for p in self.properties.all():
|
127
|
+
if p.category:
|
128
|
+
props = self.getFieldProps(p.category)
|
129
|
+
if props.hidden:
|
130
|
+
continue
|
131
|
+
if p.category not in ret or not isinstance(ret.get(p.category, None), dict):
|
132
|
+
ret[p.category] = {}
|
133
|
+
props = self.getFieldProps("{}.{}".format(p.category, p.key))
|
134
|
+
if props.hidden:
|
135
|
+
continue
|
136
|
+
ret[p.category][p.key] = p.getValue()
|
137
|
+
else:
|
138
|
+
props = self.getFieldProps(p.key)
|
139
|
+
if props.hidden:
|
140
|
+
continue
|
141
|
+
ret[p.key] = p.getValue()
|
142
|
+
if category is not None:
|
143
|
+
if category in ret:
|
144
|
+
return ret[category]
|
145
|
+
return {}
|
146
|
+
return ret
|
147
|
+
|
148
|
+
def __initFieldProps(self):
|
149
|
+
if not hasattr(self, "__field_props"):
|
150
|
+
if hasattr(self.RestMeta, "METADATA_FIELD_PROPERTIES"):
|
151
|
+
# this provides extra protection for metadata fields
|
152
|
+
self.__field_props = self.RestMeta.METADATA_FIELD_PROPERTIES
|
153
|
+
else:
|
154
|
+
self.__field_props = None
|
155
|
+
|
156
|
+
def getFieldProps(self, key):
|
157
|
+
self.__initFieldProps()
|
158
|
+
full_key = key
|
159
|
+
category = None
|
160
|
+
if "." in key:
|
161
|
+
category, key = key.split('.')
|
162
|
+
props = objict()
|
163
|
+
if self.__field_props:
|
164
|
+
if category and self.__field_props.get(category, None):
|
165
|
+
cat_props = self.__field_props.get(category, None)
|
166
|
+
if cat_props:
|
167
|
+
props.notify = cat_props.get("notify", None)
|
168
|
+
props.requires = cat_props.get("requires", None)
|
169
|
+
props.on_change_name = cat_props.get("on_change", None)
|
170
|
+
props.hidden = cat_props.get("hidden", False)
|
171
|
+
if props.on_change_name:
|
172
|
+
props.on_change = getattr(self, props.on_change_name, None)
|
173
|
+
field_props = self.__field_props.get(full_key, None)
|
174
|
+
if field_props:
|
175
|
+
props.notify = field_props.get("notify", props.notify)
|
176
|
+
props.requires = field_props.get("requires", None)
|
177
|
+
props.hidden = field_props.get("hidden", False)
|
178
|
+
on_change_name = field_props.get("on_change", None)
|
179
|
+
if on_change_name:
|
180
|
+
on_change = getattr(self, on_change_name, None)
|
181
|
+
if on_change:
|
182
|
+
props.on_change = on_change
|
183
|
+
return props
|
184
|
+
|
185
|
+
def checkFieldPerms(self, full_key, props, request=None):
|
186
|
+
if not props.requires:
|
187
|
+
return True
|
188
|
+
if not request or not request.member:
|
189
|
+
return False
|
190
|
+
if request.member.hasPermission(props.requires) or request.user.is_superuser:
|
191
|
+
return True
|
192
|
+
|
193
|
+
# this a unauthorized attempt to change field, log and throw exception
|
194
|
+
if props.notify and request.member:
|
195
|
+
subject = "permission denied changing protected '{}' field".format(full_key)
|
196
|
+
msg = "permission denied changing protected field '{}'\nby user: {}\nfor: {}".format(
|
197
|
+
full_key,
|
198
|
+
request.user.username,
|
199
|
+
self
|
200
|
+
)
|
201
|
+
request.member.notifyWithPermission(props.notify, subject, msg, email_only=True)
|
202
|
+
raise re.PermissionDeniedException(subject)
|
203
|
+
|
204
|
+
def setProperties(self, data, category=None, request=None, using=None):
|
205
|
+
for k, v in data.items():
|
206
|
+
self.setProperty(k, v, category, request=request, using=using)
|
207
|
+
|
208
|
+
def setProperty(self, key, value, category=None, request=None, using=None, ascii_only=False, encrypted=False):
|
209
|
+
# rh.log_print("{}:{} ({})".format(key, value, type(value)))
|
210
|
+
if ascii_only and isinstance(value, str):
|
211
|
+
value = "".join([x for x in value if x in string.printable])
|
212
|
+
if encrypted:
|
213
|
+
value = ENCRYPTER.encrypt(value)
|
214
|
+
on_change = None
|
215
|
+
if not using:
|
216
|
+
using = getattr(self.RestMeta, "DATABASE", using)
|
217
|
+
if not request:
|
218
|
+
request = rh.getActiveRequest()
|
219
|
+
self.__initFieldProps()
|
220
|
+
|
221
|
+
if isinstance(value, dict):
|
222
|
+
return self.setProperties(value, key)
|
223
|
+
username = "root"
|
224
|
+
if request and request.member:
|
225
|
+
username = request.member.username
|
226
|
+
prop = None
|
227
|
+
|
228
|
+
if "." in key:
|
229
|
+
category, key = key.split('.')
|
230
|
+
if category:
|
231
|
+
# delete any keys with this category name
|
232
|
+
full_key = "{}.{}".format(category, key)
|
233
|
+
# this deletes anything with the key that matches the category
|
234
|
+
# this works because the category is stored not in key but category field
|
235
|
+
# rh.log_print("deleting key={}".format(category))
|
236
|
+
self.properties.filter(key=category).delete()
|
237
|
+
else:
|
238
|
+
full_key = key
|
239
|
+
|
240
|
+
field_props = self.getFieldProps(full_key)
|
241
|
+
if not self.checkFieldPerms(full_key, field_props, request):
|
242
|
+
return False
|
243
|
+
|
244
|
+
check_value = "{}".format(value)
|
245
|
+
has_changed = False
|
246
|
+
prop = self.properties.filter(category=category, key=key).last()
|
247
|
+
old_value = None
|
248
|
+
if prop:
|
249
|
+
# existing property we need to make sure we delete
|
250
|
+
old_value = prop.getValue()
|
251
|
+
if value is None or value == "":
|
252
|
+
prop.delete()
|
253
|
+
has_changed = True
|
254
|
+
else:
|
255
|
+
has_changed = check_value != prop.value
|
256
|
+
if not has_changed:
|
257
|
+
return
|
258
|
+
prop.setValue(value)
|
259
|
+
prop.save(using=using)
|
260
|
+
if field_props.on_change:
|
261
|
+
field_props.on_change(key, value, old_value, category)
|
262
|
+
elif value is None or value == "":
|
263
|
+
# do not create none or empty property
|
264
|
+
return False
|
265
|
+
else:
|
266
|
+
has_changed = True
|
267
|
+
PropClass = self.get_fk_model("properties")
|
268
|
+
prop = PropClass(parent=self, key=key, category=category)
|
269
|
+
prop.setValue(value)
|
270
|
+
# rh.log_print(u"saving {}.{}".format(category, key))
|
271
|
+
# rh.log_print(u"saving {} : {}".format(full_key, value))
|
272
|
+
prop.save(using=using)
|
273
|
+
|
274
|
+
if hasattr(self, "_recordRestChange"):
|
275
|
+
self._recordRestChange("metadata.{}".format(full_key), old_value)
|
276
|
+
|
277
|
+
if field_props.notify and request and request.member:
|
278
|
+
notify = field_props.get("notify")
|
279
|
+
if value:
|
280
|
+
value = str(value)
|
281
|
+
if len(value) > 5:
|
282
|
+
value = "***"
|
283
|
+
msg = "protected field '{}' changed to '{}'\nby user: {}\nfor: {}".format(
|
284
|
+
full_key,
|
285
|
+
value,
|
286
|
+
username,
|
287
|
+
self
|
288
|
+
)
|
289
|
+
request.member.notifyWithPermission(notify, "protected '{}' field changed".format(full_key), msg, email_only=True)
|
290
|
+
return has_changed
|
291
|
+
|
292
|
+
def getProperty(self, key, default=None, category=None, field_type=None, decrypted=False):
|
293
|
+
try:
|
294
|
+
if "." in key:
|
295
|
+
category, key = key.split('.')
|
296
|
+
value = self.properties.get(category=category, key=key).getValue(field_type)
|
297
|
+
if decrypted and value is not None:
|
298
|
+
return DECRYPTER.decrypt(value)
|
299
|
+
return value
|
300
|
+
except Exception:
|
301
|
+
pass
|
302
|
+
return default
|
rest/serializers/collection.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
from . import model as ms
|
2
2
|
from django.db.models.query import QuerySet
|
3
|
+
from rest import settings
|
3
4
|
# from . import profiler
|
4
5
|
|
5
6
|
|
@@ -18,7 +19,7 @@ def to_list_from_graph(qset, graph, sort=None, size=25, start=0):
|
|
18
19
|
recurse_into = graph.get("recurse_into", [])
|
19
20
|
return to_list(qset, sort, size, fields=fields, extra=extra, exclude=exclude, recurse_into=recurse_into)
|
20
21
|
|
21
|
-
|
22
|
+
|
22
23
|
def to_list(qset, sort=None, size=25, start=0, fields=[], extra=[], exclude=[], recurse_into=[], cache=None):
|
23
24
|
if cache is None:
|
24
25
|
cache = dict()
|
@@ -34,6 +35,11 @@ def to_list(qset, sort=None, size=25, start=0, fields=[], extra=[], exclude=[],
|
|
34
35
|
qset = qset[start:start+size+1]
|
35
36
|
if not fields:
|
36
37
|
fields = ms.get_fields(qset)
|
38
|
+
if settings.REST_SELECT_RELATED:
|
39
|
+
# this should improve speed greatly for lookups
|
40
|
+
foreign_fields = ms.get_foreign_fields(qset.model, fields)
|
41
|
+
if foreign_fields:
|
42
|
+
qset = qset.select_related(*foreign_fields)
|
37
43
|
data = []
|
38
44
|
output["data"] = data
|
39
45
|
for obj in qset:
|
rest/serializers/legacy.py
CHANGED
@@ -36,7 +36,7 @@ import inspect
|
|
36
36
|
import base64
|
37
37
|
import copy
|
38
38
|
import json
|
39
|
-
|
39
|
+
from . import profiler
|
40
40
|
|
41
41
|
CHUNKDIR = os.path.join(os.environ.get('TMPDIR', '/tmp'), 'chunked_uploads')
|
42
42
|
|
@@ -495,7 +495,7 @@ def updateFeature(func, request, qset, featuresets={}, features=[], **kwargs):
|
|
495
495
|
return None
|
496
496
|
|
497
497
|
|
498
|
-
|
498
|
+
@profiler.timeit
|
499
499
|
def restGet(request, qset, model=None, fields=None, extra=[], exclude=[], fkey_depth=0, recurse_into=[], filter={}, orig_request=None, orig_objs={}, ignore_noattr=False, require_perms=[], accept_list=None, featuresets={}, features=[], return_httpresponse=True):
|
500
500
|
"""
|
501
501
|
request: HttpRequest object
|
@@ -823,7 +823,7 @@ def restListOther(request, qset, size=25, start=0, sort=None, accept_list=None,
|
|
823
823
|
return restResult(request, ret, accept_list)
|
824
824
|
|
825
825
|
|
826
|
-
|
826
|
+
@profiler.timeit
|
827
827
|
def restList(request, qset, model=None, size=25, start=0, sort=None, fields=None, extra=[], exclude=[], fkey_depth=0, recurse_into=[], filter={}, get_count=True, orig_request=None, orig_objs={}, page=None, ignore_noattr=False, todata=lambda x: x, require_perms=[], accept_list=None, featuresets={}, features=[], return_httpresponse=True, totals=None):
|
828
828
|
"""
|
829
829
|
request: HttpRequest object
|
rest/serializers/model.py
CHANGED
@@ -169,3 +169,26 @@ def get_fields(qset, model=None):
|
|
169
169
|
elif model and hasattr(model, '_meta'):
|
170
170
|
fields = model._meta.fields
|
171
171
|
return [f.name for f in fields] if fields else None
|
172
|
+
|
173
|
+
|
174
|
+
def get_foreign_fields(model, fields):
|
175
|
+
fkeys = objict()
|
176
|
+
ignore_keys = []
|
177
|
+
for key in fields:
|
178
|
+
if "." in key:
|
179
|
+
fields = key.split('.')
|
180
|
+
skey = fields.pop(0)
|
181
|
+
if skey not in fkeys and skey not in ignore_keys:
|
182
|
+
fm = model.get_fk_model(skey)
|
183
|
+
if not fm:
|
184
|
+
ignore_keys.append(skey)
|
185
|
+
continue
|
186
|
+
fkeys[skey] = objict(fields=[".".join(fields)], model=fm)
|
187
|
+
else:
|
188
|
+
fkeys[skey].fields.append(".".join(fields))
|
189
|
+
|
190
|
+
output = list(fkeys.keys())
|
191
|
+
for fkey, info in fkeys.items():
|
192
|
+
for sub_key in get_foreign_fields(info.model, info.fields):
|
193
|
+
output.append(f"{fkey}__{sub_key}")
|
194
|
+
return output
|
File without changes
|
File without changes
|