simo 2.0.24__py3-none-any.whl → 2.0.26__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.
Potentially problematic release.
This version of simo might be problematic. Click here for more details.
- simo/core/__pycache__/admin.cpython-38.pyc +0 -0
- simo/core/__pycache__/api.cpython-38.pyc +0 -0
- simo/core/__pycache__/controllers.cpython-38.pyc +0 -0
- simo/core/__pycache__/forms.cpython-38.pyc +0 -0
- simo/core/__pycache__/middleware.cpython-38.pyc +0 -0
- simo/core/__pycache__/models.cpython-38.pyc +0 -0
- simo/core/__pycache__/permissions.cpython-38.pyc +0 -0
- simo/core/__pycache__/serializers.cpython-38.pyc +0 -0
- simo/core/admin.py +1 -0
- simo/core/api.py +35 -20
- simo/core/controllers.py +5 -1
- simo/core/forms.py +29 -1
- simo/core/middleware.py +1 -0
- simo/core/migrations/0032_auto_20240506_0834.py +24 -0
- simo/core/migrations/__pycache__/0032_auto_20240506_0834.cpython-38.pyc +0 -0
- simo/core/models.py +4 -2
- simo/core/permissions.py +9 -3
- simo/core/serializers.py +20 -3
- simo/core/utils/__pycache__/json.cpython-38.pyc +0 -0
- simo/core/utils/json.py +20 -0
- simo/fleet/__pycache__/forms.cpython-38.pyc +0 -0
- simo/fleet/forms.py +123 -174
- simo/generic/__pycache__/forms.cpython-38.pyc +0 -0
- simo/generic/forms.py +24 -25
- simo/users/__pycache__/admin.cpython-38.pyc +0 -0
- simo/users/__pycache__/models.cpython-38.pyc +0 -0
- simo/users/admin.py +0 -5
- simo/users/migrations/0027_permissionsrole_can_manage_components.py +18 -0
- simo/users/migrations/0028_auto_20240506_1146.py +22 -0
- simo/users/migrations/__pycache__/0027_permissionsrole_can_manage_components.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0028_auto_20240506_1146.cpython-38.pyc +0 -0
- simo/users/models.py +7 -6
- {simo-2.0.24.dist-info → simo-2.0.26.dist-info}/METADATA +1 -1
- {simo-2.0.24.dist-info → simo-2.0.26.dist-info}/RECORD +37 -29
- {simo-2.0.24.dist-info → simo-2.0.26.dist-info}/LICENSE.md +0 -0
- {simo-2.0.24.dist-info → simo-2.0.26.dist-info}/WHEEL +0 -0
- {simo-2.0.24.dist-info → simo-2.0.26.dist-info}/top_level.txt +0 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
simo/core/admin.py
CHANGED
|
@@ -60,6 +60,7 @@ class IconAdmin(admin.ModelAdmin):
|
|
|
60
60
|
@admin.register(Instance)
|
|
61
61
|
class InstanceAdmin(admin.ModelAdmin):
|
|
62
62
|
list_display = 'name', 'timezone', 'uid'
|
|
63
|
+
exclude = 'learn_fingerprints_start', 'learn_fingerprints'
|
|
63
64
|
|
|
64
65
|
|
|
65
66
|
def has_module_permission(self, request):
|
simo/core/api.py
CHANGED
|
@@ -2,11 +2,13 @@ import datetime
|
|
|
2
2
|
from calendar import monthrange
|
|
3
3
|
import pytz
|
|
4
4
|
import time
|
|
5
|
-
|
|
5
|
+
import json
|
|
6
|
+
from django.db.models import Q
|
|
6
7
|
from django.utils.translation import gettext_lazy as _
|
|
7
8
|
from django.utils import timezone
|
|
8
9
|
from django.http import HttpResponse, Http404
|
|
9
10
|
from simo.core.utils.helpers import get_self_ip, search_queryset
|
|
11
|
+
from rest_framework import status
|
|
10
12
|
from rest_framework.pagination import PageNumberPagination
|
|
11
13
|
from rest_framework import viewsets
|
|
12
14
|
from django_filters.rest_framework import DjangoFilterBackend
|
|
@@ -14,6 +16,7 @@ from rest_framework.decorators import action
|
|
|
14
16
|
from rest_framework.response import Response as RESTResponse
|
|
15
17
|
from rest_framework.exceptions import ValidationError as APIValidationError
|
|
16
18
|
from simo.core.utils.config_values import ConfigException
|
|
19
|
+
from simo.core.utils.json import restore_json
|
|
17
20
|
from .models import (
|
|
18
21
|
Instance, Category, Zone, Component, Icon, ComponentHistory,
|
|
19
22
|
HistoryAggregate, Gateway
|
|
@@ -35,11 +38,7 @@ class InstanceMixin:
|
|
|
35
38
|
slug=self.request.resolver_match.kwargs.get('instance_slug')
|
|
36
39
|
)
|
|
37
40
|
except Instance.DoesNotExist:
|
|
38
|
-
|
|
39
|
-
f"Instance {self.request.resolver_match.kwargs.get('instance_slug')} "
|
|
40
|
-
"is not found on this SIMO.io hub!",
|
|
41
|
-
status=400
|
|
42
|
-
)
|
|
41
|
+
raise Http404()
|
|
43
42
|
return super().dispatch(request, *args, **kwargs)
|
|
44
43
|
|
|
45
44
|
def get_serializer_context(self):
|
|
@@ -159,7 +158,9 @@ def get_components_queryset(instance, user):
|
|
|
159
158
|
return qs
|
|
160
159
|
|
|
161
160
|
|
|
162
|
-
class ComponentViewSet(
|
|
161
|
+
class ComponentViewSet(
|
|
162
|
+
InstanceMixin, viewsets.ModelViewSet
|
|
163
|
+
):
|
|
163
164
|
url = 'core/components'
|
|
164
165
|
basename = 'components'
|
|
165
166
|
serializer_class = ComponentSerializer
|
|
@@ -185,11 +186,14 @@ class ComponentViewSet(InstanceMixin, viewsets.ModelViewSet):
|
|
|
185
186
|
|
|
186
187
|
def perform_controller_method(self, json_data, component):
|
|
187
188
|
for method_name, param in json_data.items():
|
|
189
|
+
if method_name in ('id', 'secret'):
|
|
190
|
+
continue
|
|
188
191
|
if not hasattr(component, method_name):
|
|
189
192
|
raise APIValidationError(
|
|
190
193
|
_('"%s" method not found on controller') % method_name,
|
|
191
194
|
code=400
|
|
192
195
|
)
|
|
196
|
+
print("VARYSIM: ", method_name, param, type(param))
|
|
193
197
|
call = getattr(component, method_name)
|
|
194
198
|
|
|
195
199
|
if not isinstance(param, list) and not isinstance(param, dict):
|
|
@@ -210,10 +214,13 @@ class ComponentViewSet(InstanceMixin, viewsets.ModelViewSet):
|
|
|
210
214
|
return RESTResponse(result)
|
|
211
215
|
|
|
212
216
|
# TODO: remove post when app is updated for all users
|
|
213
|
-
@action(detail=True, methods=['post'
|
|
217
|
+
@action(detail=True, methods=['post'])
|
|
214
218
|
def subcomponent(self, request, pk=None, *args, **kwargs):
|
|
215
219
|
component = self.get_object()
|
|
216
|
-
|
|
220
|
+
data = request.data
|
|
221
|
+
if not isinstance(request.data, dict):
|
|
222
|
+
data = data.dict()
|
|
223
|
+
json_data = restore_json(data)
|
|
217
224
|
subcomponent_id = json_data.pop('id', -1)
|
|
218
225
|
try:
|
|
219
226
|
subcomponent = component.slaves.get(pk=subcomponent_id)
|
|
@@ -230,18 +237,28 @@ class ComponentViewSet(InstanceMixin, viewsets.ModelViewSet):
|
|
|
230
237
|
self.check_object_permissions(self.request, subcomponent)
|
|
231
238
|
return self.perform_controller_method(json_data, subcomponent)
|
|
232
239
|
|
|
233
|
-
@action(detail=True, methods=['post'
|
|
240
|
+
@action(detail=True, methods=['post'])
|
|
234
241
|
def controller(self, request, pk=None, *args, **kwargs):
|
|
235
242
|
component = self.get_object()
|
|
236
|
-
|
|
243
|
+
data = request.data
|
|
244
|
+
if not isinstance(request.data, dict):
|
|
245
|
+
data = data.dict()
|
|
246
|
+
request_data = restore_json(data)
|
|
247
|
+
return self.perform_controller_method(
|
|
248
|
+
restore_json(request_data), component
|
|
249
|
+
)
|
|
237
250
|
|
|
238
|
-
@action(detail=False, methods=['post'
|
|
251
|
+
@action(detail=False, methods=['post'])
|
|
239
252
|
def control(self, request, *args, **kwargs):
|
|
240
|
-
|
|
253
|
+
data = request.data
|
|
254
|
+
if not isinstance(request.data, dict):
|
|
255
|
+
data = data.dict()
|
|
256
|
+
request_data = restore_json(data)
|
|
257
|
+
component = self.get_queryset().filter(id=request_data.pop('id', 0)).first()
|
|
241
258
|
if not component:
|
|
242
259
|
raise Http404()
|
|
243
260
|
self.check_object_permissions(self.request, component)
|
|
244
|
-
return self.perform_controller_method(
|
|
261
|
+
return self.perform_controller_method(request_data, component)
|
|
245
262
|
|
|
246
263
|
@action(detail=True, methods=['get'])
|
|
247
264
|
def value_history(self, request, pk=None, *args, **kwargs):
|
|
@@ -255,6 +272,8 @@ class ComponentViewSet(InstanceMixin, viewsets.ModelViewSet):
|
|
|
255
272
|
return RESTResponse(resp_data)
|
|
256
273
|
|
|
257
274
|
|
|
275
|
+
|
|
276
|
+
|
|
258
277
|
class HistoryResultsSetPagination(PageNumberPagination):
|
|
259
278
|
page_size = 50
|
|
260
279
|
page_size_query_param = 'page_size'
|
|
@@ -532,9 +551,7 @@ class StatesViewSet(InstanceMixin, viewsets.GenericViewSet):
|
|
|
532
551
|
).exclude(email__in=('system@simo.io', 'device@simo.io'))
|
|
533
552
|
component_values = get_components_queryset(
|
|
534
553
|
self.instance, request.user
|
|
535
|
-
).filter(zone__instance=self.instance).
|
|
536
|
-
Prefetch('history', queryset=ComponentHistory.objects.filter())
|
|
537
|
-
).values(
|
|
554
|
+
).filter(zone__instance=self.instance).values(
|
|
538
555
|
'id', 'value', 'last_change', 'arm_status', 'battery_level',
|
|
539
556
|
'alive', 'meta'
|
|
540
557
|
)
|
|
@@ -623,6 +640,4 @@ class RunningDiscoveries(InstanceMixin, viewsets.GenericViewSet):
|
|
|
623
640
|
if 'controller_uid' in request.GET:
|
|
624
641
|
for gateway in gateways:
|
|
625
642
|
gateway.retry_discovery()
|
|
626
|
-
return RESTResponse(self.get_data(gateways))
|
|
627
|
-
|
|
628
|
-
|
|
643
|
+
return RESTResponse(self.get_data(gateways))
|
simo/core/controllers.py
CHANGED
|
@@ -187,6 +187,8 @@ class ControllerBase(ABC):
|
|
|
187
187
|
bulk_send_map = {self.component: value}
|
|
188
188
|
for slave in self.component.slaves.all():
|
|
189
189
|
bulk_send_map[slave] = value
|
|
190
|
+
|
|
191
|
+
print("BULK SEND MAP: ", bulk_send_map)
|
|
190
192
|
from .models import Component
|
|
191
193
|
Component.objects.bulk_send(bulk_send_map)
|
|
192
194
|
return
|
|
@@ -652,7 +654,9 @@ class MultiSwitchBase(ControllerBase):
|
|
|
652
654
|
if not(0 < number_of_values < 16):
|
|
653
655
|
raise ValidationError("Wrong number of values")
|
|
654
656
|
if number_of_values == 1:
|
|
655
|
-
if
|
|
657
|
+
if isinstance(value, int):
|
|
658
|
+
value = bool(value)
|
|
659
|
+
elif not isinstance(value, bool):
|
|
656
660
|
raise ValidationError("Must be a boolean value")
|
|
657
661
|
else:
|
|
658
662
|
if not isinstance(value, list):
|
simo/core/forms.py
CHANGED
|
@@ -195,6 +195,9 @@ class ConfigFieldsMixin:
|
|
|
195
195
|
|
|
196
196
|
def save(self, commit=True):
|
|
197
197
|
for field_name in self.config_fields:
|
|
198
|
+
# support for partial forms
|
|
199
|
+
if field_name not in self.cleaned_data:
|
|
200
|
+
continue
|
|
198
201
|
if isinstance(self.cleaned_data[field_name], models.Model):
|
|
199
202
|
self.instance.config[field_name] = \
|
|
200
203
|
self.cleaned_data[field_name].pk
|
|
@@ -282,6 +285,10 @@ class ComponentAdminForm(forms.ModelForm):
|
|
|
282
285
|
has_icon = True
|
|
283
286
|
has_alarm = True
|
|
284
287
|
|
|
288
|
+
# fields that can be edited via SIMO.io app by instance owners.
|
|
289
|
+
# Users who have is_owner enabled on their user role.
|
|
290
|
+
basic_fields = ['name', 'icon', 'zone', 'category']
|
|
291
|
+
|
|
285
292
|
class Meta:
|
|
286
293
|
model = Component
|
|
287
294
|
fields = '__all__'
|
|
@@ -324,6 +331,27 @@ class ComponentAdminForm(forms.ModelForm):
|
|
|
324
331
|
self.instance.config = self.controller.default_config
|
|
325
332
|
self.instance.meta = self.controller.default_meta
|
|
326
333
|
|
|
334
|
+
self.cleanup_missing_keys(kwargs.get("data"))
|
|
335
|
+
|
|
336
|
+
def cleanup_missing_keys(self, data):
|
|
337
|
+
"""
|
|
338
|
+
Removes missing keys from fields on form submission.
|
|
339
|
+
This avoids resetting fields that are not present in
|
|
340
|
+
the submitted data, which may be the sign of a buggy
|
|
341
|
+
or incomplete template.
|
|
342
|
+
Note that this cleanup relies on the HTML form being
|
|
343
|
+
patched to send all keys, even for checkboxes, via
|
|
344
|
+
input[type="hidden"] fields or some JS magic.
|
|
345
|
+
"""
|
|
346
|
+
if data is None:
|
|
347
|
+
# not a form submission, don't modify self.fields
|
|
348
|
+
return
|
|
349
|
+
|
|
350
|
+
got_keys = data.keys()
|
|
351
|
+
field_names = self.fields.keys()
|
|
352
|
+
for missing in set(field_names) - set(got_keys):
|
|
353
|
+
del self.fields[missing]
|
|
354
|
+
|
|
327
355
|
@classmethod
|
|
328
356
|
def get_admin_fieldsets(cls, request, obj=None):
|
|
329
357
|
main_fields = (
|
|
@@ -518,7 +546,7 @@ class SwitchForm(BaseComponentForm):
|
|
|
518
546
|
|
|
519
547
|
def save(self, commit=True):
|
|
520
548
|
obj = super().save(commit=commit)
|
|
521
|
-
if commit:
|
|
549
|
+
if commit and 'slaves' in self.cleaned_data:
|
|
522
550
|
obj.slaves.set(self.cleaned_data['slaves'])
|
|
523
551
|
return obj
|
|
524
552
|
|
simo/core/middleware.py
CHANGED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Generated by Django 3.2.9 on 2024-05-06 08:34
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
import django.db.models.deletion
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Migration(migrations.Migration):
|
|
8
|
+
|
|
9
|
+
dependencies = [
|
|
10
|
+
('core', '0031_auto_20240429_1231'),
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
operations = [
|
|
14
|
+
migrations.AlterField(
|
|
15
|
+
model_name='category',
|
|
16
|
+
name='order',
|
|
17
|
+
field=models.PositiveIntegerField(db_index=True),
|
|
18
|
+
),
|
|
19
|
+
migrations.AlterField(
|
|
20
|
+
model_name='instance',
|
|
21
|
+
name='indoor_climate_sensor',
|
|
22
|
+
field=models.ForeignKey(blank=True, limit_choices_to={'base_type__in': ['numeric-sensor', 'multi-sensor']}, null=True, on_delete=django.db.models.deletion.SET_NULL, to='core.component'),
|
|
23
|
+
),
|
|
24
|
+
]
|
|
Binary file
|
simo/core/models.py
CHANGED
|
@@ -80,7 +80,7 @@ class Instance(DirtyFieldsMixin, models.Model, SimoAdminMixin):
|
|
|
80
80
|
name = models.CharField(max_length=100, db_index=True, unique=True)
|
|
81
81
|
slug = models.CharField(max_length=100, db_index=True, unique=True)
|
|
82
82
|
cover_image = models.ImageField(
|
|
83
|
-
upload_to='hub_covers', null=True, blank=True
|
|
83
|
+
name='cover_image', upload_to='hub_covers', null=True, blank=True
|
|
84
84
|
)
|
|
85
85
|
cover_image_synced = models.BooleanField(default=False)
|
|
86
86
|
secret_key = models.CharField(max_length=100, blank=True)
|
|
@@ -100,7 +100,8 @@ class Instance(DirtyFieldsMixin, models.Model, SimoAdminMixin):
|
|
|
100
100
|
|
|
101
101
|
)
|
|
102
102
|
indoor_climate_sensor = models.ForeignKey(
|
|
103
|
-
'Component', null=True, blank=True, on_delete=models.SET_NULL
|
|
103
|
+
'Component', null=True, blank=True, on_delete=models.SET_NULL,
|
|
104
|
+
limit_choices_to={'base_type__in': ['numeric-sensor', 'multi-sensor']}
|
|
104
105
|
)
|
|
105
106
|
history_days = models.PositiveIntegerField(
|
|
106
107
|
default=90, help_text="How many days of component history do we keep?"
|
|
@@ -128,6 +129,7 @@ class Instance(DirtyFieldsMixin, models.Model, SimoAdminMixin):
|
|
|
128
129
|
def components(self):
|
|
129
130
|
return Component.objects.filter(zone__instance=self)
|
|
130
131
|
|
|
132
|
+
|
|
131
133
|
class Zone(DirtyFieldsMixin, models.Model, SimoAdminMixin):
|
|
132
134
|
instance = models.ForeignKey(Instance, on_delete=models.CASCADE)
|
|
133
135
|
name = models.CharField(_('name'), max_length=40)
|
simo/core/permissions.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from rest_framework.permissions import BasePermission, SAFE_METHODS, IsAuthenticated
|
|
2
|
+
from django.http import Http404
|
|
2
3
|
from .models import Instance, Category, Zone
|
|
3
4
|
|
|
4
5
|
|
|
@@ -13,7 +14,7 @@ class InstancePermission(BasePermission):
|
|
|
13
14
|
slug=request.resolver_match.kwargs.get('instance_slug')
|
|
14
15
|
).first()
|
|
15
16
|
if not instance:
|
|
16
|
-
|
|
17
|
+
raise Http404()
|
|
17
18
|
|
|
18
19
|
if instance not in request.user.instances:
|
|
19
20
|
return False
|
|
@@ -37,12 +38,14 @@ class InstanceSuperuserCanEdit(BasePermission):
|
|
|
37
38
|
def has_object_permission(self, request, view, obj):
|
|
38
39
|
'''
|
|
39
40
|
in this permission we only care about:
|
|
40
|
-
POST -
|
|
41
|
+
POST - new object can be created
|
|
42
|
+
PUT - create new object
|
|
41
43
|
PATCH - modify an object
|
|
42
44
|
DELETE - delete an oject
|
|
43
45
|
'''
|
|
46
|
+
# TODO: allow object creation only with PUT method, this way proper permissions system will be guaranteed.
|
|
44
47
|
|
|
45
|
-
if request.method in SAFE_METHODS + ('
|
|
48
|
+
if request.method in SAFE_METHODS + ('POST',):
|
|
46
49
|
return True
|
|
47
50
|
|
|
48
51
|
# allow deleting only empty categories and zones
|
|
@@ -56,6 +59,9 @@ class InstanceSuperuserCanEdit(BasePermission):
|
|
|
56
59
|
if user_role.is_superuser:
|
|
57
60
|
return True
|
|
58
61
|
|
|
62
|
+
if user_role.is_owner and request.method == 'PUT':
|
|
63
|
+
return True
|
|
64
|
+
|
|
59
65
|
return False
|
|
60
66
|
|
|
61
67
|
|
simo/core/serializers.py
CHANGED
|
@@ -261,6 +261,12 @@ class ComponentSerializer(FormSerializer):
|
|
|
261
261
|
self.set_form_cls()
|
|
262
262
|
|
|
263
263
|
ret = OrderedDict()
|
|
264
|
+
if not self.context['request'].user.is_master:
|
|
265
|
+
user_role = self.context['request'].user.get_role(
|
|
266
|
+
self.context['instance']
|
|
267
|
+
)
|
|
268
|
+
if not any([user_role.is_superuser, user_role.is_owner]):
|
|
269
|
+
return ret
|
|
264
270
|
|
|
265
271
|
field_mapping = reduce_attr_dict_from_instance(
|
|
266
272
|
self,
|
|
@@ -269,9 +275,10 @@ class ComponentSerializer(FormSerializer):
|
|
|
269
275
|
)
|
|
270
276
|
|
|
271
277
|
if not self.instance or isinstance(self.instance, Iterable):
|
|
272
|
-
form = self.
|
|
278
|
+
form = self.get_form()
|
|
273
279
|
else:
|
|
274
|
-
form = self.
|
|
280
|
+
form = self.get_form(instance=self.instance)
|
|
281
|
+
|
|
275
282
|
for field_name in form.fields:
|
|
276
283
|
# if field is specified as excluded field
|
|
277
284
|
if field_name in getattr(self.Meta, 'exclude', []):
|
|
@@ -355,7 +362,7 @@ class ComponentSerializer(FormSerializer):
|
|
|
355
362
|
|
|
356
363
|
def get_form(self, data=None, **kwargs):
|
|
357
364
|
self.set_form_cls()
|
|
358
|
-
if not self.instance:
|
|
365
|
+
if not self.instance or isinstance(self.instance, Iterable):
|
|
359
366
|
#controller_uid = 'simo.generic.controllers.AlarmClock'
|
|
360
367
|
controller_uid = self.context['request'].META.get('HTTP_CONTROLLER')
|
|
361
368
|
else:
|
|
@@ -365,6 +372,14 @@ class ComponentSerializer(FormSerializer):
|
|
|
365
372
|
controller_uid=controller_uid,
|
|
366
373
|
**kwargs
|
|
367
374
|
)
|
|
375
|
+
if not self.context['request'].user.is_master:
|
|
376
|
+
user_role = self.context['request'].user.get_role(
|
|
377
|
+
self.context['instance']
|
|
378
|
+
)
|
|
379
|
+
if not user_role.is_superuser and user_role.is_owner:
|
|
380
|
+
for field_name in list(form.fields.keys()):
|
|
381
|
+
if field_name not in form.basic_fields:
|
|
382
|
+
del form.fields[field_name]
|
|
368
383
|
return form
|
|
369
384
|
|
|
370
385
|
def accomodate_formsets(self, form, data):
|
|
@@ -395,6 +410,7 @@ class ComponentSerializer(FormSerializer):
|
|
|
395
410
|
)
|
|
396
411
|
form = self.get_form(instance=self.instance)
|
|
397
412
|
a_data = self.accomodate_formsets(form, data)
|
|
413
|
+
|
|
398
414
|
form = self.get_form(
|
|
399
415
|
data=a_data, instance=self.instance
|
|
400
416
|
)
|
|
@@ -410,6 +426,7 @@ class ComponentSerializer(FormSerializer):
|
|
|
410
426
|
a_data = self.accomodate_formsets(form, validated_data)
|
|
411
427
|
form = self.get_form(instance=instance, data=a_data)
|
|
412
428
|
if form.is_valid():
|
|
429
|
+
print("FORM FIELDS", form.fields)
|
|
413
430
|
instance = form.save(commit=True)
|
|
414
431
|
return instance
|
|
415
432
|
raise serializers.ValidationError(form.errors)
|
|
Binary file
|
simo/core/utils/json.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
|
|
2
|
+
def restore_json(data):
|
|
3
|
+
for key, val in data.items():
|
|
4
|
+
if not isinstance(val, str):
|
|
5
|
+
continue
|
|
6
|
+
try:
|
|
7
|
+
data[key] = int(val)
|
|
8
|
+
continue
|
|
9
|
+
except:
|
|
10
|
+
pass
|
|
11
|
+
try:
|
|
12
|
+
data[key] = float(val)
|
|
13
|
+
continue
|
|
14
|
+
except:
|
|
15
|
+
pass
|
|
16
|
+
if val.lower() == 'true':
|
|
17
|
+
data[key] = True
|
|
18
|
+
elif val.lower() == 'false':
|
|
19
|
+
data[key] = False
|
|
20
|
+
return data
|
|
Binary file
|