simo 2.0.35__py3-none-any.whl → 2.0.38__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__/controllers.cpython-38.pyc +0 -0
- simo/core/__pycache__/forms.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/controllers.py +10 -0
- simo/core/forms.py +15 -1
- simo/core/serializers.py +0 -1
- simo/core/utils/__pycache__/formsets.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/operations.cpython-38.pyc +0 -0
- simo/core/utils/formsets.py +20 -2
- simo/core/utils/operations.py +10 -0
- simo/fleet/__pycache__/controllers.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/forms.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/models.cpython-38.pyc +0 -0
- simo/fleet/forms.py +122 -3
- simo/generic/__pycache__/controllers.cpython-38.pyc +0 -0
- simo/generic/__pycache__/forms.cpython-38.pyc +0 -0
- simo/generic/__pycache__/gateways.cpython-38.pyc +0 -0
- simo/generic/controllers.py +101 -1
- simo/generic/forms.py +144 -5
- simo/generic/gateways.py +16 -7
- simo/users/__pycache__/api.cpython-38.pyc +0 -0
- simo/users/__pycache__/serializers.cpython-38.pyc +0 -0
- simo/users/api.py +3 -1
- simo/users/serializers.py +3 -2
- {simo-2.0.35.dist-info → simo-2.0.38.dist-info}/METADATA +1 -1
- {simo-2.0.35.dist-info → simo-2.0.38.dist-info}/RECORD +31 -29
- {simo-2.0.35.dist-info → simo-2.0.38.dist-info}/LICENSE.md +0 -0
- {simo-2.0.35.dist-info → simo-2.0.38.dist-info}/WHEEL +0 -0
- {simo-2.0.35.dist-info → simo-2.0.38.dist-info}/top_level.txt +0 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
simo/core/controllers.py
CHANGED
|
@@ -180,6 +180,16 @@ class ControllerBase(ABC):
|
|
|
180
180
|
else:
|
|
181
181
|
return self.component.change_init_by
|
|
182
182
|
|
|
183
|
+
def _string_to_vals(self, v):
|
|
184
|
+
'''
|
|
185
|
+
Convert a string containing list of values to approporiate list of component values
|
|
186
|
+
:param v:
|
|
187
|
+
:return:
|
|
188
|
+
'''
|
|
189
|
+
val_type = type(self.default_value)
|
|
190
|
+
v = str(v).strip('(').strip('[').rstrip(')').rstrip(']')
|
|
191
|
+
return [val_type(val.strip()) for val in v.split(',')]
|
|
192
|
+
|
|
183
193
|
def send(self, value):
|
|
184
194
|
self.component.refresh_from_db()
|
|
185
195
|
|
simo/core/forms.py
CHANGED
|
@@ -291,7 +291,7 @@ class ComponentAdminForm(forms.ModelForm):
|
|
|
291
291
|
|
|
292
292
|
class Meta:
|
|
293
293
|
model = Component
|
|
294
|
-
fields = 'name', 'icon', 'zone', 'category', 'show_in_app', 'notes'
|
|
294
|
+
fields = 'name', 'icon', 'zone', 'category', 'show_in_app', 'notes',
|
|
295
295
|
widgets = {
|
|
296
296
|
'icon': autocomplete.ModelSelect2(
|
|
297
297
|
url='autocomplete-icon', attrs={'data-html': True}
|
|
@@ -466,6 +466,13 @@ class NumericSensorForm(BaseComponentForm):
|
|
|
466
466
|
ValueLimitForm, can_delete=True, can_order=True, extra=0, max_num=3
|
|
467
467
|
), label="Graph Limits"
|
|
468
468
|
)
|
|
469
|
+
value_units = forms.CharField(required=False)
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def __init__(self, *args, **kwargs):
|
|
473
|
+
super().__init__(*args, **kwargs)
|
|
474
|
+
self.fields['value_units'].initial = self.controller.default_value_units
|
|
475
|
+
|
|
469
476
|
|
|
470
477
|
|
|
471
478
|
class MultiSensorConfigForm(BaseComponentForm):
|
|
@@ -660,6 +667,7 @@ class DimmerConfigForm(BaseComponentForm):
|
|
|
660
667
|
max = forms.FloatField(
|
|
661
668
|
initial=1.0, help_text="Maximum component value."
|
|
662
669
|
)
|
|
670
|
+
value_units = forms.CharField(required=False)
|
|
663
671
|
inverse = forms.BooleanField(
|
|
664
672
|
label=_("Inverse dimmer signal"), required=False
|
|
665
673
|
)
|
|
@@ -676,6 +684,7 @@ class DimmerConfigForm(BaseComponentForm):
|
|
|
676
684
|
|
|
677
685
|
def __init__(self, *args, **kwargs):
|
|
678
686
|
super().__init__(*args, **kwargs)
|
|
687
|
+
self.fields['value_units'].initial = self.controller.default_value_units
|
|
679
688
|
if self.instance.pk:
|
|
680
689
|
self.fields['slaves'].initial = self.instance.slaves.all()
|
|
681
690
|
|
|
@@ -704,6 +713,11 @@ class DimmerPlusConfigForm(BaseComponentForm):
|
|
|
704
713
|
secondary_max = forms.FloatField(
|
|
705
714
|
initial=1.0, help_text="Maximum secondary value."
|
|
706
715
|
)
|
|
716
|
+
value_units = forms.CharField(required=False)
|
|
717
|
+
|
|
718
|
+
def __init__(self, *args, **kwargs):
|
|
719
|
+
super().__init__(*args, **kwargs)
|
|
720
|
+
self.fields['value_units'].initial = self.controller.default_value_units
|
|
707
721
|
|
|
708
722
|
|
|
709
723
|
class RGBWConfigForm(BaseComponentForm):
|
simo/core/serializers.py
CHANGED
|
@@ -375,7 +375,6 @@ class ComponentSerializer(FormSerializer):
|
|
|
375
375
|
user_role = self.context['request'].user.get_role(
|
|
376
376
|
self.context['instance']
|
|
377
377
|
)
|
|
378
|
-
print("FORM BASIC FIELDS: ", form.basic_fields)
|
|
379
378
|
if not user_role.is_superuser and user_role.is_owner:
|
|
380
379
|
for field_name in list(form.fields.keys()):
|
|
381
380
|
if field_name not in form.basic_fields:
|
|
Binary file
|
|
Binary file
|
simo/core/utils/formsets.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import copy
|
|
2
1
|
from django import forms
|
|
3
2
|
from django.db import models
|
|
4
3
|
from django.template.loader import render_to_string
|
|
@@ -148,10 +147,21 @@ class FormsetField(forms.Field):
|
|
|
148
147
|
if formset_data.get('%s-%d-DELETE' % (prefix, i)) == 'on':
|
|
149
148
|
continue
|
|
150
149
|
form_data = {}
|
|
150
|
+
|
|
151
151
|
for field_name, field in self.formset_cls().form.declared_fields.items():
|
|
152
152
|
form_data[field_name] = formset_data.get(
|
|
153
153
|
'%s-%d-%s' % (prefix, i, field_name)
|
|
154
154
|
)
|
|
155
|
+
|
|
156
|
+
f_data = {}
|
|
157
|
+
for key, val in form_data.items():
|
|
158
|
+
f_data[f"{self.formset_cls.form.prefix}-{key}"] = val
|
|
159
|
+
form = self.formset_cls.form(f_data)
|
|
160
|
+
if form.is_valid():
|
|
161
|
+
form_data = form.cleaned_data
|
|
162
|
+
|
|
163
|
+
for field_name, field in self.formset_cls().form.declared_fields.items():
|
|
164
|
+
|
|
155
165
|
if isinstance(field, forms.models.ModelChoiceField):
|
|
156
166
|
if isinstance(form_data[field_name], models.Model):
|
|
157
167
|
form_data[field_name] = form_data[field_name].pk
|
|
@@ -164,13 +174,21 @@ class FormsetField(forms.Field):
|
|
|
164
174
|
elif isinstance(field, forms.fields.BooleanField):
|
|
165
175
|
form_data[field_name] = form_data[field_name] == 'on'
|
|
166
176
|
elif isinstance(field, forms.fields.IntegerField):
|
|
167
|
-
|
|
177
|
+
try:
|
|
178
|
+
form_data[field_name] = int(form_data[field_name])
|
|
179
|
+
except:
|
|
180
|
+
form_data[field_name] = None
|
|
181
|
+
|
|
168
182
|
if self.widget.formset.can_order:
|
|
169
183
|
form_data['order'] = int(formset_data.get(
|
|
170
184
|
'%s-%d-ORDER' % (prefix, i), 0
|
|
171
185
|
))
|
|
186
|
+
|
|
187
|
+
|
|
172
188
|
cleaned_value.append(form_data)
|
|
173
189
|
|
|
190
|
+
|
|
191
|
+
|
|
174
192
|
if self.widget.formset.can_order:
|
|
175
193
|
cleaned_value = sorted(cleaned_value, key=lambda d: d['order'])
|
|
176
194
|
for i in range(len(cleaned_value)):
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
simo/fleet/forms.py
CHANGED
|
@@ -84,8 +84,7 @@ class InterfaceAdminForm(forms.ModelForm):
|
|
|
84
84
|
|
|
85
85
|
class ColonelComponentForm(BaseComponentForm):
|
|
86
86
|
colonel = forms.ModelChoiceField(
|
|
87
|
-
label="Colonel", queryset=Colonel.objects.all()
|
|
88
|
-
help_text="ATENTION! Changing Colonel after component creation is not recommended!"
|
|
87
|
+
label="Colonel", queryset=Colonel.objects.all()
|
|
89
88
|
)
|
|
90
89
|
|
|
91
90
|
def clean_colonel(self):
|
|
@@ -133,6 +132,7 @@ class ColonelComponentForm(BaseComponentForm):
|
|
|
133
132
|
updated_vals[key] = int(val)
|
|
134
133
|
self.cleaned_data['controls'][i] = updated_vals
|
|
135
134
|
|
|
135
|
+
pins_in_use = []
|
|
136
136
|
formset_errors = {}
|
|
137
137
|
for i, control in enumerate(self.cleaned_data['controls']):
|
|
138
138
|
if pin_instances[i].colonel != self.cleaned_data['colonel']:
|
|
@@ -144,6 +144,11 @@ class ColonelComponentForm(BaseComponentForm):
|
|
|
144
144
|
formset_errors[i] = {
|
|
145
145
|
'pin': f"{pin_instances[i]} is already occupied by {pin_instances[i].occupied_by}!"
|
|
146
146
|
}
|
|
147
|
+
elif pin_instances[i].no in pins_in_use:
|
|
148
|
+
formset_errors[i] = {
|
|
149
|
+
'pin': f"{pin_instances[i].no} is already in use!"
|
|
150
|
+
}
|
|
151
|
+
pins_in_use.append(pin_instances[i].no)
|
|
147
152
|
|
|
148
153
|
errors_list = []
|
|
149
154
|
if formset_errors:
|
|
@@ -214,6 +219,22 @@ class ColonelBinarySensorConfigForm(ColonelComponentForm):
|
|
|
214
219
|
"Set debounce value in milliseconds, to remediate this. "
|
|
215
220
|
"50ms offers a good starting point!"
|
|
216
221
|
)
|
|
222
|
+
hold_time = forms.TypedChoiceField(
|
|
223
|
+
initial=0, coerce=int, choices=(
|
|
224
|
+
(0, "-----"),
|
|
225
|
+
(1, "10 s"), (2, "20 s"), (3, "30 s"), (4, "40 s"), (5, "50 s"),
|
|
226
|
+
(6, "1 min"), (9, "1.5 min"), (12, "2 min"), (18, "3 min"),
|
|
227
|
+
(30, "5 min"), (60, "10 min"), (120, "20 min"),
|
|
228
|
+
), required=False,
|
|
229
|
+
help_text="Holds positive value for given amount of time "
|
|
230
|
+
"after last negative value has been observed. "
|
|
231
|
+
"Super useful with regular motion detectors for controlling "
|
|
232
|
+
"lights or other means of automation."
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
def __init__(self, *args, **kwargs):
|
|
236
|
+
super().__init__(*args, **kwargs)
|
|
237
|
+
self.basic_fields.append('hold_time')
|
|
217
238
|
|
|
218
239
|
def clean(self):
|
|
219
240
|
super().clean()
|
|
@@ -424,6 +445,17 @@ class BME680SensorConfigForm(ColonelComponentForm):
|
|
|
424
445
|
|
|
425
446
|
)
|
|
426
447
|
|
|
448
|
+
def clean(self):
|
|
449
|
+
if not self.cleaned_data.get('colonel'):
|
|
450
|
+
return self.cleaned_data
|
|
451
|
+
if self.cleaned_data['interface'].colonel != self.cleaned_data['colonel']:
|
|
452
|
+
self.add_error(
|
|
453
|
+
'interface',
|
|
454
|
+
f"This interface is on {self.cleaned_data['interface'].colonel}, "
|
|
455
|
+
f"however we need an interface from {self.cleaned_data['colonel']}."
|
|
456
|
+
)
|
|
457
|
+
return self.cleaned_data
|
|
458
|
+
|
|
427
459
|
def save(self, commit=True):
|
|
428
460
|
if 'interface' in self.cleaned_data:
|
|
429
461
|
self.instance.config['i2c_interface'] = self.cleaned_data['interface'].no
|
|
@@ -455,6 +487,17 @@ class MPC9808SensorConfigForm(ColonelComponentForm):
|
|
|
455
487
|
|
|
456
488
|
)
|
|
457
489
|
|
|
490
|
+
def clean(self):
|
|
491
|
+
if not self.cleaned_data.get('colonel'):
|
|
492
|
+
return self.cleaned_data
|
|
493
|
+
if self.cleaned_data['interface'].colonel != self.cleaned_data['colonel']:
|
|
494
|
+
self.add_error(
|
|
495
|
+
'interface',
|
|
496
|
+
f"This interface is on {self.cleaned_data['interface'].colonel}, "
|
|
497
|
+
f"however we need an interface from {self.cleaned_data['colonel']}."
|
|
498
|
+
)
|
|
499
|
+
return self.cleaned_data
|
|
500
|
+
|
|
458
501
|
def save(self, commit=True):
|
|
459
502
|
if 'interface' in self.cleaned_data:
|
|
460
503
|
self.instance.config['i2c_interface'] = self.cleaned_data['interface'].no
|
|
@@ -560,6 +603,14 @@ class ColonelSwitchConfigForm(ColonelComponentForm):
|
|
|
560
603
|
if self.cleaned_data.get('controls'):
|
|
561
604
|
self._clean_controls()
|
|
562
605
|
|
|
606
|
+
if self.cleaned_data.get('output_pin') and self.cleaned_data.get('controls'):
|
|
607
|
+
for ctrl in self.cleaned_data['controls']:
|
|
608
|
+
if ctrl['pin'] == self.cleaned_data['output_pin']:
|
|
609
|
+
self.add_error(
|
|
610
|
+
"output_pin",
|
|
611
|
+
"Can't be used as control pin at the same time!"
|
|
612
|
+
)
|
|
613
|
+
|
|
563
614
|
return self.cleaned_data
|
|
564
615
|
|
|
565
616
|
|
|
@@ -592,6 +643,7 @@ class ColonelPWMOutputConfigForm(ColonelComponentForm):
|
|
|
592
643
|
required=True, initial=100,
|
|
593
644
|
help_text="Maximum component value"
|
|
594
645
|
)
|
|
646
|
+
value_units = forms.CharField(required=False)
|
|
595
647
|
duty_min = forms.IntegerField(
|
|
596
648
|
min_value=0, max_value=1023, required=True, initial=0,
|
|
597
649
|
help_text="Minumum PWM signal output duty (0 - 1023)"
|
|
@@ -621,6 +673,7 @@ class ColonelPWMOutputConfigForm(ColonelComponentForm):
|
|
|
621
673
|
required=True, initial=100,
|
|
622
674
|
help_text="Component ON value when used with toggle switch"
|
|
623
675
|
)
|
|
676
|
+
|
|
624
677
|
slaves = forms.ModelMultipleChoiceField(
|
|
625
678
|
required=False,
|
|
626
679
|
queryset=Component.objects.filter(
|
|
@@ -639,7 +692,10 @@ class ColonelPWMOutputConfigForm(ColonelComponentForm):
|
|
|
639
692
|
|
|
640
693
|
def __init__(self, *args, **kwargs):
|
|
641
694
|
super().__init__(*args, **kwargs)
|
|
642
|
-
self.
|
|
695
|
+
self.fields['value_units'].initial = self.controller.default_value_units
|
|
696
|
+
self.basic_fields.extend(
|
|
697
|
+
['value_units', 'turn_on_time', 'turn_off_time', 'skew']
|
|
698
|
+
)
|
|
643
699
|
if self.instance.pk and 'slaves' in self.fields:
|
|
644
700
|
self.fields['slaves'].initial = self.instance.slaves.all()
|
|
645
701
|
|
|
@@ -654,6 +710,13 @@ class ColonelPWMOutputConfigForm(ColonelComponentForm):
|
|
|
654
710
|
self._clean_pin('output_pin')
|
|
655
711
|
if 'controls' in self.cleaned_data:
|
|
656
712
|
self._clean_controls()
|
|
713
|
+
if self.cleaned_data.get('output_pin') and self.cleaned_data.get('controls'):
|
|
714
|
+
for ctrl in self.cleaned_data['controls']:
|
|
715
|
+
if ctrl['pin'] == self.cleaned_data['output_pin']:
|
|
716
|
+
self.add_error(
|
|
717
|
+
"output_pin",
|
|
718
|
+
"Can't be used as control pin at the same time!"
|
|
719
|
+
)
|
|
657
720
|
return self.cleaned_data
|
|
658
721
|
|
|
659
722
|
|
|
@@ -754,6 +817,14 @@ class ColonelRGBLightConfigForm(ColonelComponentForm):
|
|
|
754
817
|
if self.cleaned_data.get('controls'):
|
|
755
818
|
self._clean_controls()
|
|
756
819
|
|
|
820
|
+
if self.cleaned_data.get('output_pin') and self.cleaned_data.get('controls'):
|
|
821
|
+
for ctrl in self.cleaned_data['controls']:
|
|
822
|
+
if ctrl['pin'] == self.cleaned_data['output_pin']:
|
|
823
|
+
self.add_error(
|
|
824
|
+
"output_pin",
|
|
825
|
+
"Can't be used as control pin at the same time!"
|
|
826
|
+
)
|
|
827
|
+
|
|
757
828
|
if 'color_order' in self.cleaned_data:
|
|
758
829
|
if self.cleaned_data.get('color_order'):
|
|
759
830
|
if self.cleaned_data['has_white']:
|
|
@@ -818,6 +889,12 @@ class DualMotorValveForm(ColonelComponentForm):
|
|
|
818
889
|
self._clean_pin('open_pin')
|
|
819
890
|
if self.cleaned_data.get('close_pin'):
|
|
820
891
|
self._clean_pin('close_pin')
|
|
892
|
+
if self.cleaned_data.get('open_pin') \
|
|
893
|
+
and self.cleaned_data.get('close_pin') \
|
|
894
|
+
and self.cleaned_data['open_pin'] == self.cleaned_data['close_pin']:
|
|
895
|
+
self.add_error(
|
|
896
|
+
'close_pin', "Can't be the same as open pin!"
|
|
897
|
+
)
|
|
821
898
|
return self.cleaned_data
|
|
822
899
|
|
|
823
900
|
def save(self, commit=True):
|
|
@@ -905,6 +982,13 @@ class BlindsConfigForm(ColonelComponentForm):
|
|
|
905
982
|
def clean(self):
|
|
906
983
|
super().clean()
|
|
907
984
|
|
|
985
|
+
if self.cleaned_data.get('open_pin') \
|
|
986
|
+
and self.cleaned_data.get('close_pin') \
|
|
987
|
+
and self.cleaned_data['open_pin'] == self.cleaned_data['close_pin']:
|
|
988
|
+
self.add_error(
|
|
989
|
+
'close_pin', "Can't be the same as open pin!"
|
|
990
|
+
)
|
|
991
|
+
|
|
908
992
|
if self.cleaned_data.get('open_pin'):
|
|
909
993
|
self._clean_pin('open_pin')
|
|
910
994
|
if self.cleaned_data.get('close_pin'):
|
|
@@ -926,6 +1010,23 @@ class BlindsConfigForm(ColonelComponentForm):
|
|
|
926
1010
|
return self.cleaned_data
|
|
927
1011
|
|
|
928
1012
|
self._clean_controls()
|
|
1013
|
+
|
|
1014
|
+
if self.cleaned_data.get('open_pin'):
|
|
1015
|
+
for ctrl in self.cleaned_data['controls']:
|
|
1016
|
+
if ctrl['pin'] == self.cleaned_data['output_pin']:
|
|
1017
|
+
self.add_error(
|
|
1018
|
+
"open_pin",
|
|
1019
|
+
"Can't be used as control pin at the same time!"
|
|
1020
|
+
)
|
|
1021
|
+
|
|
1022
|
+
if self.cleaned_data.get('close_pin'):
|
|
1023
|
+
for ctrl in self.cleaned_data['controls']:
|
|
1024
|
+
if ctrl['pin'] == self.cleaned_data['close_pin']:
|
|
1025
|
+
self.add_error(
|
|
1026
|
+
"close_pin",
|
|
1027
|
+
"Can't be used as control pin at the same time!"
|
|
1028
|
+
)
|
|
1029
|
+
|
|
929
1030
|
return self.cleaned_data
|
|
930
1031
|
|
|
931
1032
|
def save(self, commit=True):
|
|
@@ -974,6 +1075,13 @@ class BurglarSmokeDetectorConfigForm(ColonelComponentForm):
|
|
|
974
1075
|
if 'power_pin' in self.cleaned_data:
|
|
975
1076
|
self._clean_pin('power_pin')
|
|
976
1077
|
|
|
1078
|
+
if self.cleaned_data.get('sensor_pin') \
|
|
1079
|
+
and self.cleaned_data.get('power_pin') \
|
|
1080
|
+
and self.cleaned_data['sensor_pin'] == self.cleaned_data['power_pin']:
|
|
1081
|
+
self.add_error(
|
|
1082
|
+
'power_pin', "Can't be the same as sensor pin!"
|
|
1083
|
+
)
|
|
1084
|
+
|
|
977
1085
|
return self.cleaned_data
|
|
978
1086
|
|
|
979
1087
|
def save(self, commit=True):
|
|
@@ -1049,6 +1157,17 @@ class DALIDeviceConfigForm(ColonelComponentForm):
|
|
|
1049
1157
|
)
|
|
1050
1158
|
return self.cleaned_data['interface']
|
|
1051
1159
|
|
|
1160
|
+
def clean(self):
|
|
1161
|
+
if not self.cleaned_data.get('colonel'):
|
|
1162
|
+
return self.cleaned_data
|
|
1163
|
+
if self.cleaned_data['interface'].colonel != self.cleaned_data['colonel']:
|
|
1164
|
+
self.add_error(
|
|
1165
|
+
'interface',
|
|
1166
|
+
f"This interface is on {self.cleaned_data['interface'].colonel}, "
|
|
1167
|
+
f"however we need an interface from {self.cleaned_data['colonel']}."
|
|
1168
|
+
)
|
|
1169
|
+
return self.cleaned_data
|
|
1170
|
+
|
|
1052
1171
|
def save(self, commit=True, update_colonel_config=True):
|
|
1053
1172
|
if 'interface' in self.cleaned_data:
|
|
1054
1173
|
self.instance.config['dali_interface'] = \
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
simo/generic/controllers.py
CHANGED
|
@@ -15,6 +15,7 @@ from simo.users.utils import get_system_user
|
|
|
15
15
|
from simo.core.events import GatewayObjectCommand
|
|
16
16
|
from simo.core.models import RUN_STATUS_CHOICES_MAP, Component
|
|
17
17
|
from simo.core.utils.helpers import get_random_string
|
|
18
|
+
from simo.core.utils.operations import OPERATIONS
|
|
18
19
|
from simo.core.controllers import (
|
|
19
20
|
BEFORE_SEND, BEFORE_SET, ControllerBase,
|
|
20
21
|
BinarySensor, NumericSensor, MultiSensor, Switch, Dimmer, DimmerPlus,
|
|
@@ -35,7 +36,8 @@ from .app_widgets import (
|
|
|
35
36
|
WateringWidget, StateSelectWidget, AlarmClockWidget
|
|
36
37
|
)
|
|
37
38
|
from .forms import (
|
|
38
|
-
ScriptConfigForm,
|
|
39
|
+
ScriptConfigForm, PresenceLightingConfigForm,
|
|
40
|
+
ThermostatConfigForm, AlarmGroupConfigForm,
|
|
39
41
|
IPCameraConfigForm, WeatherForecastForm, GateConfigForm,
|
|
40
42
|
BlindsConfigForm, WateringConfigForm, StateSelectForm,
|
|
41
43
|
AlarmClockConfigForm
|
|
@@ -99,6 +101,104 @@ class Script(ControllerBase, TimerMixin):
|
|
|
99
101
|
self.send('start')
|
|
100
102
|
|
|
101
103
|
|
|
104
|
+
class PresenceLighting(Script):
|
|
105
|
+
name = _("Presence lighting")
|
|
106
|
+
config_form = PresenceLightingConfigForm
|
|
107
|
+
|
|
108
|
+
# script specific variables
|
|
109
|
+
sensors = {}
|
|
110
|
+
light_org_values = {}
|
|
111
|
+
is_on = None
|
|
112
|
+
turn_off_task = None
|
|
113
|
+
|
|
114
|
+
def _run(self):
|
|
115
|
+
while True:
|
|
116
|
+
self._on_sensor()
|
|
117
|
+
time.sleep(10)
|
|
118
|
+
|
|
119
|
+
def _on_sensor(self, sensor=None):
|
|
120
|
+
|
|
121
|
+
self.component.refresh_from_db()
|
|
122
|
+
for id in self.component.config['presence_sensors']:
|
|
123
|
+
if id not in self.sensors:
|
|
124
|
+
sensor = Component.objects.filter(id=id).first()
|
|
125
|
+
if sensor:
|
|
126
|
+
sensor.on_change(self._on_sensor)
|
|
127
|
+
self.sensors[id] = sensor
|
|
128
|
+
|
|
129
|
+
if sensor:
|
|
130
|
+
self.sensors[sensor.id] = sensor
|
|
131
|
+
|
|
132
|
+
presence_values = [s.value for id, s in self.sensors.items()]
|
|
133
|
+
if self.component.config.get('act_on', 0):
|
|
134
|
+
must_on = any(presence_values)
|
|
135
|
+
else:
|
|
136
|
+
must_on = all(presence_values)
|
|
137
|
+
|
|
138
|
+
additional_conditions_met = True
|
|
139
|
+
for condition in self.component.config.get('conditions', []):
|
|
140
|
+
if not additional_conditions_met:
|
|
141
|
+
continue
|
|
142
|
+
comp = Component.objects.filter(
|
|
143
|
+
id=condition.get('component', 0)
|
|
144
|
+
).first()
|
|
145
|
+
if not comp:
|
|
146
|
+
continue
|
|
147
|
+
op = OPERATIONS.get(condition.get('op'))
|
|
148
|
+
if not op:
|
|
149
|
+
continue
|
|
150
|
+
if condition['op'] == 'in':
|
|
151
|
+
if comp.value not in self._string_to_vals(condition['value']):
|
|
152
|
+
additional_conditions_met = False
|
|
153
|
+
continue
|
|
154
|
+
|
|
155
|
+
if not op(comp.value, condition['value']):
|
|
156
|
+
additional_conditions_met = False
|
|
157
|
+
|
|
158
|
+
if must_on and not additional_conditions_met:
|
|
159
|
+
print("Presence detected, but additional conditions not met!")
|
|
160
|
+
|
|
161
|
+
if must_on and additional_conditions_met and not self.is_on:
|
|
162
|
+
print("Turn the lights ON!")
|
|
163
|
+
self.is_on = True
|
|
164
|
+
if self.turn_off_task:
|
|
165
|
+
self.turn_off_task.cancel()
|
|
166
|
+
self.turn_off_task = None
|
|
167
|
+
self.light_org_values = {}
|
|
168
|
+
for id in self.component.config['lights']:
|
|
169
|
+
comp = Component.objects.filter(id=id).first()
|
|
170
|
+
if not comp or not comp.controller:
|
|
171
|
+
continue
|
|
172
|
+
self.light_org_values[comp.id] = comp.value
|
|
173
|
+
comp.controller.send(self.component.config['on_value'])
|
|
174
|
+
return
|
|
175
|
+
|
|
176
|
+
if self.is_on or self.is_on is None:
|
|
177
|
+
if not additional_conditions_met:
|
|
178
|
+
return self._turn_it_off()
|
|
179
|
+
if not any(presence_values):
|
|
180
|
+
if not self.component.config.get('hold_time', 0):
|
|
181
|
+
return self._turn_it_off()
|
|
182
|
+
if not self.turn_off_task:
|
|
183
|
+
self.turn_off_task = threading.Timer(
|
|
184
|
+
self.component.config['hold_time'] * 10,
|
|
185
|
+
self._turn_it_off
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
def _turn_it_off(self):
|
|
189
|
+
print("Turn the lights OFF!")
|
|
190
|
+
self.is_on = False
|
|
191
|
+
self.turn_off_task = None
|
|
192
|
+
for id in self.component.config['lights']:
|
|
193
|
+
comp = Component.objects.filter(id=id).first()
|
|
194
|
+
if not comp or not comp.controller:
|
|
195
|
+
continue
|
|
196
|
+
if self.component.config['off_value'] == 0:
|
|
197
|
+
comp.send(0)
|
|
198
|
+
else:
|
|
199
|
+
comp.send(self.light_org_values.get(comp.id, 0))
|
|
200
|
+
|
|
201
|
+
|
|
102
202
|
class Thermostat(ControllerBase):
|
|
103
203
|
name = _("Thermostat")
|
|
104
204
|
base_type = 'thermostat'
|
simo/generic/forms.py
CHANGED
|
@@ -7,7 +7,7 @@ from django.contrib.contenttypes.models import ContentType
|
|
|
7
7
|
from simo.core.forms import HiddenField, BaseComponentForm
|
|
8
8
|
from simo.core.models import Icon, Component
|
|
9
9
|
from simo.core.controllers import (
|
|
10
|
-
BinarySensor, NumericSensor, MultiSensor, Switch
|
|
10
|
+
BEFORE_SET, BinarySensor, NumericSensor, MultiSensor, Switch
|
|
11
11
|
)
|
|
12
12
|
from simo.core.widgets import PythonCode, LogOutputWidget
|
|
13
13
|
from dal import autocomplete, forward
|
|
@@ -72,6 +72,145 @@ class ScriptConfigForm(BaseComponentForm):
|
|
|
72
72
|
return fieldsets
|
|
73
73
|
|
|
74
74
|
|
|
75
|
+
class ConditionForm(forms.Form):
|
|
76
|
+
component = forms.ModelChoiceField(
|
|
77
|
+
Component.objects.all(),
|
|
78
|
+
widget=autocomplete.ModelSelect2(
|
|
79
|
+
url='autocomplete-component', attrs={'data-html': True},
|
|
80
|
+
),
|
|
81
|
+
)
|
|
82
|
+
op = forms.ChoiceField(
|
|
83
|
+
initial="==", choices=(
|
|
84
|
+
('==', "is equal to"),
|
|
85
|
+
('>', "is greather than"), ('>=', "Is greather or equal to"),
|
|
86
|
+
('<', "is lower than"), ('<=', "is lower or equal to"),
|
|
87
|
+
('in', "is one of")
|
|
88
|
+
)
|
|
89
|
+
)
|
|
90
|
+
value = forms.CharField()
|
|
91
|
+
prefix = 'breach_events'
|
|
92
|
+
|
|
93
|
+
def clean(self):
|
|
94
|
+
if not self.cleaned_data.get('component'):
|
|
95
|
+
return self.cleaned_data
|
|
96
|
+
if not self.cleaned_data.get('op'):
|
|
97
|
+
return self.cleaned_data
|
|
98
|
+
component = self.cleaned_data.get('component')
|
|
99
|
+
|
|
100
|
+
if self.cleaned_data['op'] == 'in':
|
|
101
|
+
self.cleaned_data['value'] = self.cleaned_data['value']\
|
|
102
|
+
.strip('(').strip('[').rstrip(')').rstrip(']').strip()
|
|
103
|
+
values = self.cleaned_data['value'].split(',')
|
|
104
|
+
else:
|
|
105
|
+
values = [self.cleaned_data['value']]
|
|
106
|
+
|
|
107
|
+
final_values = []
|
|
108
|
+
controller_val_type = type(component.controller.default_value)
|
|
109
|
+
for val in values:
|
|
110
|
+
val = val.strip()
|
|
111
|
+
if controller_val_type == 'bool':
|
|
112
|
+
if val.lower() in ('0', 'false', 'none', 'null'):
|
|
113
|
+
final_val = False
|
|
114
|
+
else:
|
|
115
|
+
final_val = True
|
|
116
|
+
else:
|
|
117
|
+
try:
|
|
118
|
+
final_val = controller_val_type(val)
|
|
119
|
+
except:
|
|
120
|
+
self.add_error(
|
|
121
|
+
'value', f"{val} bad value type for selected component."
|
|
122
|
+
)
|
|
123
|
+
continue
|
|
124
|
+
try:
|
|
125
|
+
component.controller._validate_val(final_val, BEFORE_SET)
|
|
126
|
+
except Exception as e:
|
|
127
|
+
self.add_error(
|
|
128
|
+
'value', f"{val} is not compatible with selected component."
|
|
129
|
+
)
|
|
130
|
+
continue
|
|
131
|
+
final_values.append(final_val)
|
|
132
|
+
|
|
133
|
+
if self.cleaned_data['op'] == 'in':
|
|
134
|
+
self.cleaned_data['value'] = ', '.join(str(v) for v in final_values)
|
|
135
|
+
else:
|
|
136
|
+
self.cleaned_data['value'] = final_values[0]
|
|
137
|
+
|
|
138
|
+
return self.cleaned_data
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class PresenceLightingConfigForm(BaseComponentForm):
|
|
142
|
+
lights = forms.ModelMultipleChoiceField(
|
|
143
|
+
Component.objects.filter(
|
|
144
|
+
base_type__in=('switch', 'dimmer', 'rgbw-light', 'rgb-light')
|
|
145
|
+
),
|
|
146
|
+
required=True,
|
|
147
|
+
widget=autocomplete.ModelSelect2Multiple(
|
|
148
|
+
url='autocomplete-component', attrs={'data-html': True},
|
|
149
|
+
forward=(
|
|
150
|
+
forward.Const(['switch', 'dimmer', 'rgbw-light', 'rgb-light'],
|
|
151
|
+
'base_type'),
|
|
152
|
+
)
|
|
153
|
+
)
|
|
154
|
+
)
|
|
155
|
+
on_value = forms.IntegerField(
|
|
156
|
+
min_value=0, initial=100,
|
|
157
|
+
help_text="Value applicable for dimmers. "
|
|
158
|
+
"Switches will receive tunrn on command."
|
|
159
|
+
)
|
|
160
|
+
off_value = forms.TypedChoiceField(
|
|
161
|
+
coerce=int, initial=1, choices=(
|
|
162
|
+
(0, "0"), (1, "Original value before turning the lights on.")
|
|
163
|
+
)
|
|
164
|
+
)
|
|
165
|
+
presence_sensors = forms.ModelMultipleChoiceField(
|
|
166
|
+
Component.objects.filter(base_type='binary-sensor'),
|
|
167
|
+
required=True,
|
|
168
|
+
widget=autocomplete.ModelSelect2Multiple(
|
|
169
|
+
url='autocomplete-component', attrs={'data-html': True},
|
|
170
|
+
forward=(forward.Const(['binary-sensor'], 'base_type'),)
|
|
171
|
+
)
|
|
172
|
+
)
|
|
173
|
+
act_on = forms.TypedChoiceField(
|
|
174
|
+
coerce=int, initial=0, choices=(
|
|
175
|
+
(0, "At least one sensor detects presence"),
|
|
176
|
+
(1, "All sensors detect presence"),
|
|
177
|
+
)
|
|
178
|
+
)
|
|
179
|
+
hold_time = forms.TypedChoiceField(
|
|
180
|
+
initial=3, coerce=int, required=False, choices=(
|
|
181
|
+
(0, '----'),
|
|
182
|
+
(1, "10 s"), (2, "20 s"), (3, "30 s"), (4, "40 s"), (5, "50 s"),
|
|
183
|
+
(6, "1 min"), (9, "1.5 min"), (12, "2 min"), (18, "3 min"),
|
|
184
|
+
(30, "5 min"), (60, "10 min"), (120, "20 min"),
|
|
185
|
+
),
|
|
186
|
+
help_text="Hold off time after last presence detector is deactivated."
|
|
187
|
+
)
|
|
188
|
+
conditions = FormsetField(
|
|
189
|
+
formset_factory(
|
|
190
|
+
ConditionForm, can_delete=True, can_order=True, extra=0
|
|
191
|
+
), label='Additional conditions'
|
|
192
|
+
)
|
|
193
|
+
log = forms.CharField(
|
|
194
|
+
widget=forms.HiddenInput, required=False
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
def __init__(self, *args, **kwargs):
|
|
198
|
+
super().__init__(*args, **kwargs)
|
|
199
|
+
if self.instance.pk:
|
|
200
|
+
prefix = get_script_prefix()
|
|
201
|
+
if prefix == '/':
|
|
202
|
+
prefix = ''
|
|
203
|
+
self.fields['log'].widget = LogOutputWidget(
|
|
204
|
+
prefix + '/ws/log/%d/%d/' % (
|
|
205
|
+
ContentType.objects.get_for_model(Component).id,
|
|
206
|
+
self.instance.id
|
|
207
|
+
)
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
|
|
75
214
|
class ThermostatConfigForm(BaseComponentForm):
|
|
76
215
|
temperature_sensor = forms.ModelChoiceField(
|
|
77
216
|
Component.objects.filter(
|
|
@@ -270,7 +409,6 @@ class AlarmGroupConfigForm(BaseComponentForm):
|
|
|
270
409
|
)
|
|
271
410
|
has_alarm = False
|
|
272
411
|
|
|
273
|
-
|
|
274
412
|
def __init__(self, *args, **kwargs):
|
|
275
413
|
super().__init__(*args, **kwargs)
|
|
276
414
|
from .controllers import AlarmGroup
|
|
@@ -281,9 +419,10 @@ class AlarmGroupConfigForm(BaseComponentForm):
|
|
|
281
419
|
config__is_main=True
|
|
282
420
|
).count()
|
|
283
421
|
)
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
422
|
+
if 'is_main' in self.fields:
|
|
423
|
+
self.fields['is_main'].initial = first_alarm_group
|
|
424
|
+
if first_alarm_group:
|
|
425
|
+
self.fields['is_main'].widget.attrs['disabled'] = 'disabled'
|
|
287
426
|
else:
|
|
288
427
|
if self.instance.config.get('is_main'):
|
|
289
428
|
self.fields['is_main'].widget.attrs['disabled'] = 'disabled'
|
simo/generic/gateways.py
CHANGED
|
@@ -136,14 +136,22 @@ class ScriptRunHandler(multiprocessing.Process):
|
|
|
136
136
|
sys.stderr = StreamToLogger(self.logger, logging.ERROR)
|
|
137
137
|
self.component.value = 'running'
|
|
138
138
|
self.component.save(update_fields=['value'])
|
|
139
|
-
|
|
140
|
-
if
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
139
|
+
|
|
140
|
+
if hasattr(self.component.controller, '_run'):
|
|
141
|
+
def run_code():
|
|
142
|
+
self.component.controller._run()
|
|
143
|
+
else:
|
|
144
|
+
code = self.component.config.get('code')
|
|
145
|
+
def run_code():
|
|
146
|
+
exec(code, globals())
|
|
147
|
+
|
|
148
|
+
if not code:
|
|
149
|
+
self.component.value = 'finished'
|
|
150
|
+
self.component.save(update_fields=['value'])
|
|
151
|
+
return
|
|
144
152
|
print("------START-------")
|
|
145
153
|
try:
|
|
146
|
-
|
|
154
|
+
run_code()
|
|
147
155
|
except:
|
|
148
156
|
print("------ERROR------")
|
|
149
157
|
self.component.value = 'error'
|
|
@@ -259,7 +267,7 @@ class GenericGatewayHandler(BaseObjectCommandsGatewayHandler):
|
|
|
259
267
|
# as well as those who are designed to be kept alive, but
|
|
260
268
|
# got terminated unexpectedly
|
|
261
269
|
for script in Component.objects.filter(
|
|
262
|
-
|
|
270
|
+
base_type='script',
|
|
263
271
|
).filter(
|
|
264
272
|
Q(config__autostart=True) |
|
|
265
273
|
Q(value='error', config__keep_alive=True)
|
|
@@ -302,6 +310,7 @@ class GenericGatewayHandler(BaseObjectCommandsGatewayHandler):
|
|
|
302
310
|
|
|
303
311
|
if isinstance(component.controller, Script):
|
|
304
312
|
if payload.get('set_val') == 'start':
|
|
313
|
+
print("START THIS SCRIPT!!!", component)
|
|
305
314
|
self.start_script(component)
|
|
306
315
|
elif payload.get('set_val') == 'stop':
|
|
307
316
|
self.stop_script(component)
|
|
Binary file
|
|
Binary file
|
simo/users/api.py
CHANGED
|
@@ -35,7 +35,9 @@ class UsersViewSet(mixins.RetrieveModelMixin,
|
|
|
35
35
|
email__in=('system@simo.io', 'device@simo.io')
|
|
36
36
|
) # Exclude system user
|
|
37
37
|
|
|
38
|
-
return queryset.filter(
|
|
38
|
+
return queryset.filter(
|
|
39
|
+
Q(roles__instance=self.instance) | Q(id=self.request.user.id)
|
|
40
|
+
)
|
|
39
41
|
|
|
40
42
|
|
|
41
43
|
def check_permission_to_change(self, request, target_user):
|
simo/users/serializers.py
CHANGED
|
@@ -24,11 +24,12 @@ class UserSerializer(serializers.ModelSerializer):
|
|
|
24
24
|
class Meta:
|
|
25
25
|
model = User
|
|
26
26
|
fields = (
|
|
27
|
-
'id', 'email', 'name', 'avatar', 'role', 'is_active',
|
|
27
|
+
'id', 'email', 'name', 'avatar', 'role', 'is_master', 'is_active',
|
|
28
28
|
'at_home', 'last_action'
|
|
29
29
|
)
|
|
30
30
|
read_only_fields = (
|
|
31
|
-
'id', 'email', 'name', 'avatar', 'at_home', 'last_action', 'ssh_key'
|
|
31
|
+
'id', 'email', 'name', 'avatar', 'at_home', 'last_action', 'ssh_key',
|
|
32
|
+
'is_master'
|
|
32
33
|
)
|
|
33
34
|
|
|
34
35
|
def get_is_active(self, obj):
|
|
@@ -34,11 +34,11 @@ simo/core/auto_urls.py,sha256=0gu-IL7PHobrmKW6ksffiOkAYu-aIorykWdxRNtwGYo,1194
|
|
|
34
34
|
simo/core/autocomplete_views.py,sha256=JT5LA2_Wtr60XYSAIqaXFKFYPjrmkEf6yunXD9y2zco,4022
|
|
35
35
|
simo/core/base_types.py,sha256=yqbIZqBksrAkEuHRbt6iExwPDDy0K5II2NzRCkmOvMU,589
|
|
36
36
|
simo/core/context.py,sha256=98PXAMie43faRVBFkOG22uNpvGRNprcGhzjBFkrxaRY,1367
|
|
37
|
-
simo/core/controllers.py,sha256=
|
|
37
|
+
simo/core/controllers.py,sha256=oPyAKJjPMQFP-gz5V0aDfIxs__6v3b90DocX3R-8wjM,27639
|
|
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=f0_qnyrIem9yj5BKDAenzG5XK_tW7PGKWLuLKb3wPgg,24252
|
|
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=Qdg2-Qh4dLbW0A5Dmtnpct6CUhEuuvdbIskBijQxopU,2360
|
|
@@ -46,7 +46,7 @@ simo/core/middleware.py,sha256=pO52hQOJV_JRmNyUe7zfufSnJFlRITOWX6jwkoPWJhk,2052
|
|
|
46
46
|
simo/core/models.py,sha256=W5rShDy8l6GQzTRFiBZFAuibe_fg_LRwSNk973mv_m8,20134
|
|
47
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=7iE5_ke56GpcQ_cV6LgQIsfG3pAQXPc3t4e60HwogUs,18277
|
|
50
50
|
simo/core/signal_receivers.py,sha256=C6Jk7wVEtyo4hwcrU7L0ijtpK0wce2MNwpyBgSfSJ-U,5467
|
|
51
51
|
simo/core/socket_consumers.py,sha256=n7VE2Fvqt4iEAYLTRbTPOcI-7tszMAADu7gimBxB-Fg,9635
|
|
52
52
|
simo/core/storage.py,sha256=YlxmdRs-zhShWtFKgpJ0qp2NDBuIkJGYC1OJzqkbttQ,572
|
|
@@ -57,7 +57,7 @@ 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=1OisxqtyWMbzpgeeu5vtBW3gp3Nts4Ei4ff1P-SPpq4,13545
|
|
60
|
-
simo/core/__pycache__/api.cpython-38.pyc,sha256=
|
|
60
|
+
simo/core/__pycache__/api.cpython-38.pyc,sha256=YBk4BVAMxAkShEyWgeTkHtTpr4P-tMwrTQI5TTP7vFE,19968
|
|
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
|
|
@@ -65,11 +65,11 @@ simo/core/__pycache__/auto_urls.cpython-38.pyc,sha256=SVl4fF0-yiq7e9gt08jIM6_rL4
|
|
|
65
65
|
simo/core/__pycache__/autocomplete_views.cpython-38.pyc,sha256=hJ6JILI1LqrAtpQMvxnLvljGdW1v1gpvBsD79vFkZ58,3972
|
|
66
66
|
simo/core/__pycache__/base_types.cpython-38.pyc,sha256=CasZJN42cK_ymoQgn5E4s8oOkuZJ18fVHCgN4GPuT7c,735
|
|
67
67
|
simo/core/__pycache__/context.cpython-38.pyc,sha256=MSZPDhqMhCpUuBJl3HCIBHZA3BntYeP8RAnQcdqAH9k,1278
|
|
68
|
-
simo/core/__pycache__/controllers.cpython-38.pyc,sha256=
|
|
68
|
+
simo/core/__pycache__/controllers.cpython-38.pyc,sha256=Rsmre6XaYAbQH4aHHs6c-9EgmEZ8SyHm9Y7FYEteTpM,24542
|
|
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=ksJVqk2X2XV1SmWFeEP2DStU1wvPQ7GaKO0CaFj_pTE,20465
|
|
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=ObkzRjSOs2UQmjwFWDvZHreNzc_P5k7dVA_f7L7S7Q4,2529
|
|
@@ -77,11 +77,11 @@ simo/core/__pycache__/middleware.cpython-38.pyc,sha256=ESR5JPtITo9flczO0672sfzYU
|
|
|
77
77
|
simo/core/__pycache__/models.cpython-38.pyc,sha256=UNX6Btm5ZnpLzSCWgevQnSYzAyDWttA2Ivy7CGPu8DU,17288
|
|
78
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=8QlBSWOtgmhTpFbMMgGoXtMoTMETSnpA37z2LBgRzNQ,17178
|
|
81
81
|
simo/core/__pycache__/signal_receivers.cpython-38.pyc,sha256=UcKT8RK_14CI-JEWfplnIxskmWec_w5-gqKUXITLDA4,4323
|
|
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=yt2G8HPIkx1Djz4oVBfhUW9WOR9GOdxLhHFXViQJc6A,9139
|
|
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
|
|
@@ -10149,12 +10149,13 @@ simo/core/utils/config_values.py,sha256=4HCQmv5wQdupd16WOp80oJSyU7EDccjUO6blX--d
|
|
|
10149
10149
|
simo/core/utils/easing.py,sha256=N2NwD0CjLh82RGaYJKjyt-VVpVeS9z0mba8fqr8A1t0,1499
|
|
10150
10150
|
simo/core/utils/form_fields.py,sha256=UOzYdPd71qgCw1H3qH01u85YjrOlETPJAHOJrZKhyD0,627
|
|
10151
10151
|
simo/core/utils/form_widgets.py,sha256=Zxn9jJqPle9Q_BKNJnyTDn7MosYwNp1TFu5LoKs0bfc,408
|
|
10152
|
-
simo/core/utils/formsets.py,sha256=
|
|
10152
|
+
simo/core/utils/formsets.py,sha256=ZpExLsnDihnrlsPfYQrwy5qx54IowEmL8hnlO7KlyqE,6924
|
|
10153
10153
|
simo/core/utils/helpers.py,sha256=TOWy3slspaEYEhe9zDcb0RgzHUYslF6LZDlrWPGSqUI,3791
|
|
10154
10154
|
simo/core/utils/json.py,sha256=x3kMiiK30vuyWSYfghLVsDKo0N2JlCxZ5n8cwel85Vk,464
|
|
10155
10155
|
simo/core/utils/logs.py,sha256=Zn9JQxqCH9Odx2J1BWT84nFCfkJ4Z4p5X8psdll7hNc,2366
|
|
10156
10156
|
simo/core/utils/mixins.py,sha256=X6kUPKAi_F-uw7tgm8LEaYalBXpvDA-yrLNFCGr2rks,259
|
|
10157
10157
|
simo/core/utils/model_helpers.py,sha256=3IzJeOvBoYdUJVXCJkY20npOZXPjNPAiEFvuT0OPhwA,884
|
|
10158
|
+
simo/core/utils/operations.py,sha256=W234NEetDnMWP7_tvxJq5CWo_rT6Xhe4Cw7n9-VOpyU,176
|
|
10158
10159
|
simo/core/utils/relay.py,sha256=i1xy_nPTgY5Xn0l2W4lNI3xeVUpDQTUUfV3M8h2DeBg,457
|
|
10159
10160
|
simo/core/utils/serialization.py,sha256=v0HLQ98r3zOlsf6dv6S4WxOEt6BzmFz2eTXa_iuKjSM,2057
|
|
10160
10161
|
simo/core/utils/type_constants.py,sha256=xR2HXZOw9GZhC47iO1Py5B8mpaQMPbzvqX5nHWakhsY,4116
|
|
@@ -10166,12 +10167,13 @@ simo/core/utils/__pycache__/config_values.cpython-38.pyc,sha256=fqTVDhkjaWFv14Pr
|
|
|
10166
10167
|
simo/core/utils/__pycache__/easing.cpython-38.pyc,sha256=LupxHv19OiBqL0VXmTbMCtAOpgvBpq_b0XwLU8El-Jk,2137
|
|
10167
10168
|
simo/core/utils/__pycache__/form_fields.cpython-38.pyc,sha256=nBk6k9aj6BpWwdkpceIXdl5NU0fB6NPFhSBPaA-VtPs,1252
|
|
10168
10169
|
simo/core/utils/__pycache__/form_widgets.cpython-38.pyc,sha256=MYAYEq0I4P0WErG9FamTJYWue7-cPartAWbFAiSSg5w,908
|
|
10169
|
-
simo/core/utils/__pycache__/formsets.cpython-38.pyc,sha256=
|
|
10170
|
+
simo/core/utils/__pycache__/formsets.cpython-38.pyc,sha256=vwlFLdQ2bpZgXNUpekhtapwfouNPCIRo-SMrgOdAIMA,4813
|
|
10170
10171
|
simo/core/utils/__pycache__/helpers.cpython-38.pyc,sha256=jTGaN7kSJRwouP0EuYSaiJeMylo_RzJwSm-DKRwceHA,4291
|
|
10171
10172
|
simo/core/utils/__pycache__/json.cpython-38.pyc,sha256=akSSiSUOnza4N15GAH399gTz-X8x-5gijxZdjZoPz5Q,504
|
|
10172
10173
|
simo/core/utils/__pycache__/logs.cpython-38.pyc,sha256=BVVeQoOhfRHm3SHnCoE1d5G84kTpJZFmr_btc3jDYTU,2156
|
|
10173
10174
|
simo/core/utils/__pycache__/mixins.cpython-38.pyc,sha256=8Js2T7jVQ7hugRUIRu3rdxW86dJW4KeUUWqKqSkIGb0,615
|
|
10174
10175
|
simo/core/utils/__pycache__/model_helpers.cpython-38.pyc,sha256=QzO0rh1NuQePHDCSLmUCRrAZEnV4o8jh9CF_jp7IoUo,1351
|
|
10176
|
+
simo/core/utils/__pycache__/operations.cpython-38.pyc,sha256=1BUP7gbpBgX7mwWLpkmRH0eXgisbjn-6TIa8VDPj6v8,381
|
|
10175
10177
|
simo/core/utils/__pycache__/relay.cpython-38.pyc,sha256=gs4iN9TWBo_JIW07emIggIcv6gHKuOY_4jfmAFhuL3k,697
|
|
10176
10178
|
simo/core/utils/__pycache__/serialization.cpython-38.pyc,sha256=9nTbzozDi8Avl6krHvAo67CLdiTrYW0ij3hQtucHty0,1338
|
|
10177
10179
|
simo/core/utils/__pycache__/type_constants.cpython-38.pyc,sha256=bEMvzkBxzc6MKq6gn9A6wszXzMjLTbX-V-IK4NMht8E,3363
|
|
@@ -10183,7 +10185,7 @@ simo/fleet/auto_urls.py,sha256=X04oKJWA48wFW5iXg3PPROY2KDdHn_a99orQSE28QC4,518
|
|
|
10183
10185
|
simo/fleet/base_types.py,sha256=wL9RVkHr0gA7HI1wZq0pruGEIgvQqpfnCL4cC3ywsvw,102
|
|
10184
10186
|
simo/fleet/ble.py,sha256=eHA_9ABjbmH1vUVCv9hiPXQL2GZZSEVwfO0xyI1S0nI,1081
|
|
10185
10187
|
simo/fleet/controllers.py,sha256=WCqOA5Qrn9RavdfcB8X06WwaTE-9TGUprTQHZ8V8-nA,23172
|
|
10186
|
-
simo/fleet/forms.py,sha256=
|
|
10188
|
+
simo/fleet/forms.py,sha256=VVjFlWfU2jSfRp8CW9ntEd21Mr272LacRokzMon-YdU,48870
|
|
10187
10189
|
simo/fleet/gateways.py,sha256=KV5i5fxXIrlK-k6zyEkk83x11GJt-ELQ0npb4Ac83cM,3693
|
|
10188
10190
|
simo/fleet/managers.py,sha256=XOpDOA9L-f_550TNSyXnJbun2EmtGz1TenVTMlUSb8E,807
|
|
10189
10191
|
simo/fleet/models.py,sha256=bD5AebGFCAYGXPYhTA2nK1X9KpMG4WK4zFk9OzBDoHI,15301
|
|
@@ -10199,11 +10201,11 @@ simo/fleet/__pycache__/api.cpython-38.pyc,sha256=rL9fb7cCQatyFvXyKmlNOKmxVo8vHYe
|
|
|
10199
10201
|
simo/fleet/__pycache__/auto_urls.cpython-38.pyc,sha256=SqyTuaz_kEBvx-bL46SclsZEEP5RFh6U6TGKyXDdiOE,565
|
|
10200
10202
|
simo/fleet/__pycache__/base_types.cpython-38.pyc,sha256=deyPwjpT6xZiFxBGFnj5b7R-lbdOTh2krgpJhrcGVhc,274
|
|
10201
10203
|
simo/fleet/__pycache__/ble.cpython-38.pyc,sha256=Nrof9w7cm4OlpFWHeVnmvvanh2_oF9oQ3TknJiV93-0,1267
|
|
10202
|
-
simo/fleet/__pycache__/controllers.cpython-38.pyc,sha256=
|
|
10203
|
-
simo/fleet/__pycache__/forms.cpython-38.pyc,sha256=
|
|
10204
|
+
simo/fleet/__pycache__/controllers.cpython-38.pyc,sha256=TN3yvfZJgS7FwzgP4S1aDoaOqxbKj2oXfXOxqbkIXJU,19856
|
|
10205
|
+
simo/fleet/__pycache__/forms.cpython-38.pyc,sha256=0js770XdKp9PePLN-az0TLnpbfWE6u2Lwr8_sbfqOow,33943
|
|
10204
10206
|
simo/fleet/__pycache__/gateways.cpython-38.pyc,sha256=YAcgTOqJbtjGI03lvEcU6keFfrwAHkObVmErYzfRvjk,3569
|
|
10205
10207
|
simo/fleet/__pycache__/managers.cpython-38.pyc,sha256=8uz-xpUiqbGDgXIZ_XRZtFb-Tju6NGxflGg-Ee4Yo6k,1310
|
|
10206
|
-
simo/fleet/__pycache__/models.cpython-38.pyc,sha256=
|
|
10208
|
+
simo/fleet/__pycache__/models.cpython-38.pyc,sha256=LjcLsSytCQd17xhH-5RrzvnZ6JYI1ilvNdCY2iUCsGc,12935
|
|
10207
10209
|
simo/fleet/__pycache__/routing.cpython-38.pyc,sha256=aPrCmxFKVyB8R8ZbJDwdPdFfvT7CvobovvZeq_mqRgY,314
|
|
10208
10210
|
simo/fleet/__pycache__/serializers.cpython-38.pyc,sha256=9ljhwoHkolcVrJwOVbYCbGPAUKgALRwor_M3W_K0adE,3173
|
|
10209
10211
|
simo/fleet/__pycache__/socket_consumers.cpython-38.pyc,sha256=0-WhvzVsVJ5A_AgoKnWKOJjoJioLDNsYX4C6bGJANwQ,13551
|
|
@@ -10285,18 +10287,18 @@ simo/fleet/migrations/__pycache__/__init__.cpython-38.pyc,sha256=5k1KW0jeSDzw6Rn
|
|
|
10285
10287
|
simo/generic/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10286
10288
|
simo/generic/app_widgets.py,sha256=E_pnpA1hxMIhenRCrHoQ5cik06jm2BAHCkl_eo-OudU,1264
|
|
10287
10289
|
simo/generic/base_types.py,sha256=djymox_boXTHX1BTTCLXrCH7ED-uAsV_idhaDOc3OLI,409
|
|
10288
|
-
simo/generic/controllers.py,sha256=
|
|
10289
|
-
simo/generic/forms.py,sha256=
|
|
10290
|
-
simo/generic/gateways.py,sha256=
|
|
10290
|
+
simo/generic/controllers.py,sha256=qbcKSrKLvi4DCN4Tt1GpHMScVSGz0snQRRuWi4_-r_s,55823
|
|
10291
|
+
simo/generic/forms.py,sha256=FVhQ-buK6vUp7x7PADWo31pPVtHWgBMgshnyH-CN8wI,29239
|
|
10292
|
+
simo/generic/gateways.py,sha256=dZQPzO23UbW9q4dEB9fqgt9Meg8mX94euXnRvfwxusY,18004
|
|
10291
10293
|
simo/generic/models.py,sha256=92TACMhJHadAg0TT9GnARO_R3_Sl6i-GGjhG_x7YdFI,7391
|
|
10292
10294
|
simo/generic/routing.py,sha256=elQVZmgnPiieEuti4sJ7zITk1hlRxpgbotcutJJgC60,228
|
|
10293
10295
|
simo/generic/socket_consumers.py,sha256=NfTQGYtVAc864IoogZRxf_0xpDPM0eMCWn0SlKA5P7Y,1751
|
|
10294
10296
|
simo/generic/__pycache__/__init__.cpython-38.pyc,sha256=mLu54WS9KIl-pHwVCBKpsDFIlOqml--JsOVzAUHg6cU,161
|
|
10295
10297
|
simo/generic/__pycache__/app_widgets.cpython-38.pyc,sha256=0IoKRG9n1tkNRRkrqAeOQwWBPd_33u98JBcVtMVVCio,2374
|
|
10296
10298
|
simo/generic/__pycache__/base_types.cpython-38.pyc,sha256=ptw6axyAqemZA35oa6vzr7EihzvbhW9w7Y-G6kfDedU,555
|
|
10297
|
-
simo/generic/__pycache__/controllers.cpython-38.pyc,sha256=
|
|
10298
|
-
simo/generic/__pycache__/forms.cpython-38.pyc,sha256=
|
|
10299
|
-
simo/generic/__pycache__/gateways.cpython-38.pyc,sha256=
|
|
10299
|
+
simo/generic/__pycache__/controllers.cpython-38.pyc,sha256=6WhWx_QaWrYV_WqU5coEp9VFR11DxJVjPGTrhcUoYQ4,35583
|
|
10300
|
+
simo/generic/__pycache__/forms.cpython-38.pyc,sha256=LsYmGecSEdOnWnskBoTktLEUfYYKWMtd40lIGHbS-ZY,21207
|
|
10301
|
+
simo/generic/__pycache__/gateways.cpython-38.pyc,sha256=B35GiB4wBRzvd91ugL89Z3vYADiLiERP0T-21CnLqKc,13324
|
|
10300
10302
|
simo/generic/__pycache__/models.cpython-38.pyc,sha256=PzlZsM1jxo3FVb7QDm3bny8UFwTsGrMQe4mj4tJ06eQ,5675
|
|
10301
10303
|
simo/generic/__pycache__/routing.cpython-38.pyc,sha256=xtxTUTBTdivzFyA5Wh7k-hUj1WDO_FiRq6HYXdbr9Ks,382
|
|
10302
10304
|
simo/generic/__pycache__/socket_consumers.cpython-38.pyc,sha256=piFHces0J9QuXu_CNBCQCYjoZEeoaxyVjLfJ9KaR8C8,1898
|
|
@@ -10372,14 +10374,14 @@ simo/notifications/migrations/__pycache__/0002_notification_instance.cpython-38.
|
|
|
10372
10374
|
simo/notifications/migrations/__pycache__/__init__.cpython-38.pyc,sha256=YMBRHVon2nWDtIUbghckjnC12sIg_ykPWhV5aM0tto4,178
|
|
10373
10375
|
simo/users/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10374
10376
|
simo/users/admin.py,sha256=6RKGnwcrmewJFPzpqnxYn8rxjHO4tJPVFJvA3eMum2s,6746
|
|
10375
|
-
simo/users/api.py,sha256=
|
|
10377
|
+
simo/users/api.py,sha256=vf11fV8ZCao3Q2TdUO8mDTF3n0N3JYad7CUhz7_ZrZQ,9516
|
|
10376
10378
|
simo/users/auth_backends.py,sha256=I5pnaTa20-Lxfw_dFG8471xDITb0_fQl1PVhJalp5vU,3992
|
|
10377
10379
|
simo/users/auto_urls.py,sha256=lcJvteBsbHQMJieZpDz-63tDYejLApqsW3CUnDakd7k,272
|
|
10378
10380
|
simo/users/dynamic_settings.py,sha256=sEIsi4yJw3kH46Jq_aOkSuK7QTfQACGUE-lkyBogCaM,570
|
|
10379
10381
|
simo/users/middleware.py,sha256=GMCrnWSc_2qCleyQIkfQGdL-pU-UTEcSg1wPvIKZ9uk,1210
|
|
10380
10382
|
simo/users/models.py,sha256=I_iAa-6CLORshmGszSl021zl97oaXNIEOZYMSq5Wn5M,18902
|
|
10381
10383
|
simo/users/permissions.py,sha256=IwtYS8yQdupWbYKR9VimSRDV3qCJ2jXP57Lyjpb2EQM,242
|
|
10382
|
-
simo/users/serializers.py,sha256=
|
|
10384
|
+
simo/users/serializers.py,sha256=DwbFGi4WeTYXOSnfrBfd5rC5OGtevYurn27EaTVa1EU,2553
|
|
10383
10385
|
simo/users/sso_urls.py,sha256=gQOaPvGMYFD0NCVSwyoWO-mTEHe5j9sbzV_RK7kdvp0,251
|
|
10384
10386
|
simo/users/sso_views.py,sha256=-XI67TvQ7SN3goU4OuAHyn84u_1vtusvpn7Pu0K97zo,4648
|
|
10385
10387
|
simo/users/tasks.py,sha256=v9J7t4diB0VnqUDVZAQ8H-rlr4ZR14bgEUuEGpODyOI,854
|
|
@@ -10387,14 +10389,14 @@ simo/users/utils.py,sha256=7gU_TDnAOsDYqJM0CFo8efPah2bTXfGpXxRqzD5RiSs,1270
|
|
|
10387
10389
|
simo/users/views.py,sha256=dOQVvmlHG7ihWKJLFUBcqKOA0UDctlMKR0pTc36JZqg,3487
|
|
10388
10390
|
simo/users/__pycache__/__init__.cpython-38.pyc,sha256=9otuYxq331c4lGy0DR8pigaPpzq0lQ4nrNLhlYiFAF0,159
|
|
10389
10391
|
simo/users/__pycache__/admin.cpython-38.pyc,sha256=53Do-W6tvaOUPJ1BDx0abBbtvmQxAgdI2ShlqmYfUvI,7500
|
|
10390
|
-
simo/users/__pycache__/api.cpython-38.pyc,sha256=
|
|
10392
|
+
simo/users/__pycache__/api.cpython-38.pyc,sha256=GcGFVxv0GUcH-TVdvj3v3hty1snKJw3O3-f4PM8DIyM,8305
|
|
10391
10393
|
simo/users/__pycache__/auth_backends.cpython-38.pyc,sha256=MuOieBIXt6lrDx83-UQtdDyI_U8kE3pU9XR4yFLKBnE,3007
|
|
10392
10394
|
simo/users/__pycache__/auto_urls.cpython-38.pyc,sha256=K-3sz2h-cEitoflSmZk1t0eUg5mQMMGLNZFREVwG7_o,430
|
|
10393
10395
|
simo/users/__pycache__/dynamic_settings.cpython-38.pyc,sha256=6F8JBjZkHykySnmZjNEzjS0ijbmPdcp9yUAZ5kqq_Fo,864
|
|
10394
10396
|
simo/users/__pycache__/middleware.cpython-38.pyc,sha256=Tj4nVEAvxEW3xA63fBRiJWRJpz_M848ZOqbHioc_IPE,1149
|
|
10395
10397
|
simo/users/__pycache__/models.cpython-38.pyc,sha256=D5N0lYn3U9jt0M8-Xiz94DbiPuJefZQ9AUFJqKrYmsg,17562
|
|
10396
10398
|
simo/users/__pycache__/permissions.cpython-38.pyc,sha256=ez5NxoL_JUeeH6GsKhvFreuA3FCBgGf9floSypdXUtM,633
|
|
10397
|
-
simo/users/__pycache__/serializers.cpython-38.pyc,sha256=
|
|
10399
|
+
simo/users/__pycache__/serializers.cpython-38.pyc,sha256=tZzdmCdSnqekAgRl0kyq-msm7QfUA0J_IipfrysAMRM,3477
|
|
10398
10400
|
simo/users/__pycache__/sso_urls.cpython-38.pyc,sha256=uAwDozpOmrhUald-8tOHANILXkH7-TI8fNYXOtPkSY8,402
|
|
10399
10401
|
simo/users/__pycache__/sso_views.cpython-38.pyc,sha256=sHEoxLOac3U3Epmhm197huFnW_J3gGCDZSji57itijU,3969
|
|
10400
10402
|
simo/users/__pycache__/tasks.cpython-38.pyc,sha256=xq-XaJ5gzkpVVZRWe0bvGdA31Eh_WS2rKSY62p4eY5E,1111
|
|
@@ -10466,8 +10468,8 @@ simo/users/templates/invitations/expired_msg.html,sha256=47DEQpj8HBSa-_TImW-5JCe
|
|
|
10466
10468
|
simo/users/templates/invitations/expired_suggestion.html,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10467
10469
|
simo/users/templates/invitations/taken_msg.html,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10468
10470
|
simo/users/templates/invitations/taken_suggestion.html,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10469
|
-
simo-2.0.
|
|
10470
|
-
simo-2.0.
|
|
10471
|
-
simo-2.0.
|
|
10472
|
-
simo-2.0.
|
|
10473
|
-
simo-2.0.
|
|
10471
|
+
simo-2.0.38.dist-info/LICENSE.md,sha256=M7wm1EmMGDtwPRdg7kW4d00h1uAXjKOT3HFScYQMeiE,34916
|
|
10472
|
+
simo-2.0.38.dist-info/METADATA,sha256=lSORUFdH9kvVvxyK_3k0swsm7DHyYLhjUmxM_WTAFF8,1730
|
|
10473
|
+
simo-2.0.38.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
10474
|
+
simo-2.0.38.dist-info/top_level.txt,sha256=GmS1hrAbpVqn9OWZh6UX82eIOdRLgYA82RG9fe8v4Rs,5
|
|
10475
|
+
simo-2.0.38.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|