simo 2.0.25__py3-none-any.whl → 2.0.27__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__/api.cpython-38.pyc +0 -0
- simo/core/__pycache__/forms.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/__pycache__/tasks.cpython-38.pyc +0 -0
- simo/core/api.py +15 -3
- simo/core/forms.py +29 -1
- 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/permissions.py +5 -0
- simo/core/serializers.py +16 -3
- simo/core/tasks.py +25 -0
- simo/fleet/__pycache__/forms.cpython-38.pyc +0 -0
- simo/fleet/forms.py +121 -172
- simo/generic/__pycache__/forms.cpython-38.pyc +0 -0
- simo/generic/__pycache__/models.cpython-38.pyc +0 -0
- simo/generic/forms.py +24 -25
- simo/generic/models.py +22 -6
- simo/notifications/utils.py +4 -3
- simo/users/__pycache__/models.cpython-38.pyc +0 -0
- 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.25.dist-info → simo-2.0.27.dist-info}/METADATA +1 -1
- {simo-2.0.25.dist-info → simo-2.0.27.dist-info}/RECORD +30 -24
- {simo-2.0.25.dist-info → simo-2.0.27.dist-info}/LICENSE.md +0 -0
- {simo-2.0.25.dist-info → simo-2.0.27.dist-info}/WHEEL +0 -0
- {simo-2.0.25.dist-info → simo-2.0.27.dist-info}/top_level.txt +0 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
simo/core/api.py
CHANGED
|
@@ -217,7 +217,10 @@ class ComponentViewSet(
|
|
|
217
217
|
@action(detail=True, methods=['post'])
|
|
218
218
|
def subcomponent(self, request, pk=None, *args, **kwargs):
|
|
219
219
|
component = self.get_object()
|
|
220
|
-
|
|
220
|
+
data = request.data
|
|
221
|
+
if not isinstance(request.data, dict):
|
|
222
|
+
data = data.dict()
|
|
223
|
+
json_data = restore_json(data)
|
|
221
224
|
subcomponent_id = json_data.pop('id', -1)
|
|
222
225
|
try:
|
|
223
226
|
subcomponent = component.slaves.get(pk=subcomponent_id)
|
|
@@ -237,13 +240,20 @@ class ComponentViewSet(
|
|
|
237
240
|
@action(detail=True, methods=['post'])
|
|
238
241
|
def controller(self, request, pk=None, *args, **kwargs):
|
|
239
242
|
component = self.get_object()
|
|
243
|
+
data = request.data
|
|
244
|
+
if not isinstance(request.data, dict):
|
|
245
|
+
data = data.dict()
|
|
246
|
+
request_data = restore_json(data)
|
|
240
247
|
return self.perform_controller_method(
|
|
241
|
-
restore_json(
|
|
248
|
+
restore_json(request_data), component
|
|
242
249
|
)
|
|
243
250
|
|
|
244
251
|
@action(detail=False, methods=['post'])
|
|
245
252
|
def control(self, request, *args, **kwargs):
|
|
246
|
-
|
|
253
|
+
data = request.data
|
|
254
|
+
if not isinstance(request.data, dict):
|
|
255
|
+
data = data.dict()
|
|
256
|
+
request_data = restore_json(data)
|
|
247
257
|
component = self.get_queryset().filter(id=request_data.pop('id', 0)).first()
|
|
248
258
|
if not component:
|
|
249
259
|
raise Http404()
|
|
@@ -262,6 +272,8 @@ class ComponentViewSet(
|
|
|
262
272
|
return RESTResponse(resp_data)
|
|
263
273
|
|
|
264
274
|
|
|
275
|
+
|
|
276
|
+
|
|
265
277
|
class HistoryResultsSetPagination(PageNumberPagination):
|
|
266
278
|
page_size = 50
|
|
267
279
|
page_size_query_param = 'page_size'
|
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
|
|
|
@@ -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/permissions.py
CHANGED
|
@@ -59,6 +59,9 @@ class InstanceSuperuserCanEdit(BasePermission):
|
|
|
59
59
|
if user_role.is_superuser:
|
|
60
60
|
return True
|
|
61
61
|
|
|
62
|
+
if user_role.is_owner and request.method != 'DELETE':
|
|
63
|
+
return True
|
|
64
|
+
|
|
62
65
|
return False
|
|
63
66
|
|
|
64
67
|
|
|
@@ -73,6 +76,8 @@ class ComponentPermission(BasePermission):
|
|
|
73
76
|
user_role = request.user.get_role(view.instance)
|
|
74
77
|
if user_role.is_superuser:
|
|
75
78
|
return True
|
|
79
|
+
if user_role.is_owner and request.method != 'DELETE':
|
|
80
|
+
return True
|
|
76
81
|
if request.method == 'POST' and user_role.component_permissions.filter(
|
|
77
82
|
write=True, component=obj
|
|
78
83
|
).count():
|
simo/core/serializers.py
CHANGED
|
@@ -269,9 +269,10 @@ class ComponentSerializer(FormSerializer):
|
|
|
269
269
|
)
|
|
270
270
|
|
|
271
271
|
if not self.instance or isinstance(self.instance, Iterable):
|
|
272
|
-
form = self.
|
|
272
|
+
form = self.get_form()
|
|
273
273
|
else:
|
|
274
|
-
form = self.
|
|
274
|
+
form = self.get_form(instance=self.instance)
|
|
275
|
+
|
|
275
276
|
for field_name in form.fields:
|
|
276
277
|
# if field is specified as excluded field
|
|
277
278
|
if field_name in getattr(self.Meta, 'exclude', []):
|
|
@@ -355,7 +356,7 @@ class ComponentSerializer(FormSerializer):
|
|
|
355
356
|
|
|
356
357
|
def get_form(self, data=None, **kwargs):
|
|
357
358
|
self.set_form_cls()
|
|
358
|
-
if not self.instance:
|
|
359
|
+
if not self.instance or isinstance(self.instance, Iterable):
|
|
359
360
|
#controller_uid = 'simo.generic.controllers.AlarmClock'
|
|
360
361
|
controller_uid = self.context['request'].META.get('HTTP_CONTROLLER')
|
|
361
362
|
else:
|
|
@@ -365,6 +366,16 @@ class ComponentSerializer(FormSerializer):
|
|
|
365
366
|
controller_uid=controller_uid,
|
|
366
367
|
**kwargs
|
|
367
368
|
)
|
|
369
|
+
if not self.context['request'].user.is_master:
|
|
370
|
+
user_role = self.context['request'].user.get_role(
|
|
371
|
+
self.context['instance']
|
|
372
|
+
)
|
|
373
|
+
print("FORM BASIC FIELDS: ", form.basic_fields)
|
|
374
|
+
if not user_role.is_superuser and user_role.is_owner:
|
|
375
|
+
for field_name in list(form.fields.keys()):
|
|
376
|
+
if field_name not in form.basic_fields:
|
|
377
|
+
print("DELETE FIELD: ", field_name)
|
|
378
|
+
del form.fields[field_name]
|
|
368
379
|
return form
|
|
369
380
|
|
|
370
381
|
def accomodate_formsets(self, form, data):
|
|
@@ -395,6 +406,7 @@ class ComponentSerializer(FormSerializer):
|
|
|
395
406
|
)
|
|
396
407
|
form = self.get_form(instance=self.instance)
|
|
397
408
|
a_data = self.accomodate_formsets(form, data)
|
|
409
|
+
|
|
398
410
|
form = self.get_form(
|
|
399
411
|
data=a_data, instance=self.instance
|
|
400
412
|
)
|
|
@@ -410,6 +422,7 @@ class ComponentSerializer(FormSerializer):
|
|
|
410
422
|
a_data = self.accomodate_formsets(form, validated_data)
|
|
411
423
|
form = self.get_form(instance=instance, data=a_data)
|
|
412
424
|
if form.is_valid():
|
|
425
|
+
print("FORM FIELDS", form.fields)
|
|
413
426
|
instance = form.save(commit=True)
|
|
414
427
|
return instance
|
|
415
428
|
raise serializers.ValidationError(form.errors)
|
simo/core/tasks.py
CHANGED
|
@@ -331,6 +331,30 @@ def restart_postgresql():
|
|
|
331
331
|
proc.communicate()
|
|
332
332
|
|
|
333
333
|
|
|
334
|
+
@celery_app.task
|
|
335
|
+
def low_battery_notifications():
|
|
336
|
+
from simo.users.models import User
|
|
337
|
+
from simo.notifications.utils import notify_users
|
|
338
|
+
for instance in Instance.objects.all():
|
|
339
|
+
timezone.activate(instance.timezone)
|
|
340
|
+
if timezone.localtime().hour != 10:
|
|
341
|
+
continue
|
|
342
|
+
for comp in Component.objects.filter(
|
|
343
|
+
zone__instance=instance,
|
|
344
|
+
battery_level__isnull=False, battery_level__lt=20
|
|
345
|
+
):
|
|
346
|
+
users = User.objects.filter(
|
|
347
|
+
roles__is_owner=True, roles__instance=comp.zone.instance
|
|
348
|
+
).distinct()
|
|
349
|
+
if users:
|
|
350
|
+
notify_users(
|
|
351
|
+
comp.zone.instance, 'warning',
|
|
352
|
+
f"Low battery ({comp.battery_level}%) on {comp}",
|
|
353
|
+
component=comp, users=users
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
|
|
334
358
|
@celery_app.on_after_finalize.connect
|
|
335
359
|
def setup_periodic_tasks(sender, **kwargs):
|
|
336
360
|
sender.add_periodic_task(1, watch_timers.s())
|
|
@@ -339,3 +363,4 @@ def setup_periodic_tasks(sender, **kwargs):
|
|
|
339
363
|
sender.add_periodic_task(60 * 60 * 6, update_latest_version_available.s())
|
|
340
364
|
sender.add_periodic_task(60, drop_fingerprints_learn.s())
|
|
341
365
|
sender.add_periodic_task(60 * 60 * 24, restart_postgresql.s())
|
|
366
|
+
sender.add_periodic_task(60 * 60, low_battery_notifications.s())
|
|
Binary file
|
simo/fleet/forms.py
CHANGED
|
@@ -91,13 +91,16 @@ class ColonelComponentForm(BaseComponentForm):
|
|
|
91
91
|
def clean_colonel(self):
|
|
92
92
|
if not self.instance.pk:
|
|
93
93
|
return self.cleaned_data['colonel']
|
|
94
|
+
colonel = self.cleaned_data.get('colonel')
|
|
95
|
+
if not colonel:
|
|
96
|
+
return
|
|
94
97
|
org = self.instance.config.get('colonel')
|
|
95
|
-
if org and org !=
|
|
98
|
+
if org and org != colonel.id:
|
|
96
99
|
raise forms.ValidationError(
|
|
97
100
|
"Changing colonel after component is created "
|
|
98
101
|
"it is not allowed!"
|
|
99
102
|
)
|
|
100
|
-
return
|
|
103
|
+
return colonel
|
|
101
104
|
|
|
102
105
|
def _clean_pin(self, field_name):
|
|
103
106
|
if self.cleaned_data[field_name].colonel != self.cleaned_data['colonel']:
|
|
@@ -153,7 +156,7 @@ class ColonelComponentForm(BaseComponentForm):
|
|
|
153
156
|
|
|
154
157
|
def save(self, commit=True):
|
|
155
158
|
obj = super().save(commit)
|
|
156
|
-
if commit:
|
|
159
|
+
if commit and 'colonel' in self.cleaned_data:
|
|
157
160
|
self.cleaned_data['colonel'].components.add(obj)
|
|
158
161
|
self.cleaned_data['colonel'].rebuild_occupied_pins()
|
|
159
162
|
self.cleaned_data['colonel'].save()
|
|
@@ -211,7 +214,7 @@ class ColonelBinarySensorConfigForm(ColonelComponentForm):
|
|
|
211
214
|
|
|
212
215
|
def clean(self):
|
|
213
216
|
super().clean()
|
|
214
|
-
if not self.cleaned_data
|
|
217
|
+
if 'colonel' not in self.cleaned_data:
|
|
215
218
|
return self.cleaned_data
|
|
216
219
|
if 'pin' not in self.cleaned_data:
|
|
217
220
|
return self.cleaned_data
|
|
@@ -251,7 +254,8 @@ class ColonelBinarySensorConfigForm(ColonelComponentForm):
|
|
|
251
254
|
|
|
252
255
|
|
|
253
256
|
def save(self, commit=True):
|
|
254
|
-
|
|
257
|
+
if 'pin' in self.cleaned_data:
|
|
258
|
+
self.instance.config['pin_no'] = self.cleaned_data['pin'].no
|
|
255
259
|
return super().save(commit=commit)
|
|
256
260
|
|
|
257
261
|
|
|
@@ -291,7 +295,7 @@ class ColonelNumericSensorConfigForm(ColonelComponentForm, NumericSensorForm):
|
|
|
291
295
|
|
|
292
296
|
def clean(self):
|
|
293
297
|
super().clean()
|
|
294
|
-
if not self.cleaned_data
|
|
298
|
+
if 'colonel' not in self.cleaned_data:
|
|
295
299
|
return self.cleaned_data
|
|
296
300
|
if 'pin' not in self.cleaned_data:
|
|
297
301
|
return self.cleaned_data
|
|
@@ -302,7 +306,8 @@ class ColonelNumericSensorConfigForm(ColonelComponentForm, NumericSensorForm):
|
|
|
302
306
|
|
|
303
307
|
|
|
304
308
|
def save(self, commit=True):
|
|
305
|
-
|
|
309
|
+
if 'pin' in self.cleaned_data:
|
|
310
|
+
self.instance.config['pin_no'] = self.cleaned_data['pin'].no
|
|
306
311
|
return super().save(commit=commit)
|
|
307
312
|
|
|
308
313
|
|
|
@@ -329,7 +334,7 @@ class DS18B20SensorConfigForm(ColonelComponentForm, NumericSensorForm):
|
|
|
329
334
|
|
|
330
335
|
def clean(self):
|
|
331
336
|
super().clean()
|
|
332
|
-
if not self.cleaned_data
|
|
337
|
+
if 'colonel' not in self.cleaned_data:
|
|
333
338
|
return self.cleaned_data
|
|
334
339
|
if 'pin' not in self.cleaned_data:
|
|
335
340
|
return self.cleaned_data
|
|
@@ -339,7 +344,8 @@ class DS18B20SensorConfigForm(ColonelComponentForm, NumericSensorForm):
|
|
|
339
344
|
return self.cleaned_data
|
|
340
345
|
|
|
341
346
|
def save(self, commit=True):
|
|
342
|
-
|
|
347
|
+
if 'pin' in self.cleaned_data:
|
|
348
|
+
self.instance.config['pin_no'] = self.cleaned_data['pin'].no
|
|
343
349
|
return super().save(commit=commit)
|
|
344
350
|
|
|
345
351
|
|
|
@@ -385,7 +391,8 @@ class ColonelDHTSensorConfigForm(ColonelComponentForm):
|
|
|
385
391
|
return self.cleaned_data
|
|
386
392
|
|
|
387
393
|
def save(self, commit=True):
|
|
388
|
-
|
|
394
|
+
if 'pin' in self.cleaned_data:
|
|
395
|
+
self.instance.config['pin_no'] = self.cleaned_data['pin'].no
|
|
389
396
|
return super().save(commit=commit)
|
|
390
397
|
|
|
391
398
|
|
|
@@ -415,7 +422,8 @@ class BME680SensorConfigForm(ColonelComponentForm):
|
|
|
415
422
|
)
|
|
416
423
|
|
|
417
424
|
def save(self, commit=True):
|
|
418
|
-
|
|
425
|
+
if 'interface' in self.cleaned_data:
|
|
426
|
+
self.instance.config['i2c_interface'] = self.cleaned_data['interface'].no
|
|
419
427
|
return super().save(commit=commit)
|
|
420
428
|
|
|
421
429
|
|
|
@@ -445,7 +453,8 @@ class MPC9808SensorConfigForm(ColonelComponentForm):
|
|
|
445
453
|
)
|
|
446
454
|
|
|
447
455
|
def save(self, commit=True):
|
|
448
|
-
|
|
456
|
+
if 'interface' in self.cleaned_data:
|
|
457
|
+
self.instance.config['i2c_interface'] = self.cleaned_data['interface'].no
|
|
449
458
|
return super().save(commit=commit)
|
|
450
459
|
|
|
451
460
|
|
|
@@ -472,7 +481,7 @@ class ColonelTouchSensorConfigForm(ColonelComponentForm):
|
|
|
472
481
|
|
|
473
482
|
def clean(self):
|
|
474
483
|
super().clean()
|
|
475
|
-
if not self.cleaned_data
|
|
484
|
+
if 'colonel' not in self.cleaned_data:
|
|
476
485
|
return self.cleaned_data
|
|
477
486
|
if 'pin' not in self.cleaned_data:
|
|
478
487
|
return self.cleaned_data
|
|
@@ -483,7 +492,8 @@ class ColonelTouchSensorConfigForm(ColonelComponentForm):
|
|
|
483
492
|
|
|
484
493
|
|
|
485
494
|
def save(self, commit=True):
|
|
486
|
-
|
|
495
|
+
if 'pin' in self.cleaned_data:
|
|
496
|
+
self.instance.config['pin_no'] = self.cleaned_data['pin'].no
|
|
487
497
|
return super().save(commit=commit)
|
|
488
498
|
|
|
489
499
|
|
|
@@ -528,35 +538,33 @@ class ColonelSwitchConfigForm(ColonelComponentForm):
|
|
|
528
538
|
|
|
529
539
|
def __init__(self, *args, **kwargs):
|
|
530
540
|
super().__init__(*args, **kwargs)
|
|
531
|
-
|
|
541
|
+
self.basic_fields.append('auto_off')
|
|
542
|
+
if self.instance.pk and 'slaves' in self.fields:
|
|
532
543
|
self.fields['slaves'].initial = self.instance.slaves.all()
|
|
533
544
|
|
|
534
545
|
def clean_slaves(self):
|
|
546
|
+
if 'slaves' not in self.cleaned_data:
|
|
547
|
+
return
|
|
535
548
|
if not self.cleaned_data['slaves'] or not self.instance:
|
|
536
549
|
return self.cleaned_data['slaves']
|
|
537
550
|
return validate_slaves(self.cleaned_data['slaves'], self.instance)
|
|
538
551
|
|
|
539
552
|
def clean(self):
|
|
540
553
|
super().clean()
|
|
541
|
-
if not self.cleaned_data.get('colonel'):
|
|
542
|
-
return self.cleaned_data
|
|
543
|
-
if not self.cleaned_data.get('output_pin'):
|
|
544
|
-
return self.cleaned_data
|
|
545
|
-
|
|
546
|
-
self._clean_pin('output_pin')
|
|
547
|
-
|
|
548
|
-
if not self.cleaned_data.get('controls'):
|
|
549
|
-
return self.cleaned_data
|
|
550
554
|
|
|
551
|
-
self.
|
|
555
|
+
if self.cleaned_data.get('output_pin'):
|
|
556
|
+
self._clean_pin('output_pin')
|
|
557
|
+
if self.cleaned_data.get('controls'):
|
|
558
|
+
self._clean_controls()
|
|
552
559
|
|
|
553
560
|
return self.cleaned_data
|
|
554
561
|
|
|
555
562
|
|
|
556
563
|
def save(self, commit=True):
|
|
557
|
-
|
|
564
|
+
if 'output_pin' in self.cleaned_data:
|
|
565
|
+
self.instance.config['output_pin_no'] = self.cleaned_data['output_pin'].no
|
|
558
566
|
obj = super().save(commit=commit)
|
|
559
|
-
if commit:
|
|
567
|
+
if commit and 'slaves' in self.cleaned_data:
|
|
560
568
|
obj.slaves.set(self.cleaned_data['slaves'])
|
|
561
569
|
return obj
|
|
562
570
|
|
|
@@ -628,7 +636,8 @@ class ColonelPWMOutputConfigForm(ColonelComponentForm):
|
|
|
628
636
|
|
|
629
637
|
def __init__(self, *args, **kwargs):
|
|
630
638
|
super().__init__(*args, **kwargs)
|
|
631
|
-
|
|
639
|
+
self.basic_fields.extend(['turn_on_time', 'turn_off_time', 'skew'])
|
|
640
|
+
if self.instance.pk and 'slaves' in self.fields:
|
|
632
641
|
self.fields['slaves'].initial = self.instance.slaves.all()
|
|
633
642
|
|
|
634
643
|
def clean_slaves(self):
|
|
@@ -638,25 +647,18 @@ class ColonelPWMOutputConfigForm(ColonelComponentForm):
|
|
|
638
647
|
|
|
639
648
|
def clean(self):
|
|
640
649
|
super().clean()
|
|
641
|
-
if
|
|
642
|
-
|
|
643
|
-
if
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
self._clean_pin('output_pin')
|
|
647
|
-
|
|
648
|
-
if not self.cleaned_data.get('controls'):
|
|
649
|
-
return self.cleaned_data
|
|
650
|
-
|
|
651
|
-
self._clean_controls()
|
|
652
|
-
|
|
650
|
+
if self.cleaned_data.get('output_pin'):
|
|
651
|
+
self._clean_pin('output_pin')
|
|
652
|
+
if self.cleaned_data.get('controls'):
|
|
653
|
+
self._clean_controls()
|
|
653
654
|
return self.cleaned_data
|
|
654
655
|
|
|
655
656
|
|
|
656
657
|
def save(self, commit=True):
|
|
657
|
-
|
|
658
|
+
if 'output_pin' in self.cleaned_data:
|
|
659
|
+
self.instance.config['output_pin_no'] = self.cleaned_data['output_pin'].no
|
|
658
660
|
obj = super().save(commit=commit)
|
|
659
|
-
if commit:
|
|
661
|
+
if commit and 'slaves' in self.cleaned_data:
|
|
660
662
|
obj.slaves.set(self.cleaned_data['slaves'])
|
|
661
663
|
return obj
|
|
662
664
|
|
|
@@ -716,7 +718,9 @@ class ColonelRGBLightConfigForm(ColonelComponentForm):
|
|
|
716
718
|
)
|
|
717
719
|
|
|
718
720
|
def save(self, commit=True):
|
|
719
|
-
|
|
721
|
+
if 'output_pin' in self.cleaned_data:
|
|
722
|
+
self.instance.config['output_pin_no'] = \
|
|
723
|
+
self.cleaned_data['output_pin'].no
|
|
720
724
|
return super().save(commit)
|
|
721
725
|
|
|
722
726
|
def clean_custom_timing(self):
|
|
@@ -740,31 +744,25 @@ class ColonelRGBLightConfigForm(ColonelComponentForm):
|
|
|
740
744
|
|
|
741
745
|
def clean(self):
|
|
742
746
|
super().clean()
|
|
743
|
-
if not self.cleaned_data.get('colonel'):
|
|
744
|
-
return self.cleaned_data
|
|
745
|
-
if not self.cleaned_data.get('output_pin'):
|
|
746
|
-
return self.cleaned_data
|
|
747
|
-
|
|
748
|
-
if self.cleaned_data.get('color_order'):
|
|
749
|
-
if self.cleaned_data['has_white']:
|
|
750
|
-
if len(self.cleaned_data['color_order']) != 4:
|
|
751
|
-
self.add_error(
|
|
752
|
-
"color_order",
|
|
753
|
-
_("4 colors expected for stripes with dedicated White led.")
|
|
754
|
-
)
|
|
755
|
-
else:
|
|
756
|
-
if len(self.cleaned_data['color_order']) != 3:
|
|
757
|
-
self.add_error(
|
|
758
|
-
"color_order",
|
|
759
|
-
_("3 colors expected for stripes without dedicated White led.")
|
|
760
|
-
)
|
|
761
747
|
|
|
762
748
|
self._clean_pin('output_pin')
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
749
|
+
if self.cleaned_data.get('controls'):
|
|
750
|
+
self._clean_controls()
|
|
751
|
+
|
|
752
|
+
if 'color_order' in self.cleaned_data:
|
|
753
|
+
if self.cleaned_data.get('color_order'):
|
|
754
|
+
if self.cleaned_data['has_white']:
|
|
755
|
+
if len(self.cleaned_data['color_order']) != 4:
|
|
756
|
+
self.add_error(
|
|
757
|
+
"color_order",
|
|
758
|
+
_("4 colors expected for stripes with dedicated White led.")
|
|
759
|
+
)
|
|
760
|
+
else:
|
|
761
|
+
if len(self.cleaned_data['color_order']) != 3:
|
|
762
|
+
self.add_error(
|
|
763
|
+
"color_order",
|
|
764
|
+
_("3 colors expected for stripes without dedicated White led.")
|
|
765
|
+
)
|
|
768
766
|
|
|
769
767
|
return self.cleaned_data
|
|
770
768
|
|
|
@@ -811,21 +809,19 @@ class DualMotorValveForm(ColonelComponentForm):
|
|
|
811
809
|
|
|
812
810
|
def clean(self):
|
|
813
811
|
super().clean()
|
|
814
|
-
if
|
|
815
|
-
|
|
816
|
-
if
|
|
817
|
-
|
|
818
|
-
if not self.cleaned_data.get('close_pin'):
|
|
819
|
-
return self.cleaned_data
|
|
820
|
-
|
|
821
|
-
self._clean_pin('open_pin')
|
|
822
|
-
self._clean_pin('close_pin')
|
|
823
|
-
|
|
812
|
+
if self.cleaned_data.get('open_pin'):
|
|
813
|
+
self._clean_pin('open_pin')
|
|
814
|
+
if self.cleaned_data.get('close_pin'):
|
|
815
|
+
self._clean_pin('close_pin')
|
|
824
816
|
return self.cleaned_data
|
|
825
817
|
|
|
826
818
|
def save(self, commit=True):
|
|
827
|
-
|
|
828
|
-
|
|
819
|
+
if 'open_pin' in self.cleaned_data:
|
|
820
|
+
self.instance.config['open_pin_no'] = \
|
|
821
|
+
self.cleaned_data['open_pin'].no
|
|
822
|
+
if 'close_pin' in self.cleaned_data:
|
|
823
|
+
self.instance.config['close_pin_no'] = \
|
|
824
|
+
self.cleaned_data['close_pin'].no
|
|
829
825
|
return super().save(commit=commit)
|
|
830
826
|
|
|
831
827
|
|
|
@@ -903,40 +899,37 @@ class BlindsConfigForm(ColonelComponentForm):
|
|
|
903
899
|
|
|
904
900
|
def clean(self):
|
|
905
901
|
super().clean()
|
|
906
|
-
if not self.cleaned_data.get('colonel'):
|
|
907
|
-
return self.cleaned_data
|
|
908
|
-
if not self.cleaned_data.get('open_pin'):
|
|
909
|
-
return self.cleaned_data
|
|
910
|
-
if not self.cleaned_data.get('close_pin'):
|
|
911
|
-
return self.cleaned_data
|
|
912
|
-
|
|
913
|
-
self._clean_pin('open_pin')
|
|
914
|
-
self._clean_pin('close_pin')
|
|
915
|
-
|
|
916
|
-
if 'controls' not in self.cleaned_data:
|
|
917
|
-
return self.cleaned_data
|
|
918
|
-
|
|
919
|
-
if len(self.cleaned_data['controls']) not in (0, 2):
|
|
920
|
-
self.add_error('controls', "Must have 0 or 2 controls")
|
|
921
|
-
return self.cleaned_data
|
|
922
|
-
|
|
923
|
-
if len(self.cleaned_data['controls']) == 2:
|
|
924
|
-
method = None
|
|
925
|
-
for c in self.cleaned_data['controls']:
|
|
926
|
-
if not method:
|
|
927
|
-
method = c['method']
|
|
928
|
-
else:
|
|
929
|
-
if c['method'] != method:
|
|
930
|
-
self.add_error('controls', "Both must use the same control method.")
|
|
931
|
-
return self.cleaned_data
|
|
932
|
-
|
|
933
|
-
self._clean_controls()
|
|
934
902
|
|
|
903
|
+
if self.cleaned_data.get('open_pin'):
|
|
904
|
+
self._clean_pin('open_pin')
|
|
905
|
+
if self.cleaned_data.get('close_pin'):
|
|
906
|
+
self._clean_pin('close_pin')
|
|
907
|
+
|
|
908
|
+
if 'controls' in self.cleaned_data:
|
|
909
|
+
if len(self.cleaned_data['controls']) not in (0, 2):
|
|
910
|
+
self.add_error('controls', "Must have 0 or 2 controls")
|
|
911
|
+
return self.cleaned_data
|
|
912
|
+
|
|
913
|
+
if len(self.cleaned_data['controls']) == 2:
|
|
914
|
+
method = None
|
|
915
|
+
for c in self.cleaned_data['controls']:
|
|
916
|
+
if not method:
|
|
917
|
+
method = c['method']
|
|
918
|
+
else:
|
|
919
|
+
if c['method'] != method:
|
|
920
|
+
self.add_error('controls', "Both must use the same control method.")
|
|
921
|
+
return self.cleaned_data
|
|
922
|
+
|
|
923
|
+
self._clean_controls()
|
|
935
924
|
return self.cleaned_data
|
|
936
925
|
|
|
937
926
|
def save(self, commit=True):
|
|
938
|
-
|
|
939
|
-
|
|
927
|
+
if 'open_pin' in self.cleaned_data:
|
|
928
|
+
self.instance.config['open_pin_no'] = \
|
|
929
|
+
self.cleaned_data['open_pin'].no
|
|
930
|
+
if 'close_pin' in self.cleaned_data:
|
|
931
|
+
self.instance.config['close_pin_no'] = \
|
|
932
|
+
self.cleaned_data['close_pin'].no
|
|
940
933
|
return super().save(commit=commit)
|
|
941
934
|
|
|
942
935
|
|
|
@@ -952,9 +945,6 @@ class BurglarSmokeDetectorConfigForm(ColonelComponentForm):
|
|
|
952
945
|
]
|
|
953
946
|
)
|
|
954
947
|
)
|
|
955
|
-
power_action = forms.ChoiceField(
|
|
956
|
-
choices=(('HIGH', "HIGH"), ('LOW', "LOW")),
|
|
957
|
-
)
|
|
958
948
|
sensor_pin = ColonelPinChoiceField(
|
|
959
949
|
queryset=ColonelPin.objects.filter(input=True),
|
|
960
950
|
widget=autocomplete.ListSelect2(
|
|
@@ -966,67 +956,28 @@ class BurglarSmokeDetectorConfigForm(ColonelComponentForm):
|
|
|
966
956
|
]
|
|
967
957
|
)
|
|
968
958
|
)
|
|
969
|
-
sensor_pull = forms.ChoiceField(
|
|
970
|
-
choices=(
|
|
971
|
-
('HIGH', "HIGH"), ('LOW', "LOW"), ("FLOATING", "leave floating"),
|
|
972
|
-
),
|
|
973
|
-
help_text="If you are not sure what is this all about, "
|
|
974
|
-
"you are most definitely want to pull this HIGH or LOW "
|
|
975
|
-
"but not leave it floating!"
|
|
976
|
-
)
|
|
977
959
|
sensor_inverse = forms.TypedChoiceField(
|
|
978
|
-
choices=((
|
|
979
|
-
help_text="Hint: Set
|
|
960
|
+
choices=((0, "No"), (1, "Yes")), coerce=int, initial=0,
|
|
961
|
+
help_text="Hint: Set to Yes, to get ON signal when "
|
|
980
962
|
"you deliver GND to the pin and OFF when you cut it out."
|
|
981
963
|
)
|
|
982
964
|
|
|
983
|
-
|
|
984
965
|
def clean(self):
|
|
985
966
|
super().clean()
|
|
986
|
-
if
|
|
987
|
-
|
|
988
|
-
if '
|
|
989
|
-
|
|
990
|
-
if 'power_pin' not in self.cleaned_data:
|
|
991
|
-
return self.cleaned_data
|
|
992
|
-
|
|
993
|
-
self._clean_pin('sensor_pin')
|
|
994
|
-
self._clean_pin('power_pin')
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
if self.cleaned_data['sensor_pin'].no > 100:
|
|
998
|
-
if self.cleaned_data['sensor_pin'].no < 126:
|
|
999
|
-
if self.cleaned_data.get('sensor_pull') == 'HIGH':
|
|
1000
|
-
self.add_error(
|
|
1001
|
-
'sensor_pull',
|
|
1002
|
-
"Sorry, but this pin is already pulled LOW and "
|
|
1003
|
-
"it can not be changed by this setting. "
|
|
1004
|
-
"Please use 5kohm resistor to physically pull it HIGH "
|
|
1005
|
-
"if that's what you want to do."
|
|
1006
|
-
)
|
|
1007
|
-
else:
|
|
1008
|
-
if self.cleaned_data.get('sensor_pull') == 'LOW':
|
|
1009
|
-
self.add_error(
|
|
1010
|
-
'sensor_pull',
|
|
1011
|
-
"Sorry, but this pin is already pulled HIGH and "
|
|
1012
|
-
"it can not be changed by this setting. "
|
|
1013
|
-
"Please use 5kohm resistor to physically pull it LOW "
|
|
1014
|
-
"if that's what you want to do."
|
|
1015
|
-
)
|
|
1016
|
-
elif self.cleaned_data.get('sensor_pull') != 'FLOATING':
|
|
1017
|
-
if not self.cleaned_data['sensor_pin'].output:
|
|
1018
|
-
self.add_error(
|
|
1019
|
-
'sensor_pin',
|
|
1020
|
-
f"Sorry, but {self.cleaned_data['sensor_pin']} "
|
|
1021
|
-
f"does not have internal pull HIGH/LOW"
|
|
1022
|
-
" resistance capability"
|
|
1023
|
-
)
|
|
967
|
+
if 'sensor_pin' in self.cleaned_data:
|
|
968
|
+
self._clean_pin('sensor_pin')
|
|
969
|
+
if 'power_pin' in self.cleaned_data:
|
|
970
|
+
self._clean_pin('power_pin')
|
|
1024
971
|
|
|
1025
972
|
return self.cleaned_data
|
|
1026
973
|
|
|
1027
974
|
def save(self, commit=True):
|
|
1028
|
-
|
|
1029
|
-
|
|
975
|
+
if 'sensor_pin' in self.cleaned_data:
|
|
976
|
+
self.instance.config['sensor_pin_no'] = \
|
|
977
|
+
self.cleaned_data['sensor_pin'].no
|
|
978
|
+
if 'power_pin' in self.cleaned_data:
|
|
979
|
+
self.instance.config['power_pin_no'] = \
|
|
980
|
+
self.cleaned_data['power_pin'].no
|
|
1030
981
|
return super().save(commit=commit)
|
|
1031
982
|
|
|
1032
983
|
|
|
@@ -1059,18 +1010,14 @@ class TTLockConfigForm(ColonelComponentForm):
|
|
|
1059
1010
|
|
|
1060
1011
|
def save(self, commit=True):
|
|
1061
1012
|
obj = super(ColonelComponentForm, self).save(commit)
|
|
1062
|
-
if commit:
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
self.instance.gateway, self.cleaned_data['door_sensor'],
|
|
1068
|
-
command='watch_lock_sensor'
|
|
1069
|
-
).publish()
|
|
1013
|
+
if commit and 'door_sensor' in self.cleaned_data:
|
|
1014
|
+
GatewayObjectCommand(
|
|
1015
|
+
self.instance.gateway, self.cleaned_data['door_sensor'],
|
|
1016
|
+
command='watch_lock_sensor'
|
|
1017
|
+
).publish()
|
|
1070
1018
|
return obj
|
|
1071
1019
|
|
|
1072
1020
|
|
|
1073
|
-
|
|
1074
1021
|
class DALIDeviceConfigForm(ColonelComponentForm):
|
|
1075
1022
|
interface = ColonelInterfacesChoiceField(
|
|
1076
1023
|
queryset=Interface.objects.filter(type='dali'),
|
|
@@ -1087,7 +1034,9 @@ class DALIDeviceConfigForm(ColonelComponentForm):
|
|
|
1087
1034
|
)
|
|
1088
1035
|
|
|
1089
1036
|
def save(self, commit=True):
|
|
1090
|
-
|
|
1037
|
+
if 'interface' in self.cleaned_data:
|
|
1038
|
+
self.instance.config['dali_interface'] = \
|
|
1039
|
+
self.cleaned_data['interface'].no
|
|
1091
1040
|
return super().save(commit=commit)
|
|
1092
1041
|
|
|
1093
1042
|
|
|
Binary file
|
|
Binary file
|
simo/generic/forms.py
CHANGED
|
@@ -137,8 +137,6 @@ class ThermostatConfigForm(BaseComponentForm):
|
|
|
137
137
|
else:
|
|
138
138
|
self.fields['use_real_feel'].disabled = True
|
|
139
139
|
|
|
140
|
-
|
|
141
|
-
|
|
142
140
|
def save(self, commit=True):
|
|
143
141
|
self.instance.value_units = self.cleaned_data[
|
|
144
142
|
'temperature_sensor'
|
|
@@ -314,8 +312,9 @@ class AlarmGroupConfigForm(BaseComponentForm):
|
|
|
314
312
|
def save(self, *args, **kwargs):
|
|
315
313
|
self.instance.value_units = 'status'
|
|
316
314
|
from .controllers import AlarmGroup
|
|
317
|
-
if
|
|
318
|
-
self.
|
|
315
|
+
if 'is_main' in self.cleaned_data:
|
|
316
|
+
if self.fields['is_main'].widget.attrs.get('disabled'):
|
|
317
|
+
self.cleaned_data['is_main'] = self.fields['is_main'].initial
|
|
319
318
|
obj = super().save(*args, **kwargs)
|
|
320
319
|
if obj.config.get('is_main'):
|
|
321
320
|
for c in Component.objects.filter(
|
|
@@ -366,8 +365,9 @@ class WeatherForecastForm(BaseComponentForm):
|
|
|
366
365
|
def save(self, *args, **kwargs):
|
|
367
366
|
self.instance.value_units = 'status'
|
|
368
367
|
from .controllers import WeatherForecast
|
|
369
|
-
if self.fields
|
|
370
|
-
self.
|
|
368
|
+
if 'is_main' in self.fields and 'is_main' in self.cleaned_data:
|
|
369
|
+
if self.fields['is_main'].widget.attrs.get('disabled'):
|
|
370
|
+
self.cleaned_data['is_main'] = self.fields['is_main'].initial
|
|
371
371
|
obj = super().save(*args, **kwargs)
|
|
372
372
|
if obj.config.get('is_main'):
|
|
373
373
|
for c in Component.objects.filter(
|
|
@@ -541,11 +541,12 @@ class WateringConfigForm(BaseComponentForm):
|
|
|
541
541
|
return contours
|
|
542
542
|
|
|
543
543
|
def save(self, commit=True):
|
|
544
|
-
|
|
545
|
-
self.
|
|
546
|
-
|
|
544
|
+
if 'contours' in self.cleaned_data:
|
|
545
|
+
self.instance.config['program'] = self.controller._build_program(
|
|
546
|
+
self.cleaned_data['contours']
|
|
547
|
+
)
|
|
547
548
|
obj = super().save(commit=commit)
|
|
548
|
-
if commit:
|
|
549
|
+
if commit and 'contours' in self.cleaned_data:
|
|
549
550
|
obj.slaves.clear()
|
|
550
551
|
for contour in self.cleaned_data['contours']:
|
|
551
552
|
obj.slaves.add(
|
|
@@ -565,10 +566,6 @@ class StateForm(forms.Form):
|
|
|
565
566
|
help_text = forms.CharField(required=False, widget=forms.Textarea(attrs={'rows': 3}))
|
|
566
567
|
prefix = 'states'
|
|
567
568
|
|
|
568
|
-
def clean(self):
|
|
569
|
-
print("Let's clean the data! ", self.cleaned_data)
|
|
570
|
-
return self.cleaned_data
|
|
571
|
-
|
|
572
569
|
|
|
573
570
|
class StateSelectForm(BaseComponentForm):
|
|
574
571
|
states = FormsetField(
|
|
@@ -602,18 +599,20 @@ class AlarmClockEventForm(forms.Form):
|
|
|
602
599
|
if not self.cleaned_data.get('play_action'):
|
|
603
600
|
return self.cleaned_data
|
|
604
601
|
component = self.cleaned_data.get('component')
|
|
605
|
-
if
|
|
606
|
-
self.
|
|
607
|
-
'play_action',
|
|
608
|
-
f"{component} has no {self.cleaned_data['play_action']} action!"
|
|
609
|
-
)
|
|
610
|
-
if self.cleaned_data.get('reverse_action'):
|
|
611
|
-
if not hasattr(component, self.cleaned_data['reverse_action']):
|
|
602
|
+
if 'play_action' in self.cleaned_data:
|
|
603
|
+
if not hasattr(component, self.cleaned_data['play_action']):
|
|
612
604
|
self.add_error(
|
|
613
|
-
'
|
|
614
|
-
f"{component} has no "
|
|
615
|
-
f"{self.cleaned_data['reverse_action']} action!"
|
|
605
|
+
'play_action',
|
|
606
|
+
f"{component} has no {self.cleaned_data['play_action']} action!"
|
|
616
607
|
)
|
|
608
|
+
if 'reverse_action' in self.cleaned_data:
|
|
609
|
+
if self.cleaned_data.get('reverse_action'):
|
|
610
|
+
if not hasattr(component, self.cleaned_data['reverse_action']):
|
|
611
|
+
self.add_error(
|
|
612
|
+
'reverse_action',
|
|
613
|
+
f"{component} has no "
|
|
614
|
+
f"{self.cleaned_data['reverse_action']} action!"
|
|
615
|
+
)
|
|
617
616
|
return self.cleaned_data
|
|
618
617
|
|
|
619
618
|
|
|
@@ -633,7 +632,7 @@ class AlarmClockConfigForm(BaseComponentForm):
|
|
|
633
632
|
|
|
634
633
|
def save(self, commit=True):
|
|
635
634
|
obj = super().save(commit=commit)
|
|
636
|
-
if commit:
|
|
635
|
+
if commit and 'default_events' in self.cleaned_data:
|
|
637
636
|
obj.slaves.clear()
|
|
638
637
|
for comp in self.cleaned_data['default_events']:
|
|
639
638
|
c = Component.objects.filter(pk=comp['component']).first()
|
simo/generic/models.py
CHANGED
|
@@ -125,11 +125,27 @@ def bind_controlling_locks_to_alarm_groups(sender, instance, *args, **kwargs):
|
|
|
125
125
|
base_type=AlarmGroup.base_type,
|
|
126
126
|
config__arming_locks__contains=instance.id
|
|
127
127
|
):
|
|
128
|
-
if ag.config.get(
|
|
129
|
-
'arm_on_away', ''
|
|
130
|
-
).startswith('on_away_and_locked'):
|
|
128
|
+
if ag.config.get('arm_on_away') in (None, 'on_away'):
|
|
131
129
|
continue
|
|
132
|
-
|
|
130
|
+
users_at_home = InstanceUser.objects.filter(
|
|
131
|
+
instance=instance.instance, at_home=True
|
|
132
|
+
).exclude(is_active=False).exclude(id=instance.id).count()
|
|
133
|
+
if users_at_home:
|
|
134
|
+
continue
|
|
135
|
+
if ag.config.get('arm_on_away') == 'on_away_and_locked':
|
|
136
|
+
print(f"Nobody is at home, lock was locked. Arm {ag}!")
|
|
137
|
+
ag.arm()
|
|
138
|
+
continue
|
|
139
|
+
locked_states = [
|
|
140
|
+
True if l['value'] == 'locked' else False
|
|
141
|
+
for l in Component.objects.filter(
|
|
142
|
+
base_type='lock', id__in=ag.config.get('arming_locks', []),
|
|
143
|
+
).values('value')
|
|
144
|
+
]
|
|
145
|
+
if all(locked_states):
|
|
146
|
+
print(f"Nobody is at home, all locks are now locked. Arm {ag}!")
|
|
147
|
+
ag.arm()
|
|
148
|
+
|
|
133
149
|
elif instance.value == 'unlocked':
|
|
134
150
|
for ag in Component.objects.filter(
|
|
135
151
|
base_type=AlarmGroup.base_type,
|
|
@@ -148,8 +164,8 @@ def bind_alarm_groups(sender, instance, created, *args, **kwargs):
|
|
|
148
164
|
return
|
|
149
165
|
users_at_home = InstanceUser.objects.filter(
|
|
150
166
|
instance=instance.instance, at_home=True
|
|
151
|
-
).exclude(is_active=False).exclude(id=instance.id)
|
|
152
|
-
if users_at_home
|
|
167
|
+
).exclude(is_active=False).exclude(id=instance.id).count()
|
|
168
|
+
if users_at_home:
|
|
153
169
|
return
|
|
154
170
|
for ag in Component.objects.filter(
|
|
155
171
|
zone__instance=instance.instance,
|
simo/notifications/utils.py
CHANGED
|
@@ -6,9 +6,7 @@ def notify_users(instance, severity, title, body=None, component=None, users=Non
|
|
|
6
6
|
assert severity in ('info', 'warning', 'alarm')
|
|
7
7
|
notification = Notification.objects.create(
|
|
8
8
|
instance=instance,
|
|
9
|
-
title='
|
|
10
|
-
instance.name, title
|
|
11
|
-
),
|
|
9
|
+
title=f'{instance.name}: {title}',
|
|
12
10
|
severity=severity, body=body,
|
|
13
11
|
component=component
|
|
14
12
|
)
|
|
@@ -18,6 +16,9 @@ def notify_users(instance, severity, title, body=None, component=None, users=Non
|
|
|
18
16
|
instance_roles__is_active=True
|
|
19
17
|
)
|
|
20
18
|
for user in users:
|
|
19
|
+
# do not send emails to system users
|
|
20
|
+
if user.email.endswith('simo.io'):
|
|
21
|
+
continue
|
|
21
22
|
if instance not in user.instances:
|
|
22
23
|
continue
|
|
23
24
|
if component and not component.can_write(user):
|
|
Binary file
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Generated by Django 3.2.9 on 2024-05-06 08:34
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
dependencies = [
|
|
9
|
+
('users', '0026_fingerprint_name'),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.AddField(
|
|
14
|
+
model_name='permissionsrole',
|
|
15
|
+
name='can_manage_components',
|
|
16
|
+
field=models.BooleanField(default=False, help_text='Can manage zones and basic component parameters via SIMO.io app.'),
|
|
17
|
+
),
|
|
18
|
+
]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Generated by Django 3.2.9 on 2024-05-06 11:46
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
dependencies = [
|
|
9
|
+
('users', '0027_permissionsrole_can_manage_components'),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.RemoveField(
|
|
14
|
+
model_name='permissionsrole',
|
|
15
|
+
name='can_manage_components',
|
|
16
|
+
),
|
|
17
|
+
migrations.AddField(
|
|
18
|
+
model_name='permissionsrole',
|
|
19
|
+
name='is_owner',
|
|
20
|
+
field=models.BooleanField(default=False, help_text='Can manage zones, basic component parametersand other things via SIMO.io app, but is not yet allowed to perform any serious system changes, like superusers can.'),
|
|
21
|
+
),
|
|
22
|
+
]
|
|
Binary file
|
|
Binary file
|
simo/users/models.py
CHANGED
|
@@ -35,6 +35,12 @@ class PermissionsRole(models.Model):
|
|
|
35
35
|
help_text="Global role if instance is not set."
|
|
36
36
|
)
|
|
37
37
|
name = models.CharField(max_length=100, db_index=True)
|
|
38
|
+
is_owner = models.BooleanField(
|
|
39
|
+
default=False,
|
|
40
|
+
help_text="Can manage zones, basic component parameters"
|
|
41
|
+
"and other things via SIMO.io app, but is not yet allowed "
|
|
42
|
+
"to perform any serious system changes, like superusers can."
|
|
43
|
+
)
|
|
38
44
|
can_manage_users = models.BooleanField(default=False)
|
|
39
45
|
is_superuser = models.BooleanField(
|
|
40
46
|
default=False,
|
|
@@ -213,12 +219,7 @@ class User(AbstractBaseUser, SimoAdminMixin):
|
|
|
213
219
|
return self.is_active and self.ssh_key and self.is_master
|
|
214
220
|
|
|
215
221
|
def get_role(self, instance):
|
|
216
|
-
|
|
217
|
-
if not role.instance:
|
|
218
|
-
return role
|
|
219
|
-
for role in self.roles.all():
|
|
220
|
-
if role.instance == instance:
|
|
221
|
-
return role
|
|
222
|
+
return self.roles.filter(instance=instance).first()
|
|
222
223
|
|
|
223
224
|
def set_instance(self, instance):
|
|
224
225
|
self._instance = instance
|
|
@@ -26,7 +26,7 @@ simo/_hub_template/hub/supervisor.conf,sha256=IY3fdK0fDD2eAothB0n54xhjQj8LYoXIR9
|
|
|
26
26
|
simo/_hub_template/hub/urls.py,sha256=Ydm-1BkYAzWeEF-MKSDIFf-7aE4qNLPm48-SA51XgJQ,25
|
|
27
27
|
simo/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
28
28
|
simo/core/admin.py,sha256=vZp2TZTt41YararDkIEs35X6ARqRp94wrIddsIfe-Bs,17738
|
|
29
|
-
simo/core/api.py,sha256=
|
|
29
|
+
simo/core/api.py,sha256=YLO6r1htHgBjokAHoTXCtEFmx_Ug5xIlPg6sQX-CT4A,23413
|
|
30
30
|
simo/core/api_auth.py,sha256=_3hG4e1eLKrcRnSAOB_xTL6cwtOJ2_7JS7GZU_iqTgA,1251
|
|
31
31
|
simo/core/api_meta.py,sha256=ySmmhtVrWatI3yqnYPuP5ipapmJfyfEbl32w-7_W5O4,3551
|
|
32
32
|
simo/core/app_widgets.py,sha256=EEQOto3fGR0syDqpJE38tQrx8DoTTyg26nF5kYzHY38,2018
|
|
@@ -38,26 +38,26 @@ simo/core/controllers.py,sha256=iJ7cIGv2WhSGjyxCFfK49pZXuEMfSe75bMEAkRN8G4g,2710
|
|
|
38
38
|
simo/core/dynamic_settings.py,sha256=U2WNL96JzVXdZh0EqMPWrxqO6BaRR2Eo5KTDqz7MC4o,1943
|
|
39
39
|
simo/core/events.py,sha256=LvtonJGNyCb6HLozs4EG0WZItnDwNdtnGQ4vTcnKvUs,4438
|
|
40
40
|
simo/core/filters.py,sha256=ghtOZcrwNAkIyF5_G9Sn73NkiI71mXv0NhwCk4IyMIM,411
|
|
41
|
-
simo/core/forms.py,sha256=
|
|
41
|
+
simo/core/forms.py,sha256=tsWBsoBJeUiXDVU6lKbMbnBZORmNrnK5XnoBS_9uWsU,23583
|
|
42
42
|
simo/core/gateways.py,sha256=s_c2W0v2_te89i6LS4Nj7F2wn9UwjZXPT7pfy6SToVo,3714
|
|
43
43
|
simo/core/loggers.py,sha256=EBdq23gTQScVfQVH-xeP90-wII2DQFDjoROAW6ggUP4,1645
|
|
44
44
|
simo/core/managers.py,sha256=WoQ4OX3akIvoroSYji-nLVqXBSJzCiC1u_IiWkKbKmA,2413
|
|
45
45
|
simo/core/middleware.py,sha256=pO52hQOJV_JRmNyUe7zfufSnJFlRITOWX6jwkoPWJhk,2052
|
|
46
46
|
simo/core/models.py,sha256=m05fXzOtLrq4LEGknU1mjTHzaTmIEBi1zhwvtDWDknE,19773
|
|
47
|
-
simo/core/permissions.py,sha256=
|
|
47
|
+
simo/core/permissions.py,sha256=yqVXq6SNZccSKcOoGdb0oh-WHsyTTtI9ovJdJyhjv28,2707
|
|
48
48
|
simo/core/routing.py,sha256=X1_IHxyA-_Q7hw1udDoviVP4_FSBDl8GYETTC2zWTbY,499
|
|
49
|
-
simo/core/serializers.py,sha256=
|
|
49
|
+
simo/core/serializers.py,sha256=vOrWlfSkmdyaG0VPM0fmRLd7OkkNNGyAkc6Q8Cwa_1Q,18179
|
|
50
50
|
simo/core/signal_receivers.py,sha256=EZ8NSYZxUgSaLS16YZdK7T__l8dl0joMRllOxx5PUt4,2809
|
|
51
51
|
simo/core/socket_consumers.py,sha256=n7VE2Fvqt4iEAYLTRbTPOcI-7tszMAADu7gimBxB-Fg,9635
|
|
52
52
|
simo/core/storage.py,sha256=YlxmdRs-zhShWtFKgpJ0qp2NDBuIkJGYC1OJzqkbttQ,572
|
|
53
|
-
simo/core/tasks.py,sha256=
|
|
53
|
+
simo/core/tasks.py,sha256=PEGGsKBbkQOl3LhLfGR5orbYtCxzcv8peMLiVxgl3R0,12228
|
|
54
54
|
simo/core/todos.py,sha256=eYVXfLGiapkxKK57XuviSNe3WsUYyIWZ0hgQJk7ThKo,665
|
|
55
55
|
simo/core/types.py,sha256=WJEq48mIbFi_5Alt4wxWMGXxNxUTXqfQU5koH7wqHHI,1108
|
|
56
56
|
simo/core/views.py,sha256=hlAKpAbCbqI3a-uL5tDp532T2oLFiF0MBzKUJ_SNzo0,5833
|
|
57
57
|
simo/core/widgets.py,sha256=J9e06C6I22F6xKic3VMgG7WeX07glAcl-4bF2Mg180A,2827
|
|
58
58
|
simo/core/__pycache__/__init__.cpython-38.pyc,sha256=y0IW37wBUIGa3Eh_ZG28pRqHKoLiPyTgUX2OnbkEPlc,158
|
|
59
59
|
simo/core/__pycache__/admin.cpython-38.pyc,sha256=gZpNwM9f-JMbGe9sbQKt2hU2BBYhRzKSH9XRJYKN3Vo,13534
|
|
60
|
-
simo/core/__pycache__/api.cpython-38.pyc,sha256=
|
|
60
|
+
simo/core/__pycache__/api.cpython-38.pyc,sha256=yL3vQiZ6McrOILD37EmuRdzZHZ0bPCsboDAeHZP_iyM,18581
|
|
61
61
|
simo/core/__pycache__/api_auth.cpython-38.pyc,sha256=5UTBr3rDMERAfc0OuOVDwGeQkt6Q7GLBtZJAMBse1sg,1712
|
|
62
62
|
simo/core/__pycache__/api_meta.cpython-38.pyc,sha256=94T3_rybn2T1_bkaDQnQRyjy21LBaGOnz-mmkJ6T0N8,2840
|
|
63
63
|
simo/core/__pycache__/app_widgets.cpython-38.pyc,sha256=9Es2wZNduzUJv-jZ_HX0-L3vqwpXWBbseEwoC5K6b-w,3465
|
|
@@ -69,19 +69,19 @@ simo/core/__pycache__/controllers.cpython-38.pyc,sha256=40wJ3mu0CnzexxDzXyDIlSxa
|
|
|
69
69
|
simo/core/__pycache__/dynamic_settings.cpython-38.pyc,sha256=ELu06Hub4DOidja71ybvD3ZM4HdXiyZjNJrZfnXZXNA,2476
|
|
70
70
|
simo/core/__pycache__/events.cpython-38.pyc,sha256=A1Axx-qftd1r7st7wkO3DkvTdt9-RkcJe5KJhpzJVk8,5109
|
|
71
71
|
simo/core/__pycache__/filters.cpython-38.pyc,sha256=VIMADCBiYhziIyRmxAyUDJluZvuZmiC4bNYWTRsGSao,721
|
|
72
|
-
simo/core/__pycache__/forms.cpython-38.pyc,sha256=
|
|
72
|
+
simo/core/__pycache__/forms.cpython-38.pyc,sha256=d0ruviTG6zihcc9hJ1TvVxPODwe7LJCyo_ZpLIUt5pI,19850
|
|
73
73
|
simo/core/__pycache__/gateways.cpython-38.pyc,sha256=XBiwMfBkjoQ2re6jvADJOwK0_0Aav-crzie9qtfqT9U,4599
|
|
74
74
|
simo/core/__pycache__/loggers.cpython-38.pyc,sha256=Z-cdQnC6XlIonPV4Sl4E52tP4NMEdPAiHK0cFaIL7I8,1623
|
|
75
75
|
simo/core/__pycache__/managers.cpython-38.pyc,sha256=5vstOMfm997CZBBkaSiaS7EojhLTWZlbeA_EQ8u-yfg,2554
|
|
76
76
|
simo/core/__pycache__/middleware.cpython-38.pyc,sha256=ESR5JPtITo9flczO0672sfzYUxrc_cQU0e0w5DFL-60,2038
|
|
77
77
|
simo/core/__pycache__/models.cpython-38.pyc,sha256=kYcojazryAAgZCXMoqqutIG2p8ofSKxza9TMx1SR2dQ,17233
|
|
78
|
-
simo/core/__pycache__/permissions.cpython-38.pyc,sha256=
|
|
78
|
+
simo/core/__pycache__/permissions.cpython-38.pyc,sha256=flJOCh94U8mFhE0XWzUD0sGR6Xe1HlfG4hQtNSnAGZ4,2788
|
|
79
79
|
simo/core/__pycache__/routing.cpython-38.pyc,sha256=3T3FPJ8Cn99xZCGvMyg2xjl7al-Shm9CelbSpkJtNP8,599
|
|
80
|
-
simo/core/__pycache__/serializers.cpython-38.pyc,sha256=
|
|
80
|
+
simo/core/__pycache__/serializers.cpython-38.pyc,sha256=Pi9lddoS_DtMuA1iZ4mFHu_dG2mjouWxajM4udPQKqo,17185
|
|
81
81
|
simo/core/__pycache__/signal_receivers.cpython-38.pyc,sha256=sgjH_wv-1U99auH5uHb3or0qettPeHAlsz8P7B03ajY,2430
|
|
82
82
|
simo/core/__pycache__/socket_consumers.cpython-38.pyc,sha256=NJUr7nRyHFvmAumxxWpsod5wzVVZM99rCEuJs1utHA4,8432
|
|
83
83
|
simo/core/__pycache__/storage.cpython-38.pyc,sha256=BTkYH8QQyjqI0WOtJC8fHNtgu0YA1vjqZclXjC2vCVI,1116
|
|
84
|
-
simo/core/__pycache__/tasks.cpython-38.pyc,sha256=
|
|
84
|
+
simo/core/__pycache__/tasks.cpython-38.pyc,sha256=8VyzcG4psw5cS2oTy8W7EVW_nP7fzhoMvxXczTe1rcw,9134
|
|
85
85
|
simo/core/__pycache__/todos.cpython-38.pyc,sha256=lOqGZ58siHM3isoJV4r7sg8igrfE9fFd-jSfeBa0AQI,253
|
|
86
86
|
simo/core/__pycache__/views.cpython-38.pyc,sha256=YrKRZPaV_JM4VGpdhVhsK-90UwUTOqp-V-Yj0SRGZgs,4212
|
|
87
87
|
simo/core/__pycache__/widgets.cpython-38.pyc,sha256=sR0ZeHCHrhnNDBJuRrxp3zUsfBp0xrtF0xrK2TkQv1o,3520
|
|
@@ -173,6 +173,7 @@ simo/core/migrations/0028_rename_subcomponents_component_slaves.py,sha256=ioQcfO
|
|
|
173
173
|
simo/core/migrations/0029_auto_20240229_1331.py,sha256=BYXPNwjXApAx7mxE5li3QssfksWTsSjDf_VPQ8iGV8c,1140
|
|
174
174
|
simo/core/migrations/0030_alter_instance_timezone.py,sha256=XZuYr2eD9MvE21Jxfdat8RC3sbc7PaGUfNPaqqxBNzE,23248
|
|
175
175
|
simo/core/migrations/0031_auto_20240429_1231.py,sha256=kskD8dylxqg-l-ZQgxl6ZdZd7iNcJ52rOGPJFa9s-wk,829
|
|
176
|
+
simo/core/migrations/0032_auto_20240506_0834.py,sha256=w-dXSZVYZD0B6pRmMXLy27Y2twRR3WbN9WdNJCQqqwk,746
|
|
176
177
|
simo/core/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
177
178
|
simo/core/migrations/__pycache__/0001_initial.cpython-38.pyc,sha256=w6GiBXVxWj30Bg4Sn_pFVeA041d-pCrkaq8mR3KuF70,5381
|
|
178
179
|
simo/core/migrations/__pycache__/0002_load_icons.cpython-38.pyc,sha256=Nb9RrPjVYo_RpZ5PmzoaEIWGCeVt4kicpmGiKlBrpIw,2123
|
|
@@ -205,6 +206,7 @@ simo/core/migrations/__pycache__/0028_rename_subcomponents_component_slaves.cpyt
|
|
|
205
206
|
simo/core/migrations/__pycache__/0029_auto_20240229_1331.cpython-38.pyc,sha256=nO6lBU-1SxMQv67jQs7cGgNnot722UVL9pj3UE14I1k,1086
|
|
206
207
|
simo/core/migrations/__pycache__/0030_alter_instance_timezone.cpython-38.pyc,sha256=paRgdijg5o8QzluT4GjSEQIMk6UAF7XmSwjqknXPE_4,16333
|
|
207
208
|
simo/core/migrations/__pycache__/0031_auto_20240429_1231.cpython-38.pyc,sha256=Kl76gU1VUyTWMz9RIZUrbnb_xeteWLDSRfs_GtNCVow,863
|
|
209
|
+
simo/core/migrations/__pycache__/0032_auto_20240506_0834.cpython-38.pyc,sha256=UhrErBBhvAOPVph1x3ibQvSrcVUrITIsU9CGmjT8nLk,895
|
|
208
210
|
simo/core/migrations/__pycache__/__init__.cpython-38.pyc,sha256=VZmDQ57BTcebuM0KMhjiTOabgWZCBxQmSJzWZos9SO8,169
|
|
209
211
|
simo/core/static/ansi_styles.css,sha256=4ieJGrjZPKyPSago9FdB_gflHoGE1vxCHi8qVn5tY-Y,37352
|
|
210
212
|
simo/core/static/admin/Img/plus.svg,sha256=2NpSFPWqGIjpAQGFI7LDQHPKagEhYkJiJX95ufCoZaI,741
|
|
@@ -10173,7 +10175,7 @@ simo/fleet/auto_urls.py,sha256=X04oKJWA48wFW5iXg3PPROY2KDdHn_a99orQSE28QC4,518
|
|
|
10173
10175
|
simo/fleet/base_types.py,sha256=wL9RVkHr0gA7HI1wZq0pruGEIgvQqpfnCL4cC3ywsvw,102
|
|
10174
10176
|
simo/fleet/ble.py,sha256=eHA_9ABjbmH1vUVCv9hiPXQL2GZZSEVwfO0xyI1S0nI,1081
|
|
10175
10177
|
simo/fleet/controllers.py,sha256=rTxRFf-LKWAZxzixrsLZHHm51BmMx9a1PLdgf6inlNM,20533
|
|
10176
|
-
simo/fleet/forms.py,sha256=
|
|
10178
|
+
simo/fleet/forms.py,sha256=ivP30jRtABtf1NkAuOaRRwZB7khHIJ7XwEzS5aCF480,36881
|
|
10177
10179
|
simo/fleet/gateways.py,sha256=KV5i5fxXIrlK-k6zyEkk83x11GJt-ELQ0npb4Ac83cM,3693
|
|
10178
10180
|
simo/fleet/managers.py,sha256=XOpDOA9L-f_550TNSyXnJbun2EmtGz1TenVTMlUSb8E,807
|
|
10179
10181
|
simo/fleet/models.py,sha256=ve-97F1cwGt-AmwfSJK0d-57pP3NyZpeu0XlHu2oK28,14494
|
|
@@ -10190,7 +10192,7 @@ simo/fleet/__pycache__/auto_urls.cpython-38.pyc,sha256=SqyTuaz_kEBvx-bL46SclsZEE
|
|
|
10190
10192
|
simo/fleet/__pycache__/base_types.cpython-38.pyc,sha256=deyPwjpT6xZiFxBGFnj5b7R-lbdOTh2krgpJhrcGVhc,274
|
|
10191
10193
|
simo/fleet/__pycache__/ble.cpython-38.pyc,sha256=Nrof9w7cm4OlpFWHeVnmvvanh2_oF9oQ3TknJiV93-0,1267
|
|
10192
10194
|
simo/fleet/__pycache__/controllers.cpython-38.pyc,sha256=l9bz18Qp33C12TJOKPSn9vIXnlBKnBusODNk7Fg64qA,18103
|
|
10193
|
-
simo/fleet/__pycache__/forms.cpython-38.pyc,sha256=
|
|
10195
|
+
simo/fleet/__pycache__/forms.cpython-38.pyc,sha256=TH4NC-otAl9V6YlvrffE9sghlT-tw_3RFK1ztO-fzfg,27368
|
|
10194
10196
|
simo/fleet/__pycache__/gateways.cpython-38.pyc,sha256=YAcgTOqJbtjGI03lvEcU6keFfrwAHkObVmErYzfRvjk,3569
|
|
10195
10197
|
simo/fleet/__pycache__/managers.cpython-38.pyc,sha256=8uz-xpUiqbGDgXIZ_XRZtFb-Tju6NGxflGg-Ee4Yo6k,1310
|
|
10196
10198
|
simo/fleet/__pycache__/models.cpython-38.pyc,sha256=pHNRUiPRjH0SLp14pzbSIxHi_-27SpZFgSh_7lzA8Wo,12359
|
|
@@ -10274,18 +10276,18 @@ simo/generic/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
10274
10276
|
simo/generic/app_widgets.py,sha256=E_pnpA1hxMIhenRCrHoQ5cik06jm2BAHCkl_eo-OudU,1264
|
|
10275
10277
|
simo/generic/base_types.py,sha256=djymox_boXTHX1BTTCLXrCH7ED-uAsV_idhaDOc3OLI,409
|
|
10276
10278
|
simo/generic/controllers.py,sha256=WYuOUzDWvkYRaTvlbdGy_qmwp1o_ohqKDfV7OrOq2QU,52218
|
|
10277
|
-
simo/generic/forms.py,sha256=
|
|
10279
|
+
simo/generic/forms.py,sha256=uLZ25Fw1xGqyDBngWPve9EaHpyldFegY_TPBKV4Oby0,24051
|
|
10278
10280
|
simo/generic/gateways.py,sha256=b3tQ2bAkDVYXCF5iZi2yi-6nZAM8WmHE9ICwxMyR0to,17034
|
|
10279
|
-
simo/generic/models.py,sha256=
|
|
10281
|
+
simo/generic/models.py,sha256=92TACMhJHadAg0TT9GnARO_R3_Sl6i-GGjhG_x7YdFI,7391
|
|
10280
10282
|
simo/generic/routing.py,sha256=elQVZmgnPiieEuti4sJ7zITk1hlRxpgbotcutJJgC60,228
|
|
10281
10283
|
simo/generic/socket_consumers.py,sha256=NfTQGYtVAc864IoogZRxf_0xpDPM0eMCWn0SlKA5P7Y,1751
|
|
10282
10284
|
simo/generic/__pycache__/__init__.cpython-38.pyc,sha256=mLu54WS9KIl-pHwVCBKpsDFIlOqml--JsOVzAUHg6cU,161
|
|
10283
10285
|
simo/generic/__pycache__/app_widgets.cpython-38.pyc,sha256=0IoKRG9n1tkNRRkrqAeOQwWBPd_33u98JBcVtMVVCio,2374
|
|
10284
10286
|
simo/generic/__pycache__/base_types.cpython-38.pyc,sha256=ptw6axyAqemZA35oa6vzr7EihzvbhW9w7Y-G6kfDedU,555
|
|
10285
10287
|
simo/generic/__pycache__/controllers.cpython-38.pyc,sha256=e0bvgyePgJbIs1omBq0TRPlVSKar2sK_JbUKqDRj7mY,33235
|
|
10286
|
-
simo/generic/__pycache__/forms.cpython-38.pyc,sha256=
|
|
10288
|
+
simo/generic/__pycache__/forms.cpython-38.pyc,sha256=U67jiwt6R8J547n7d84-_9mUxCXxKl-L0VFdYfo1aiM,17787
|
|
10287
10289
|
simo/generic/__pycache__/gateways.cpython-38.pyc,sha256=a4lLIMPyxm9tNzIqorXHIPZFVTcXlPsM1ycJMghxcHA,12673
|
|
10288
|
-
simo/generic/__pycache__/models.cpython-38.pyc,sha256=
|
|
10290
|
+
simo/generic/__pycache__/models.cpython-38.pyc,sha256=PzlZsM1jxo3FVb7QDm3bny8UFwTsGrMQe4mj4tJ06eQ,5675
|
|
10289
10291
|
simo/generic/__pycache__/routing.cpython-38.pyc,sha256=xtxTUTBTdivzFyA5Wh7k-hUj1WDO_FiRq6HYXdbr9Ks,382
|
|
10290
10292
|
simo/generic/__pycache__/socket_consumers.cpython-38.pyc,sha256=piFHces0J9QuXu_CNBCQCYjoZEeoaxyVjLfJ9KaR8C8,1898
|
|
10291
10293
|
simo/generic/static/weather_icons/01d@2x.png,sha256=TZfWi6Rfddb2P-oldWWcjUiuCHiU9Yrc5hyrQAhF26I,948
|
|
@@ -10346,7 +10348,7 @@ simo/notifications/admin.py,sha256=y_gmHYXbDh98LUUa-lp9DilTIgM6-pIujWPQPLQsJo8,8
|
|
|
10346
10348
|
simo/notifications/api.py,sha256=GXQpq68ULBaJpU8w3SJKaCKuxYGWYehKnGeocGB1RVc,1783
|
|
10347
10349
|
simo/notifications/models.py,sha256=VZcvweii59j89nPKlWeUSJ44Qz3ZLjJ6mXN6uB9F1Sw,2506
|
|
10348
10350
|
simo/notifications/serializers.py,sha256=altDEAPWwOhxRcEzE9-34jL8EFpyf3vPoEdAPoVLfGc,523
|
|
10349
|
-
simo/notifications/utils.py,sha256=
|
|
10351
|
+
simo/notifications/utils.py,sha256=VCBKcHrtZrdCalKe2w7umt7-trhG3vw-fKn1ndyr3CA,971
|
|
10350
10352
|
simo/notifications/__pycache__/__init__.cpython-38.pyc,sha256=YvucUfu98XFvEEg1LYFMlOZJpo_jSGxTVrM-ylAFLOg,167
|
|
10351
10353
|
simo/notifications/__pycache__/admin.cpython-38.pyc,sha256=Fl4crSZTFQOTYQioV6ff9fBRV4MhNiwQgMS2VnmCI4M,1632
|
|
10352
10354
|
simo/notifications/__pycache__/api.cpython-38.pyc,sha256=ys6E4AFghX6bq-rQ0gtA9s0Y2Hh-ypsWH8-Yz4edMrc,2073
|
|
@@ -10365,7 +10367,7 @@ simo/users/auth_backends.py,sha256=I5pnaTa20-Lxfw_dFG8471xDITb0_fQl1PVhJalp5vU,3
|
|
|
10365
10367
|
simo/users/auto_urls.py,sha256=lcJvteBsbHQMJieZpDz-63tDYejLApqsW3CUnDakd7k,272
|
|
10366
10368
|
simo/users/dynamic_settings.py,sha256=sEIsi4yJw3kH46Jq_aOkSuK7QTfQACGUE-lkyBogCaM,570
|
|
10367
10369
|
simo/users/middleware.py,sha256=GMCrnWSc_2qCleyQIkfQGdL-pU-UTEcSg1wPvIKZ9uk,1210
|
|
10368
|
-
simo/users/models.py,sha256=
|
|
10370
|
+
simo/users/models.py,sha256=VJWjqDXZpCzsrTYeXmxc8JrsxmK0Ml9-X9eOHj4BP78,19005
|
|
10369
10371
|
simo/users/permissions.py,sha256=IwtYS8yQdupWbYKR9VimSRDV3qCJ2jXP57Lyjpb2EQM,242
|
|
10370
10372
|
simo/users/serializers.py,sha256=e6yIUsO7BfvrZ4IQHBn-FdpAUMgic5clmGQdTtRlGRY,2515
|
|
10371
10373
|
simo/users/sso_urls.py,sha256=gQOaPvGMYFD0NCVSwyoWO-mTEHe5j9sbzV_RK7kdvp0,251
|
|
@@ -10380,7 +10382,7 @@ simo/users/__pycache__/auth_backends.cpython-38.pyc,sha256=MuOieBIXt6lrDx83-UQtd
|
|
|
10380
10382
|
simo/users/__pycache__/auto_urls.cpython-38.pyc,sha256=K-3sz2h-cEitoflSmZk1t0eUg5mQMMGLNZFREVwG7_o,430
|
|
10381
10383
|
simo/users/__pycache__/dynamic_settings.cpython-38.pyc,sha256=6F8JBjZkHykySnmZjNEzjS0ijbmPdcp9yUAZ5kqq_Fo,864
|
|
10382
10384
|
simo/users/__pycache__/middleware.cpython-38.pyc,sha256=Tj4nVEAvxEW3xA63fBRiJWRJpz_M848ZOqbHioc_IPE,1149
|
|
10383
|
-
simo/users/__pycache__/models.cpython-38.pyc,sha256=
|
|
10385
|
+
simo/users/__pycache__/models.cpython-38.pyc,sha256=Hui7Uu0kCIENoQuDZ5xoCHVNqsoI_hygaxKAgqqyPo8,17739
|
|
10384
10386
|
simo/users/__pycache__/permissions.cpython-38.pyc,sha256=ez5NxoL_JUeeH6GsKhvFreuA3FCBgGf9floSypdXUtM,633
|
|
10385
10387
|
simo/users/__pycache__/serializers.cpython-38.pyc,sha256=ylapsfu5qbSzbfX2lG3uc4wV6hhndFbIvI109lhhKOo,3461
|
|
10386
10388
|
simo/users/__pycache__/sso_urls.cpython-38.pyc,sha256=uAwDozpOmrhUald-8tOHANILXkH7-TI8fNYXOtPkSY8,402
|
|
@@ -10414,6 +10416,8 @@ simo/users/migrations/0023_auto_20240105_0719.py,sha256=OAkWJusXjzT6dx4EgwjvNvME
|
|
|
10414
10416
|
simo/users/migrations/0024_fingerprint.py,sha256=0wfplJ3iHv_6heJx7yIQYX3D68Nf9pLPlIZoM5NcPk8,1021
|
|
10415
10417
|
simo/users/migrations/0025_rename_name_fingerprint_type_and_more.py,sha256=Azw_a1qxIDttdG4m0DcLa82amv-mFsbW8PsjG9qFL0Y,466
|
|
10416
10418
|
simo/users/migrations/0026_fingerprint_name.py,sha256=DPmfi1brbaPymdNiPgc7dINSKy97yVHdKpKp-ZfnS3I,428
|
|
10419
|
+
simo/users/migrations/0027_permissionsrole_can_manage_components.py,sha256=VcGZE6u-q6UkGo7D01K_T1XBtIvIGe8SCk5ZPRrPpGo,485
|
|
10420
|
+
simo/users/migrations/0028_auto_20240506_1146.py,sha256=7RUFF2rJH-bnPeHwc77p8Q4kEAc3owyG4qp9Kc4aKhU,716
|
|
10417
10421
|
simo/users/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10418
10422
|
simo/users/migrations/__pycache__/0001_initial.cpython-38.pyc,sha256=ngXA1QR-Qc2VS-BTTZWybVXiEfifIgKaVS6bADiN8nU,4269
|
|
10419
10423
|
simo/users/migrations/__pycache__/0002_componentpermission.cpython-38.pyc,sha256=pknJnpic8p6Vdx9DX41FfODXNnvexDswJtUCmC5w1tg,995
|
|
@@ -10441,6 +10445,8 @@ simo/users/migrations/__pycache__/0023_auto_20240105_0719.cpython-38.pyc,sha256=
|
|
|
10441
10445
|
simo/users/migrations/__pycache__/0024_fingerprint.cpython-38.pyc,sha256=QLW3v4BK78HL682qeHkK_aXZgCJSs9sIIte60ieF5ZY,1132
|
|
10442
10446
|
simo/users/migrations/__pycache__/0025_rename_name_fingerprint_type_and_more.cpython-38.pyc,sha256=21CgVotrTT02MW58zqePt_-gsbpdN_m01T_SOHgWkBo,631
|
|
10443
10447
|
simo/users/migrations/__pycache__/0026_fingerprint_name.cpython-38.pyc,sha256=Ti0NLIKb0Wffn33LCBQQ-cumFCX6JFxSi1FYoV8C0ZE,642
|
|
10448
|
+
simo/users/migrations/__pycache__/0027_permissionsrole_can_manage_components.cpython-38.pyc,sha256=Ju4FLSKUNoJ419bAp_Np_MKrSyxzlJnYGfeNYg7HC5M,721
|
|
10449
|
+
simo/users/migrations/__pycache__/0028_auto_20240506_1146.cpython-38.pyc,sha256=e8J_lTYJMixdqV37OgonG9ndd9gjn1E9hVj-jh4bxGw,874
|
|
10444
10450
|
simo/users/migrations/__pycache__/__init__.cpython-38.pyc,sha256=NKq7WLgktK8WV1oOqCPbAbdkrPV5GRGhYx4VxxI4dcs,170
|
|
10445
10451
|
simo/users/templates/conf/mosquitto.conf,sha256=1eIGNuRu4Y3hfAU6qiWix648eCRrw0oOT24PnyFI4ys,189
|
|
10446
10452
|
simo/users/templates/conf/mosquitto_acls.conf,sha256=ga44caTDNQE0CBKw55iM2jOuna6-9fKGwAhjyERZdRE,500
|
|
@@ -10450,8 +10456,8 @@ simo/users/templates/invitations/expired_msg.html,sha256=47DEQpj8HBSa-_TImW-5JCe
|
|
|
10450
10456
|
simo/users/templates/invitations/expired_suggestion.html,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10451
10457
|
simo/users/templates/invitations/taken_msg.html,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10452
10458
|
simo/users/templates/invitations/taken_suggestion.html,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10453
|
-
simo-2.0.
|
|
10454
|
-
simo-2.0.
|
|
10455
|
-
simo-2.0.
|
|
10456
|
-
simo-2.0.
|
|
10457
|
-
simo-2.0.
|
|
10459
|
+
simo-2.0.27.dist-info/LICENSE.md,sha256=M7wm1EmMGDtwPRdg7kW4d00h1uAXjKOT3HFScYQMeiE,34916
|
|
10460
|
+
simo-2.0.27.dist-info/METADATA,sha256=ZfFs5I2gydjhtWLdXn1RkHW05xtXHAe7R9uZed92SUQ,1730
|
|
10461
|
+
simo-2.0.27.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
10462
|
+
simo-2.0.27.dist-info/top_level.txt,sha256=GmS1hrAbpVqn9OWZh6UX82eIOdRLgYA82RG9fe8v4Rs,5
|
|
10463
|
+
simo-2.0.27.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|