django-restit 4.2.72__py3-none-any.whl → 4.2.73__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.
- account/models/device.py +13 -2
- account/models/member.py +8 -1
- account/models/membership.py +3 -2
- account/models/session.py +1 -1
- account/rpc/auth.py +8 -1
- account/rpc/device.py +7 -0
- {django_restit-4.2.72.dist-info → django_restit-4.2.73.dist-info}/METADATA +1 -1
- {django_restit-4.2.72.dist-info → django_restit-4.2.73.dist-info}/RECORD +13 -13
- incident/models/rules.py +17 -0
- metrics/tq.py +1 -1
- rest/models/base.py +64 -17
- {django_restit-4.2.72.dist-info → django_restit-4.2.73.dist-info}/LICENSE.md +0 -0
- {django_restit-4.2.72.dist-info → django_restit-4.2.73.dist-info}/WHEEL +0 -0
account/models/device.py
CHANGED
@@ -113,9 +113,20 @@ class MemberDeviceMetaData(rm.MetaDataBase):
|
|
113
113
|
|
114
114
|
class CloudCredentials(models.Model, rm.RestModel, rm.MetaDataModel):
|
115
115
|
"""
|
116
|
-
|
117
|
-
This can include mobile and desktop devices.
|
116
|
+
CloudCredentials is a global setting for a group to store this groups cloud credentials.
|
118
117
|
"""
|
118
|
+
class RestMeta:
|
119
|
+
VIEW_PERMS = ["view_cm", "manage_cm"]
|
120
|
+
EDIT_PERMS = ["manage_cm"]
|
121
|
+
GRAPHS = {
|
122
|
+
"default": {
|
123
|
+
"extra": ["metadata"],
|
124
|
+
"graphs": {
|
125
|
+
"group": "basic"
|
126
|
+
}
|
127
|
+
}
|
128
|
+
}
|
129
|
+
|
119
130
|
created = models.DateTimeField(auto_now_add=True)
|
120
131
|
modified = models.DateTimeField(auto_now=True)
|
121
132
|
group = models.ForeignKey("account.Group", related_name="cloud_credentials", on_delete=models.CASCADE)
|
account/models/member.py
CHANGED
@@ -88,7 +88,8 @@ class Member(User, RestModel, MetaDataModel):
|
|
88
88
|
("perms", "properties|permissions.")]
|
89
89
|
QUERY_FIELDS_SPECIAL = {
|
90
90
|
"ip": "auth_sessions__ip",
|
91
|
-
"login_country": "auth_sessions__location__country"
|
91
|
+
"login_country": "auth_sessions__location__country",
|
92
|
+
"not_login_country": "auth_sessions__location__country__ne"
|
92
93
|
}
|
93
94
|
UNIQUE_LOOKUP = ["username", "email"]
|
94
95
|
METADATA_FIELD_PROPERTIES = settings.USER_METADATA_PROPERTIES
|
@@ -221,6 +222,10 @@ class Member(User, RestModel, MetaDataModel):
|
|
221
222
|
return token.secure_token
|
222
223
|
return None
|
223
224
|
|
225
|
+
@property
|
226
|
+
def force_single_session(self):
|
227
|
+
return self.getProperty("force_single_session", category="permissions", default=None)
|
228
|
+
|
224
229
|
@property
|
225
230
|
def has_totp(self):
|
226
231
|
token = self.getProperty("totp_token", category="secrets", default=None)
|
@@ -847,6 +852,8 @@ class Member(User, RestModel, MetaDataModel):
|
|
847
852
|
if token is None:
|
848
853
|
token = AuthToken(member=self, role="default")
|
849
854
|
token.generateToken()
|
855
|
+
elif action == "refresh_keys":
|
856
|
+
self.refreshSecurityToken()
|
850
857
|
|
851
858
|
def set_full_name(self, value):
|
852
859
|
self.set_name(value)
|
account/models/membership.py
CHANGED
@@ -82,13 +82,14 @@ class Membership(models.Model, RestModel, MetaDataModel):
|
|
82
82
|
if value == "resend_invite":
|
83
83
|
self.sendInvite(self.getActiveRequest())
|
84
84
|
|
85
|
-
def sendInvite(self, request=None, url=None, subject=None, site_logo=None, company_name=None):
|
85
|
+
def sendInvite(self, request=None, url=None, subject=None, site_logo=None, company_name=None, msg=None):
|
86
86
|
if request:
|
87
87
|
powered_by = request.DATA.get("powered_by", True)
|
88
88
|
subject = request.DATA.get("invite_subject")
|
89
89
|
url = request.DATA.get("invite_url")
|
90
90
|
site_logo = request.DATA.get("site_logo", settings.SITE_LOGO)
|
91
91
|
company_name = request.DATA.get("company_name", settings.COMPANY_NAME)
|
92
|
+
msg = request.DATA.get("invite_msg", None)
|
92
93
|
else:
|
93
94
|
powered_by = True
|
94
95
|
site_logo = settings.SITE_LOGO
|
@@ -109,7 +110,7 @@ class Membership(models.Model, RestModel, MetaDataModel):
|
|
109
110
|
url = "{}&auth_code={}".format(url, auth_token.toBase64())
|
110
111
|
self.member.sendInvite(
|
111
112
|
subject, self.group, url=url,
|
112
|
-
msg=
|
113
|
+
msg=msg,
|
113
114
|
POWERED_BY=powered_by,
|
114
115
|
SITE_LOGO=site_logo,
|
115
116
|
COMPANY_NAME=company_name,
|
account/models/session.py
CHANGED
@@ -8,7 +8,7 @@ from rest import ua
|
|
8
8
|
# replacing legacy cookie session system with more robust session info
|
9
9
|
class AuthSession(models.Model, RestModel):
|
10
10
|
class RestMeta:
|
11
|
-
SEARCH_FIELDS = ["ip", "member__username", "browser", "location__city"]
|
11
|
+
SEARCH_FIELDS = ["ip", "member__username", "browser", "location__city", "buid"]
|
12
12
|
VIEW_PERMS = ["view_members", "manage_members", "manage_users", "owner"]
|
13
13
|
CAN_SAVE = False
|
14
14
|
CAN_DELETE = False
|
account/rpc/auth.py
CHANGED
@@ -12,6 +12,7 @@ from django.http import HttpResponse
|
|
12
12
|
from datetime import datetime, timedelta
|
13
13
|
|
14
14
|
ALLOW_BASIC_LOGIN = settings.get("ALLOW_BASIC_LOGIN", False)
|
15
|
+
FORGET_ALWAYS_TRUE = settings.get("FORGET_ALWAYS_TRUE", True)
|
15
16
|
|
16
17
|
|
17
18
|
@rd.urlPOST(r'^login$')
|
@@ -301,14 +302,20 @@ def member_forgot_password(request):
|
|
301
302
|
"""
|
302
303
|
member, resp = get_member_from_request(request)
|
303
304
|
if member is None:
|
305
|
+
if FORGET_ALWAYS_TRUE:
|
306
|
+
return rv.restStatus(request, True, msg="Password reset instructions have been sent to your email. (If valid account)")
|
304
307
|
return resp
|
305
308
|
if resp is not None and not member.is_active:
|
309
|
+
if FORGET_ALWAYS_TRUE:
|
310
|
+
return rv.restStatus(request, True, msg="Password reset instructions have been sent to your email. (If valid account)")
|
306
311
|
return resp
|
307
312
|
|
308
313
|
if request.DATA.get("use_code", False):
|
309
314
|
return member_forgot_password_code(request, member)
|
310
315
|
|
311
316
|
if member.auth_code is not None and member.auth_code_expires > datetime.now():
|
317
|
+
if FORGET_ALWAYS_TRUE:
|
318
|
+
return rv.restStatus(request, True, msg="Password reset instructions have been sent to your email. (If valid account)")
|
312
319
|
return rv.restPermissionDenied(request, "already sent valid auth code")
|
313
320
|
|
314
321
|
member.auth_code = crypto.randomString(16)
|
@@ -325,7 +332,7 @@ def member_forgot_password(request):
|
|
325
332
|
'to': [member.email],
|
326
333
|
})
|
327
334
|
|
328
|
-
return rv.restStatus(request, True, msg="Password reset instructions have been sent to your email.")
|
335
|
+
return rv.restStatus(request, True, msg="Password reset instructions have been sent to your email. (If valid account)")
|
329
336
|
|
330
337
|
|
331
338
|
def member_forgot_password_code(request, member):
|
account/rpc/device.py
CHANGED
@@ -5,6 +5,13 @@ from account import models as am
|
|
5
5
|
from objict import objict
|
6
6
|
|
7
7
|
|
8
|
+
@rd.url(r'^group/cloud/credentials$')
|
9
|
+
@rd.url(r'^group/cloud/credentials/(?P<pk>\d+)$')
|
10
|
+
@rd.login_required
|
11
|
+
def rest_on_group_cloud_creds(request, pk=None):
|
12
|
+
return am.CloudCredentials.on_rest_request(request, pk)
|
13
|
+
|
14
|
+
|
8
15
|
@rd.url(r'^member/device$')
|
9
16
|
@rd.url(r'^member/device/(?P<pk>\d+)$')
|
10
17
|
@rd.login_required
|
@@ -24,15 +24,15 @@ account/migrations/0019_group_location.py,sha256=EfMB_w4qWUGDqQeNc453PFZwpjpTeoA
|
|
24
24
|
account/migrations/0020_cloudcredentials_cloudcredentialsmetadata.py,sha256=mHwxkyDfA4ueQOt34w5ndJB4XwNTDLv79CkKgzhlz-c,2250
|
25
25
|
account/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
26
26
|
account/models/__init__.py,sha256=cV_lMnT2vL_mjiYtT4hlcIHo52ocFbGSNVkOIHHLXZY,385
|
27
|
-
account/models/device.py,sha256=
|
27
|
+
account/models/device.py,sha256=TloXvvrx3khF3BeGFuVYn6DhXjOW0AMZb4F9Fl5nBII,5491
|
28
28
|
account/models/feeds.py,sha256=vI7fG4ASY1M0Zjke24RdnfDcuWeATl_yR_25jPmT64g,2011
|
29
29
|
account/models/group.py,sha256=iDD_oSgswKV_t_gXZuVK80MvICrZZqdANm2jtGtOFy8,21985
|
30
30
|
account/models/legacy.py,sha256=zYdtv4LC0ooxPVqWM-uToPwV-lYWQLorSE6p6yn1xDw,2720
|
31
|
-
account/models/member.py,sha256=
|
32
|
-
account/models/membership.py,sha256=
|
31
|
+
account/models/member.py,sha256=yM1tB0eXH5FZ49KPb2MKlt22v8qFOwcQRbO2_KRt5gw,51053
|
32
|
+
account/models/membership.py,sha256=90EpAhOsGaqphDAkONP6j_qQ0OWSRaQsI8H7E7fgMkE,9249
|
33
33
|
account/models/notify.py,sha256=YnZujSHJHY7B09e6FIyZIEJRWLPYk1Sk1e92tFzB1IA,12078
|
34
34
|
account/models/passkeys.py,sha256=TJxITUi4DT4_1tW2K7ZlOcRjJuMVl2NtKz7pKQU8-Tw,1516
|
35
|
-
account/models/session.py,sha256=
|
35
|
+
account/models/session.py,sha256=ELkWjB_2KXQvPtRPrvuGJpJsqrxCQX_4J53SbqGz_2U,3737
|
36
36
|
account/models/settings.py,sha256=gOyRWBVd3BQpjfj_hJPtqX3H46ztyRAFxBrPbv11lQg,2137
|
37
37
|
account/oauth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
38
38
|
account/oauth/google.py,sha256=q5M6Qhpfp9QslKRVYFZBvtG6kgXV6vYMrR5fp6Xdb9I,2078
|
@@ -40,8 +40,8 @@ account/passkeys/__init__.py,sha256=FwXYJXwSJXfkLojGBcVpF1dFpgFhzDdd9N_3naYQ0cc,
|
|
40
40
|
account/passkeys/core.py,sha256=xj-vXjSrfWDvc5MYtEmXzwaMkNHl-cXrQKVrN9soRCg,4126
|
41
41
|
account/periodic.py,sha256=-u0n-7QTJgDOkasGhBAPwHAwjpqWGA-MZLEFkVTqCGU,874
|
42
42
|
account/rpc/__init__.py,sha256=SGF0M_-H0dKh3b1apSX29BotNWAvITYccGQVC0MIjL8,336
|
43
|
-
account/rpc/auth.py,sha256=
|
44
|
-
account/rpc/device.py,sha256=
|
43
|
+
account/rpc/auth.py,sha256=vcsAUCj-iyZEZA2D20KL4h7w7aeN2qgwoD_eUs0nJpQ,16452
|
44
|
+
account/rpc/device.py,sha256=mB14a6qvJIBnCa9ivLhPXwEt5Gk2foyqsKBtZxC506k,3070
|
45
45
|
account/rpc/group.py,sha256=FD9GymgPY68y-gtDLsZxYVdwQJeLGpqcP4hjcDUh-GM,4022
|
46
46
|
account/rpc/member.py,sha256=PU-Uz5KUI_BZFy-F-taDqAfnt_AwONYXSzUvfm7eyTw,1264
|
47
47
|
account/rpc/notify.py,sha256=Q2YWejP36egeF060Hih5uX4Psv_B8NWlLLPi7iDYlIw,3344
|
@@ -110,7 +110,7 @@ incident/models/__init__.py,sha256=NMphuhb0RTMf7Ov4QkNv7iv6_I8Wtr3xQ54yjX_a31M,2
|
|
110
110
|
incident/models/event.py,sha256=WRNzvjo0jypdnQNBksOOyi-_0kVT4qWUrDZf0Aw_MPM,7355
|
111
111
|
incident/models/incident.py,sha256=Jx0RnOo70BzPX4BLMVF8jB5UOYwFPKPlxWznkTeMzOU,19332
|
112
112
|
incident/models/ossec.py,sha256=p1ptr-8lnaj1EP_VmPR58b2LmaYBGaYYKAMqhWK5yZM,2227
|
113
|
-
incident/models/rules.py,sha256=
|
113
|
+
incident/models/rules.py,sha256=SMlDRw_r3fGv-vmRojRLmsklqRRxDcjrSLVBIz-gadA,6884
|
114
114
|
incident/models/ticket.py,sha256=S3kqGQpYLE6Y4M9IKu_60sgW-f592xNr8uufqHnvDoU,2302
|
115
115
|
incident/parsers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
116
116
|
incident/parsers/ossec.py,sha256=Bc82n0AeXMBxMxzfAR-1puHyxldcikqeu5MeGRk1zMc,7142
|
@@ -351,7 +351,7 @@ metrics/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU
|
|
351
351
|
metrics/providers/aws.py,sha256=RDM5RLeFADHexm4cHaJdAm3K6iz1NwMSNcV9GYuGtjY,7432
|
352
352
|
metrics/rpc.py,sha256=L6gjRAK7MNzu9haG7Uw9ECWDCdYWknM_ft7kYr4H0ts,20412
|
353
353
|
metrics/settings.py,sha256=wwHA9Z7BAHNeu3tFVn8Fh5j46KR-eGx0E8r5dzCFlAU,132
|
354
|
-
metrics/tq.py,sha256=
|
354
|
+
metrics/tq.py,sha256=WHBRYSinmTuxF9l-_-lx0yfzEYkb0ffVMt_uvCj9bYo,825
|
355
355
|
metrics/utils.py,sha256=w6H2v8zjlOZ5uqZsJOQvZoN-2Kyv1h8PN76gMGow7AE,11995
|
356
356
|
pushit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
357
357
|
pushit/admin.py,sha256=69HdDZU_Iz8Fm72M8r8FUztsZvW37zdGwVmj8VTqr0c,451
|
@@ -400,7 +400,7 @@ rest/middleware/request.py,sha256=JchRNy5L-bGd-7h-KFYekGRvREe2eCkZXKOYqIkP2hI,41
|
|
400
400
|
rest/middleware/session.py,sha256=zHSoQpIzRLmpqr_JvW406wzpvU3W3gDbm5JhtzLAMlE,10240
|
401
401
|
rest/middleware/session_store.py,sha256=1nSdeXK8PyuYgGgIufqrS6j6QpIrQ7zbMNT0ol75e6U,1901
|
402
402
|
rest/models/__init__.py,sha256=M8pvFDq-WCF-QcM58X7pMufYYe0aaQ3U0PwGe9TKbbY,130
|
403
|
-
rest/models/base.py,sha256=
|
403
|
+
rest/models/base.py,sha256=MIZUQStR5Y2ndSjmOSu-NSIg3SZs9IFoMlRQ2re75OE,69565
|
404
404
|
rest/models/cacher.py,sha256=eKz8TINVhWEqKhJGMsRkKZTtBUIv5rN3NHbZwOC56Uk,578
|
405
405
|
rest/models/metadata.py,sha256=65GvfFbc26_7wJz8qEAzU7fEOZWVz0ttO5j5m_gs4hk,12860
|
406
406
|
rest/net.py,sha256=LcB2QV6VNRtsSdmiQvYZgwQUDwOPMn_VBdRiZ6OpI-I,2974
|
@@ -501,7 +501,7 @@ ws4redis/servers/uwsgi.py,sha256=VyhoCI1DnVFqBiJYHoxqn5Idlf6uJPHvfBKgkjs34mo,172
|
|
501
501
|
ws4redis/settings.py,sha256=K0yBiLUuY81iDM4Yr-k8hbvjn5VVHu5zQhmMK8Dtz0s,1536
|
502
502
|
ws4redis/utf8validator.py,sha256=S0OlfjeGRP75aO6CzZsF4oTjRQAgR17OWE9rgZdMBZA,5122
|
503
503
|
ws4redis/websocket.py,sha256=R0TUyPsoVRD7Y_oU7w2I6NL4fPwiz5Vl94-fUkZgLHA,14848
|
504
|
-
django_restit-4.2.
|
505
|
-
django_restit-4.2.
|
506
|
-
django_restit-4.2.
|
507
|
-
django_restit-4.2.
|
504
|
+
django_restit-4.2.73.dist-info/LICENSE.md,sha256=VHN4hhEeVOoFjtG-5fVv4jesA4SWi0Z-KgOzzN6a1ps,1068
|
505
|
+
django_restit-4.2.73.dist-info/METADATA,sha256=On1RFDnzXlyZJmbbSLrg9QAPXMYm2vNuDmrX9OVCP8U,7645
|
506
|
+
django_restit-4.2.73.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
507
|
+
django_restit-4.2.73.dist-info/RECORD,,
|
incident/models/rules.py
CHANGED
@@ -26,6 +26,7 @@ added to to same incident.
|
|
26
26
|
class Rule(models.Model, rm.RestModel):
|
27
27
|
class RestMeta:
|
28
28
|
SEARCH_FIELDS = ["name", "group__name"]
|
29
|
+
POST_SAVE_FIELDS = ["checks"]
|
29
30
|
CAN_DELETE = True
|
30
31
|
VIEW_PERMS = ["view_incidents"]
|
31
32
|
# VIEW_PERMS = ["example_permission"]
|
@@ -50,6 +51,11 @@ class Rule(models.Model, rm.RestModel):
|
|
50
51
|
"bundle_by",
|
51
52
|
"match_by"
|
52
53
|
]
|
54
|
+
},
|
55
|
+
"download": {
|
56
|
+
"graphs": {
|
57
|
+
"checks": "basic"
|
58
|
+
}
|
53
59
|
}
|
54
60
|
}
|
55
61
|
|
@@ -102,6 +108,17 @@ class Rule(models.Model, rm.RestModel):
|
|
102
108
|
return True
|
103
109
|
return False
|
104
110
|
|
111
|
+
def set_checks(self, value):
|
112
|
+
if not isinstance(value, list):
|
113
|
+
return
|
114
|
+
exclude = ["id", "pk", "created", "modified", "parent"]
|
115
|
+
for item in value:
|
116
|
+
nval = {key: item[key] for key in item if key not in exclude and RuleCheck.hasField(key)}
|
117
|
+
nval["parent"] = self.id
|
118
|
+
rh.debug(nval)
|
119
|
+
obj = RuleCheck.createFromDict(self.getActiveRequest(), nval)
|
120
|
+
rh.debug("created", obj)
|
121
|
+
|
105
122
|
|
106
123
|
class RuleCheck(models.Model, rm.RestModel):
|
107
124
|
class RestMeta:
|
metrics/tq.py
CHANGED
@@ -16,7 +16,7 @@ def checkDomains(task):
|
|
16
16
|
alarm_date = now + timedelta(days=30)
|
17
17
|
result = ssl_check.check(*settings.DOMAIN_WATCH)
|
18
18
|
for key, value in result.items():
|
19
|
-
if value is None:
|
19
|
+
if value is None or isinstance(value, str):
|
20
20
|
continue
|
21
21
|
if value < alarm_date:
|
22
22
|
task.log(f"{key} is expiring soon")
|
rest/models/base.py
CHANGED
@@ -394,6 +394,8 @@ class RestModel(object):
|
|
394
394
|
return
|
395
395
|
if not hasattr(self, "_field_names__"):
|
396
396
|
self._field_names__ = [f.name for f in self._meta.get_fields()]
|
397
|
+
# if not hasattr(self, "_related_field_names__"):
|
398
|
+
# self._related_field_names__ = self.get_related_name_fields()
|
397
399
|
# print "saving field: {0} = {1}".format(fieldname, value)
|
398
400
|
if fieldname in RestModel.__RestMeta__.NO_SAVE_FIELDS:
|
399
401
|
return
|
@@ -479,8 +481,6 @@ class RestModel(object):
|
|
479
481
|
if hasattr(self, fieldname) and getattr(self, fieldname) != value:
|
480
482
|
self._changed__[fieldname] = getattr(self, fieldname)
|
481
483
|
setattr(self, fieldname, value)
|
482
|
-
# else:
|
483
|
-
# print "does not have field: {0}".format(fieldname)
|
484
484
|
|
485
485
|
def saveFromRequest(self, request, **kwargs):
|
486
486
|
if "files" not in kwargs:
|
@@ -1064,6 +1064,10 @@ class RestModel(object):
|
|
1064
1064
|
if hasattr(cls.RestMeta, "FORMATS"):
|
1065
1065
|
fields = cls.RestMeta.FORMATS.get(format, fields)
|
1066
1066
|
if len(fields) == 0:
|
1067
|
+
if format == "json":
|
1068
|
+
g = cls.getGraph("default")
|
1069
|
+
if "fields" in g:
|
1070
|
+
return g["fields"]
|
1067
1071
|
no_show_fields = RestModel.__RestMeta__.NO_SHOW_FIELDS
|
1068
1072
|
if hasattr(cls.RestMeta, "NO_SHOW_FIELDS"):
|
1069
1073
|
no_show_fields = cls.RestMeta.NO_SHOW_FIELDS
|
@@ -1077,12 +1081,17 @@ class RestModel(object):
|
|
1077
1081
|
def on_rest_list_format(cls, request, format, qset):
|
1078
1082
|
if format in ["summary", "summary_only"]:
|
1079
1083
|
return cls.on_rest_list_summary(request, qset)
|
1080
|
-
fields =
|
1084
|
+
fields = None
|
1085
|
+
if format == "json":
|
1086
|
+
g = cls.getGraph(request.DATA.get("graph", "download"))
|
1087
|
+
if g and "fields" in g:
|
1088
|
+
fields = g["fields"]
|
1089
|
+
if fields is None:
|
1090
|
+
fields = cls.getRestFormatFields(format)
|
1081
1091
|
if fields or format == "json":
|
1082
1092
|
name = request.DATA.get("format_filename", None)
|
1083
1093
|
format_size = request.DATA.get("format_size", 10000)
|
1084
1094
|
localize = request.DATA.get("localize", None, field_type=dict)
|
1085
|
-
rh.debug("localize", localize)
|
1086
1095
|
if name is None:
|
1087
1096
|
ext = format
|
1088
1097
|
if "_" in ext:
|
@@ -1157,6 +1166,14 @@ class RestModel(object):
|
|
1157
1166
|
output[lbl] = rh.getMax(act_qset, field)
|
1158
1167
|
return GRAPH_HELPERS.restGet(request, output)
|
1159
1168
|
|
1169
|
+
@classmethod
|
1170
|
+
def getRestBatchCreateFilter(cls, item, exclude=["id", "pk", "created", "modified"], include=None):
|
1171
|
+
if isinstance(include, list):
|
1172
|
+
return {key: item[key] for key in include if key in item}
|
1173
|
+
# ignore related fields
|
1174
|
+
rfs = cls.get_related_name_fields()
|
1175
|
+
return {key: item[key] for key in item if key not in exclude and cls.hasField(key) and key not in rfs}
|
1176
|
+
|
1160
1177
|
@classmethod
|
1161
1178
|
def on_rest_batch(cls, request, action):
|
1162
1179
|
# this method is called when rest_batch='somme action'
|
@@ -1185,19 +1202,38 @@ class RestModel(object):
|
|
1185
1202
|
count = qset.update(**update_fields)
|
1186
1203
|
return GRAPH_HELPERS.restStatus(request, True, error="updated {} items".format(count))
|
1187
1204
|
elif action == "create":
|
1188
|
-
|
1189
|
-
items = []
|
1190
|
-
for item in batch_data:
|
1191
|
-
try:
|
1192
|
-
obj = cls.ro_objects().filter(**item).last()
|
1193
|
-
if not obj:
|
1194
|
-
obj.checkPermsAndSave(request, item)
|
1195
|
-
items.append(obj)
|
1196
|
-
except Exception:
|
1197
|
-
pass
|
1198
|
-
return GRAPH_HELPERS.restList(request, items)
|
1205
|
+
cls.on_rest_batch_create(request)
|
1199
1206
|
return GRAPH_HELPERS.restStatus(request, False, error="not implemented")
|
1200
1207
|
|
1208
|
+
@classmethod
|
1209
|
+
def on_rest_batch_create(cls, request):
|
1210
|
+
batch_data = request.DATA.get("batch_data")
|
1211
|
+
if isinstance(batch_data, str):
|
1212
|
+
batch_data = objict.fromJSON(batch_data)
|
1213
|
+
if isinstance(batch_data, dict):
|
1214
|
+
if "data" in batch_data:
|
1215
|
+
batch_data = batch_data["data"]
|
1216
|
+
items = []
|
1217
|
+
for item in batch_data:
|
1218
|
+
obj = cls.createFromBatch(item)
|
1219
|
+
if obj:
|
1220
|
+
items.append(obj)
|
1221
|
+
return GRAPH_HELPERS.restList(request, items)
|
1222
|
+
|
1223
|
+
@classmethod
|
1224
|
+
def createFromBatch(cls, item, request=None):
|
1225
|
+
obj = None
|
1226
|
+
try:
|
1227
|
+
rh.debug("batch item", item)
|
1228
|
+
item_filter = cls.getRestBatchCreateFilter(item)
|
1229
|
+
rh.debug("batch filters", item_filter)
|
1230
|
+
obj = cls.ro_objects().filter(**item_filter).last()
|
1231
|
+
if obj is None:
|
1232
|
+
obj = cls.createFromDict(request, item)
|
1233
|
+
except Exception:
|
1234
|
+
rh.log_exception(item)
|
1235
|
+
return obj
|
1236
|
+
|
1201
1237
|
@classmethod
|
1202
1238
|
def on_rest_create(cls, request, pk=None):
|
1203
1239
|
# permissions are checked in the save routine
|
@@ -1526,6 +1562,7 @@ class RestModel(object):
|
|
1526
1562
|
<field_name>=<operator>:value
|
1527
1563
|
"""
|
1528
1564
|
q = {}
|
1565
|
+
exq = {}
|
1529
1566
|
field_names = cls.rest_getQueryFields()
|
1530
1567
|
query_keys = request.DATA.keys()
|
1531
1568
|
special_keys = getattr(cls.RestMeta, "QUERY_FIELDS_SPECIAL", {})
|
@@ -1597,6 +1634,8 @@ class RestModel(object):
|
|
1597
1634
|
if oper == "in":
|
1598
1635
|
if isinstance(value, str) and ',' in value:
|
1599
1636
|
value = [a.strip() for a in value.split(',')]
|
1637
|
+
elif oper == "isnull":
|
1638
|
+
value = value in [True, "true", 1, "1"]
|
1600
1639
|
elif oper is None and value in ["null", "None"]:
|
1601
1640
|
key = "{}__isnull".format(key)
|
1602
1641
|
value = True
|
@@ -1607,8 +1646,12 @@ class RestModel(object):
|
|
1607
1646
|
value = rh.toInteger(value)
|
1608
1647
|
q[key] = value
|
1609
1648
|
if bool(q):
|
1610
|
-
|
1611
|
-
|
1649
|
+
exq = {key[:-4]: q[key] for key in q if key.endswith("__ne")}
|
1650
|
+
q = {key: q[key] for key in q if not key.endswith("__ne")}
|
1651
|
+
if bool(exq):
|
1652
|
+
qset = qset.exclude(**exq)
|
1653
|
+
if bool(q):
|
1654
|
+
qset = qset.filter(**q)
|
1612
1655
|
return qset
|
1613
1656
|
|
1614
1657
|
@classmethod
|
@@ -1671,6 +1714,10 @@ class RestModel(object):
|
|
1671
1714
|
'''returns the internal field type'''
|
1672
1715
|
return [field.name for field in cls._meta.fields if field.get_internal_type() == "ForeignKey"]
|
1673
1716
|
|
1717
|
+
@classmethod
|
1718
|
+
def get_related_name_fields(cls):
|
1719
|
+
return [f.related_name for f in cls._meta.related_objects]
|
1720
|
+
|
1674
1721
|
@classmethod
|
1675
1722
|
def get_fk_model(cls, fieldname):
|
1676
1723
|
'''returns None if not foreignkey, otherswise the relevant model'''
|
File without changes
|
File without changes
|