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.
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
+
@@ -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
@@ -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
- # @profiler.timeit
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:
@@ -36,7 +36,7 @@ import inspect
36
36
  import base64
37
37
  import copy
38
38
  import json
39
- # from . import profiler
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
- # @profiler.timeit
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
- # @profiler.timeit
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