simo 2.5.8__py3-none-any.whl → 2.5.9__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.

Binary file
Binary file
Binary file
simo/core/form_fields.py CHANGED
@@ -26,10 +26,11 @@ class LazyChoicesMixin:
26
26
  return obj
27
27
 
28
28
  def optgroups(self, name, value, attrs=None):
29
- for (val, display) in self.choices:
30
- if val == value[0]:
31
- self.choices = [(val, display)]
32
- break
29
+ if len(value) == 2:
30
+ for (val, display) in self.choices:
31
+ if val == value[0]:
32
+ self.choices = [(val, display)]
33
+ break
33
34
  return super().optgroups(name, value, attrs)
34
35
 
35
36
 
@@ -67,7 +68,7 @@ class Select2ListChoiceField(Select2ListMixin, forms.ChoiceField):
67
68
 
68
69
  class Select2MultipleMixin:
69
70
 
70
- def __init__(self, url, forward=None, *args, **kwargs):
71
+ def __init__(self, url=None, forward=None, *args, **kwargs):
71
72
  self.url = url
72
73
  self.forward = []
73
74
  if forward:
@@ -76,9 +77,9 @@ class Select2MultipleMixin:
76
77
  widget = Select2Multiple(
77
78
  url=url, forward=forward, attrs={'data-html': True}
78
79
  )
79
- widget.choices = kwargs.pop('choices', None)
80
+ widget.choices = kwargs.pop('choices', [])
80
81
 
81
- super().__init__(widget, *args, **kwargs)
82
+ super().__init__(widget=widget, *args, **kwargs)
82
83
 
83
84
 
84
85
  class Select2ModelMultipleChoiceField(
simo/core/forms.py CHANGED
@@ -13,6 +13,7 @@ from dal import autocomplete
13
13
  from .models import (
14
14
  Icon, Category, Gateway, Component
15
15
  )
16
+ from .form_fields import Select2ModelMultipleChoiceField
16
17
  from .widgets import SVGFileWidget, LogOutputWidget
17
18
  from .utils.formsets import FormsetField
18
19
  from .utils.validators import validate_slaves
@@ -459,19 +460,17 @@ class MultiSensorConfigForm(BaseComponentForm):
459
460
 
460
461
 
461
462
  class SwitchForm(BaseComponentForm):
462
- slaves = forms.ModelMultipleChoiceField(
463
- required=False,
463
+ slaves = Select2ModelMultipleChoiceField(
464
464
  queryset=Component.objects.filter(
465
465
  base_type__in=(
466
466
  'dimmer', 'switch', 'blinds', 'script'
467
467
  )
468
468
  ),
469
- widget=autocomplete.ModelSelect2Multiple(
470
- url='autocomplete-component', attrs={'data-html': True},
471
- forward=(forward.Const(
472
- ['dimmer', 'switch', 'blinds', 'script'], 'base_type'),
473
- )
474
- )
469
+ url='autocomplete-component',
470
+ forward=(forward.Const(
471
+ ['dimmer', 'switch', 'blinds', 'script'], 'base_type'),
472
+ ),
473
+ required=False
475
474
  )
476
475
 
477
476
  def __init__(self, *args, **kwargs):
@@ -596,29 +595,18 @@ class DimmerConfigForm(BaseComponentForm):
596
595
  initial=0.0, help_text="Minimum component value."
597
596
  )
598
597
  max = forms.FloatField(
599
- initial=1.0, help_text="Maximum component value."
598
+ initial=100.0, help_text="Maximum component value."
600
599
  )
601
600
  value_units = forms.CharField(required=False)
602
- inverse = forms.BooleanField(
603
- label=_("Inverse dimmer signal"), required=False
604
- )
605
- slaves = forms.ModelMultipleChoiceField(
606
- required=False,
601
+ slaves = Select2ModelMultipleChoiceField(
607
602
  queryset=Component.objects.filter(
608
603
  base_type__in='dimmer',
609
604
  ),
610
- widget=autocomplete.ModelSelect2Multiple(
611
- url='autocomplete-component', attrs={'data-html': True},
612
- forward=(forward.Const(['dimmer', ], 'base_type'),)
613
- )
605
+ url='autocomplete-component',
606
+ forward=(forward.Const(['dimmer', ], 'base_type'),),
607
+ required=False
614
608
  )
615
609
 
616
- def __init__(self, *args, **kwargs):
617
- super().__init__(*args, **kwargs)
618
- self.fields['value_units'].initial = self.controller.default_value_units
619
- if self.instance.pk:
620
- self.fields['slaves'].initial = self.instance.slaves.all()
621
-
622
610
  def clean_slaves(self):
623
611
  if not self.cleaned_data['slaves'] or not self.instance:
624
612
  return self.cleaned_data['slaves']
@@ -626,7 +614,7 @@ class DimmerConfigForm(BaseComponentForm):
626
614
 
627
615
  def save(self, commit=True):
628
616
  obj = super().save(commit=commit)
629
- if commit:
617
+ if commit and 'slaves' in self.cleaned_data:
630
618
  obj.slaves.set(self.cleaned_data['slaves'])
631
619
  return obj
632
620
 
simo/core/models.py CHANGED
@@ -78,7 +78,8 @@ class Instance(DirtyFieldsMixin, models.Model, SimoAdminMixin):
78
78
  )
79
79
  location = PlainLocationField(null=True, blank=True, zoom=7)
80
80
  timezone = models.CharField(
81
- max_length=50, db_index=True, choices=ALL_TIMEZONES_CHOICES
81
+ max_length=50, db_index=True, choices=ALL_TIMEZONES_CHOICES,
82
+ default='UTC'
82
83
  )
83
84
  units_of_measure = models.CharField(
84
85
  max_length=100, default='metric',
simo/core/serializers.py CHANGED
@@ -13,8 +13,8 @@ from rest_framework.relations import Hyperlink, PKOnlyObject
13
13
  from actstream.models import Action
14
14
  from simo.core.forms import HiddenField, FormsetField
15
15
  from simo.core.form_fields import (
16
- Select2ListChoiceField, Select2ListChoiceField,
17
- Select2ModelChoiceField, Select2ListMultipleChoiceField
16
+ Select2ListChoiceField, Select2ModelChoiceField,
17
+ Select2ListMultipleChoiceField, Select2ModelMultipleChoiceField
18
18
  )
19
19
  from simo.core.models import Component
20
20
  from rest_framework.relations import PrimaryKeyRelatedField, ManyRelatedField
@@ -273,6 +273,7 @@ class ComponentSerializer(FormSerializer):
273
273
  Select2ModelChoiceField: ComponentPrimaryKeyRelatedField,
274
274
  forms.ModelMultipleChoiceField: ComponentManyToManyRelatedField,
275
275
  Select2ListMultipleChoiceField: ComponentManyToManyRelatedField,
276
+ Select2ModelMultipleChoiceField: ComponentManyToManyRelatedField,
276
277
  FormsetField: ComponentFormsetField,
277
278
  }
278
279
 
simo/core/tasks.py CHANGED
@@ -403,7 +403,6 @@ def restart_postgresql():
403
403
 
404
404
  @celery_app.task
405
405
  def low_battery_notifications():
406
- from simo.users.models import User
407
406
  from simo.notifications.utils import notify_users
408
407
  for instance in Instance.objects.filter(is_active=True):
409
408
  timezone.activate(instance.timezone)
Binary file
simo/fleet/forms.py CHANGED
@@ -8,7 +8,7 @@ from dal import autocomplete
8
8
  from dal import forward
9
9
  from simo.core.models import Component
10
10
  from simo.core.forms import (
11
- BaseComponentForm, ValueLimitForm, NumericSensorForm, SwitchForm
11
+ BaseComponentForm, ValueLimitForm, NumericSensorForm
12
12
  )
13
13
  from simo.core.utils.formsets import FormsetField
14
14
  from simo.core.widgets import LogOutputWidget
@@ -16,7 +16,10 @@ from simo.core.utils.easing import EASING_CHOICES
16
16
  from simo.core.utils.validators import validate_slaves
17
17
  from simo.core.utils.admin import AdminFormActionForm
18
18
  from simo.core.events import GatewayObjectCommand
19
- from simo.core.form_fields import Select2ModelChoiceField, Select2ListChoiceField
19
+ from simo.core.form_fields import (
20
+ Select2ModelChoiceField, Select2ListChoiceField,
21
+ Select2ModelMultipleChoiceField
22
+ )
20
23
  from .models import Colonel, ColonelPin, Interface
21
24
  from .utils import INTERFACES_PINS_MAP, get_all_control_input_choices
22
25
 
@@ -654,19 +657,16 @@ class ColonelSwitchConfigForm(ColonelComponentForm):
654
657
  "given amount of seconds after every turn on event."
655
658
  )
656
659
  inverse = forms.BooleanField(required=False)
657
- slaves = forms.ModelMultipleChoiceField(
658
- required=False,
660
+ slaves = Select2ModelMultipleChoiceField(
659
661
  queryset=Component.objects.filter(
660
662
  base_type__in=(
661
663
  'dimmer', 'switch', 'blinds', 'script'
662
664
  )
663
665
  ),
664
- widget=autocomplete.ModelSelect2Multiple(
665
- url='autocomplete-component', attrs={'data-html': True},
666
- forward=(forward.Const(
667
- ['dimmer', 'switch', 'blinds', 'script'], 'base_type'),
668
- )
669
- )
666
+ url='autocomplete-component',
667
+ forward=[
668
+ forward.Const(['dimmer', 'switch', 'blinds', 'script'], 'base_type')
669
+ ]
670
670
  )
671
671
 
672
672
  controls = FormsetField(
@@ -775,15 +775,13 @@ class ColonelPWMOutputConfigForm(ColonelComponentForm):
775
775
  help_text="ON value when used with toggle switch"
776
776
  )
777
777
 
778
- slaves = forms.ModelMultipleChoiceField(
779
- required=False,
778
+ slaves = Select2ModelMultipleChoiceField(
780
779
  queryset=Component.objects.filter(
781
780
  base_type__in=('dimmer', ),
782
781
  ),
783
- widget=autocomplete.ModelSelect2Multiple(
784
- url='autocomplete-component', attrs={'data-html': True},
785
- forward=(forward.Const(['dimmer', ], 'base_type'),)
786
- )
782
+ url='autocomplete-component',
783
+ forward=(forward.Const(['dimmer', ], 'base_type'),),
784
+ required=False
787
785
  )
788
786
  controls = FormsetField(
789
787
  formset_factory(
@@ -1412,16 +1410,13 @@ class BurglarSmokeDetectorConfigForm(ColonelComponentForm):
1412
1410
 
1413
1411
  class TTLockConfigForm(ColonelComponentForm):
1414
1412
 
1415
- door_sensor = forms.ModelChoiceField(
1416
- Component.objects.filter(base_type='binary-sensor'),
1417
- required=False,
1418
- help_text="Quickens up lock status reporting on open/close if provided.",
1419
- widget=autocomplete.ModelSelect2(
1420
- url='autocomplete-component', attrs={'data-html': True},
1421
- forward=(
1422
- forward.Const(['binary-sensor'], 'base_type'),
1423
- )
1424
- )
1413
+ door_sensor = Select2ModelChoiceField(
1414
+ queryset=Component.objects.filter(base_type='binary-sensor'),
1415
+ url='autocomplete-component',
1416
+ forward=(
1417
+ forward.Const(['binary-sensor'], 'base_type'),
1418
+ ), required=False,
1419
+ help_text="Quickens up lock status reporting on open/close if provided."
1425
1420
  )
1426
1421
 
1427
1422
  def clean(self):
@@ -1591,18 +1586,16 @@ class DaliGearGroupForm(DALIDeviceConfigForm, BaseComponentForm):
1591
1586
  help_text="If provided, group will be turned off after "
1592
1587
  "given amount of seconds after last turn on event."
1593
1588
  )
1594
- members = forms.ModelMultipleChoiceField(
1595
- Component.objects.filter(
1589
+ members = Select2ModelMultipleChoiceField(
1590
+ queryset=Component.objects.filter(
1596
1591
  controller_uid='simo.fleet.controllers.DALILamp',
1597
1592
  ),
1598
1593
  label="Members", required=True,
1599
- widget=autocomplete.ModelSelect2Multiple(
1600
- url='autocomplete-component', attrs={'data-html': True},
1601
- forward=(
1602
- forward.Const(
1603
- ['simo.fleet.controllers.DALILamp', ], 'controller_uid'
1604
- ),
1605
- )
1594
+ url='autocomplete-component',
1595
+ forward=(
1596
+ forward.Const(
1597
+ ['simo.fleet.controllers.DALILamp', ], 'controller_uid'
1598
+ ),
1606
1599
  )
1607
1600
  )
1608
1601
  on_value = forms.FloatField(
@@ -6,6 +6,7 @@ import json
6
6
  import requests
7
7
  import traceback
8
8
  import sys
9
+ import random
9
10
  from bs4 import BeautifulSoup
10
11
  from django.core.exceptions import ValidationError
11
12
  from django.utils import timezone
@@ -43,7 +44,7 @@ from .app_widgets import (
43
44
  from .forms import (
44
45
  ScriptConfigForm, PresenceLightingConfigForm,
45
46
  ThermostatConfigForm, AlarmGroupConfigForm,
46
- IPCameraConfigForm, WeatherForecastForm, GateConfigForm,
47
+ IPCameraConfigForm, WeatherForecastForm,
47
48
  WateringConfigForm, StateSelectForm,
48
49
  AlarmClockConfigForm
49
50
  )
@@ -155,11 +156,17 @@ class PresenceLighting(Script):
155
156
  light_org_values = {}
156
157
  is_on = False
157
158
  turn_off_task = None
159
+ last_presence = 0
158
160
 
159
161
  def _run(self):
160
162
  while True:
161
163
  self._on_sensor()
162
- time.sleep(10)
164
+ hold_time = self.component.config.get('hold_time', 0) * 10
165
+ if self.last_presence and hold_time and (
166
+ time.time() - hold_time > self.last_presence
167
+ ):
168
+ self._turn_it_off()
169
+ time.sleep(random.randint(5, 15))
163
170
 
164
171
  def _on_sensor(self, sensor=None):
165
172
 
@@ -180,6 +187,9 @@ class PresenceLighting(Script):
180
187
  else:
181
188
  must_on = all(presence_values)
182
189
 
190
+ if sensor and must_on:
191
+ print("Presence detected!")
192
+
183
193
  additional_conditions_met = True
184
194
  for condition in self.component.config.get('conditions', []):
185
195
  if not additional_conditions_met:
@@ -198,17 +208,13 @@ class PresenceLighting(Script):
198
208
  continue
199
209
 
200
210
  if not op(comp.value, condition['value']):
211
+ if sensor and must_on:
212
+ print(f"Condition not met: [{comp} value:{comp.value} {condition['op']} {condition['value']}]")
201
213
  additional_conditions_met = False
202
214
 
203
- if must_on and not additional_conditions_met:
204
- print("Presence detected, but additional conditions not met!")
205
-
206
215
  if must_on and additional_conditions_met and not self.is_on:
207
216
  print("Turn the lights ON!")
208
217
  self.is_on = True
209
- if self.turn_off_task:
210
- self.turn_off_task.cancel()
211
- self.turn_off_task = None
212
218
  self.light_org_values = {}
213
219
  for id in self.component.config['lights']:
214
220
  comp = Component.objects.filter(id=id).first()
@@ -224,16 +230,13 @@ class PresenceLighting(Script):
224
230
  if not any(presence_values):
225
231
  if not self.component.config.get('hold_time', 0):
226
232
  return self._turn_it_off()
227
- if not self.turn_off_task:
228
- self.turn_off_task = threading.Timer(
229
- self.component.config['hold_time'] * 10,
230
- self._turn_it_off
231
- )
233
+ if not self.last_presence:
234
+ self.last_presence = time.time()
232
235
 
233
236
  def _turn_it_off(self):
234
237
  print("Turn the lights OFF!")
235
238
  self.is_on = False
236
- self.turn_off_task = None
239
+ self.last_presence = 0
237
240
  for id in self.component.config['lights']:
238
241
  comp = Component.objects.filter(id=id).first()
239
242
  if not comp or not comp.controller:
simo/generic/forms.py CHANGED
@@ -16,7 +16,10 @@ from simo.core.utils.config_values import config_to_dict
16
16
  from simo.core.utils.formsets import FormsetField
17
17
  from simo.core.utils.helpers import get_random_string
18
18
  from simo.core.utils.form_fields import ListSelect2Widget
19
- from simo.conf import dynamic_settings
19
+ from simo.core.form_fields import (
20
+ Select2ModelChoiceField, Select2ListChoiceField,
21
+ Select2ModelMultipleChoiceField
22
+ )
20
23
 
21
24
 
22
25
  ACTION_METHODS = (
@@ -134,11 +137,9 @@ class ScriptConfigForm(BaseComponentForm):
134
137
 
135
138
 
136
139
  class ConditionForm(forms.Form):
137
- component = forms.ModelChoiceField(
138
- Component.objects.all(),
139
- widget=autocomplete.ModelSelect2(
140
- url='autocomplete-component', attrs={'data-html': True},
141
- ),
140
+ component = Select2ModelChoiceField(
141
+ queryset=Component.objects.all(),
142
+ url='autocomplete-component',
142
143
  )
143
144
  op = forms.ChoiceField(
144
145
  initial="==", choices=(
@@ -200,19 +201,18 @@ class ConditionForm(forms.Form):
200
201
 
201
202
 
202
203
  class PresenceLightingConfigForm(BaseComponentForm):
203
- lights = forms.ModelMultipleChoiceField(
204
- Component.objects.filter(
204
+ lights = Select2ModelMultipleChoiceField(
205
+ queryset=Component.objects.filter(
205
206
  base_type__in=('switch', 'dimmer', 'rgbw-light', 'rgb-light')
206
207
  ),
207
208
  required=True,
208
- widget=autocomplete.ModelSelect2Multiple(
209
- url='autocomplete-component', attrs={'data-html': True},
210
- forward=(
211
- forward.Const(['switch', 'dimmer', 'rgbw-light', 'rgb-light'],
212
- 'base_type'),
213
- )
209
+ url='autocomplete-component',
210
+ forward=(
211
+ forward.Const(['switch', 'dimmer', 'rgbw-light', 'rgb-light'],
212
+ 'base_type'),
214
213
  )
215
214
  )
215
+
216
216
  on_value = forms.IntegerField(
217
217
  min_value=0, initial=100,
218
218
  help_text="Value applicable for dimmers. "
@@ -223,13 +223,13 @@ class PresenceLightingConfigForm(BaseComponentForm):
223
223
  (0, "0"), (1, "Original value before turning the lights on.")
224
224
  )
225
225
  )
226
- presence_sensors = forms.ModelMultipleChoiceField(
227
- Component.objects.filter(base_type='binary-sensor'),
226
+ presence_sensors = Select2ModelMultipleChoiceField(
227
+ queryset=Component.objects.filter(
228
+ base_type__in=('binary-sensor', 'switch')
229
+ ),
228
230
  required=True,
229
- widget=autocomplete.ModelSelect2Multiple(
230
- url='autocomplete-component', attrs={'data-html': True},
231
- forward=(forward.Const(['binary-sensor'], 'base_type'),)
232
- )
231
+ url='autocomplete-component',
232
+ forward=(forward.Const(['binary-sensor', 'switch'], 'base_type'),)
233
233
  )
234
234
  act_on = forms.TypedChoiceField(
235
235
  coerce=int, initial=0, choices=(
@@ -263,6 +263,8 @@ class PresenceLightingConfigForm(BaseComponentForm):
263
263
  widget=forms.HiddenInput, required=False
264
264
  )
265
265
 
266
+ app_exclude_fields = ('alarm_category', 'code', 'log')
267
+
266
268
  def __init__(self, *args, **kwargs):
267
269
  super().__init__(*args, **kwargs)
268
270
  self.basic_fields.extend(
@@ -280,46 +282,53 @@ class PresenceLightingConfigForm(BaseComponentForm):
280
282
  )
281
283
  )
282
284
 
285
+ def save(self, commit=True):
286
+ obj = super().save(commit)
287
+ if commit:
288
+ obj.controller.stop()
289
+ if self.cleaned_data.get('keep_alive') \
290
+ or self.cleaned_data.get('autostart'):
291
+ time.sleep(2)
292
+ obj.controller.start()
293
+ return obj
294
+
283
295
 
284
296
  class ThermostatConfigForm(BaseComponentForm):
285
- temperature_sensor = forms.ModelChoiceField(
286
- Component.objects.filter(
297
+ temperature_sensor = Select2ModelChoiceField(
298
+ queryset=Component.objects.filter(
287
299
  base_type__in=(
288
300
  NumericSensor.base_type,
289
301
  MultiSensor.base_type
290
302
  )
291
303
  ),
292
- widget=autocomplete.ModelSelect2(
293
- url='autocomplete-component', attrs={'data-html': True},
294
- forward=(
295
- forward.Const([
296
- NumericSensor.base_type,
297
- MultiSensor.base_type
298
- ], 'base_type'),
299
- )
304
+ url='autocomplete-component',
305
+ forward=(
306
+ forward.Const([
307
+ NumericSensor.base_type,
308
+ MultiSensor.base_type
309
+ ], 'base_type'),
300
310
  )
301
311
  )
302
- heater = forms.ModelChoiceField(
303
- Component.objects.filter(base_type=Switch.base_type),
304
- required=False, widget=autocomplete.ModelSelect2(
305
- url='autocomplete-component', attrs={'data-html': True},
306
- forward=(
307
- forward.Const([
308
- Switch.base_type,
309
- ], 'base_type'),
310
- )
312
+ heater = Select2ModelChoiceField(
313
+ queryset=Component.objects.filter(base_type=Switch.base_type),
314
+ required=False,
315
+ url='autocomplete-component',
316
+ forward=(
317
+ forward.Const([
318
+ Switch.base_type,
319
+ ], 'base_type'),
311
320
  )
312
321
  )
313
- cooler = forms.ModelChoiceField(
314
- Component.objects.filter(base_type=Switch.base_type),
315
- required=False, widget=autocomplete.ModelSelect2(
316
- url='autocomplete-component', attrs={'data-html': True},
317
- forward=(
318
- forward.Const([
319
- Switch.base_type,
320
- ], 'base_type'),
321
- )
322
+ cooler = Select2ModelChoiceField(
323
+ queryset=Component.objects.filter(base_type=Switch.base_type),
324
+ required=False,
325
+ url='autocomplete-component',
326
+ forward=(
327
+ forward.Const([
328
+ Switch.base_type,
329
+ ], 'base_type'),
322
330
  )
331
+
323
332
  )
324
333
  mode = forms.ChoiceField(
325
334
  choices=(('heater', "Heater"), ('cooler', "Cooler"), ('auto', "Auto"),),
@@ -368,11 +377,9 @@ class ThermostatConfigForm(BaseComponentForm):
368
377
 
369
378
  class AlarmBreachEventForm(forms.Form):
370
379
  uid = HiddenField(required=False)
371
- component = forms.ModelChoiceField(
372
- Component.objects.all(),
373
- widget=autocomplete.ModelSelect2(
374
- url='autocomplete-component', attrs={'data-html': True},
375
- ),
380
+ component = Select2ModelChoiceField(
381
+ queryset=Component.objects.all(),
382
+ url='autocomplete-component',
376
383
  )
377
384
  breach_action = forms.ChoiceField(
378
385
  initial='turn_on', choices=ACTION_METHODS
@@ -413,16 +420,14 @@ class AlarmBreachEventForm(forms.Form):
413
420
 
414
421
  # TODO: create control widget for admin use.
415
422
  class AlarmGroupConfigForm(BaseComponentForm):
416
- components = forms.ModelMultipleChoiceField(
417
- Component.objects.filter(
423
+ components = Select2ModelMultipleChoiceField(
424
+ queryset=Component.objects.filter(
418
425
  Q(alarm_category__isnull=False) | Q(base_type='alarm-group')
419
426
  ),
420
427
  required=True,
421
- widget=autocomplete.ModelSelect2Multiple(
422
- url='autocomplete-component', attrs={'data-html': True},
423
- forward=(
424
- forward.Const(['security', 'fire', 'flood', 'other'], 'alarm_category'),
425
- )
428
+ url='autocomplete-component',
429
+ forward=(
430
+ forward.Const(['security', 'fire', 'flood', 'other'], 'alarm_category'),
426
431
  )
427
432
  )
428
433
  is_main = forms.BooleanField(
@@ -439,28 +444,24 @@ class AlarmGroupConfigForm(BaseComponentForm):
439
444
  ),
440
445
  help_text="Arm automatically as soon as everybody leaves.<br>"
441
446
  )
442
- arming_locks = forms.ModelMultipleChoiceField(
443
- Component.objects.filter(base_type='lock'),
447
+ arming_locks = Select2ModelMultipleChoiceField(
448
+ queryset=Component.objects.filter(base_type='lock'),
444
449
  label="Arming locks", required=False,
445
- widget=autocomplete.ModelSelect2Multiple(
446
- url='autocomplete-component', attrs={'data-html': True},
447
- forward=(
448
- forward.Const(['lock'], 'base_type'),
449
- )
450
+ url='autocomplete-component',
451
+ forward=(
452
+ forward.Const(['lock'], 'base_type'),
450
453
  ),
451
454
  help_text="Alarm group will get armed automatically whenever "
452
455
  "any of assigned locks changes it's state to locked. <br>"
453
456
  "If Arm on away is enabled and set to work with arming locks, "
454
457
  "arming will take effect only after everybody leaves."
455
458
  )
456
- disarming_locks = forms.ModelMultipleChoiceField(
457
- Component.objects.filter(base_type='lock'),
459
+ disarming_locks = Select2ModelMultipleChoiceField(
460
+ queryset=Component.objects.filter(base_type='lock'),
458
461
  label="Disarming locks", required=False,
459
- widget=autocomplete.ModelSelect2Multiple(
460
- url='autocomplete-component', attrs={'data-html': True},
461
- forward=(
462
- forward.Const(['lock'], 'base_type'),
463
- )
462
+ url='autocomplete-component',
463
+ forward=(
464
+ forward.Const(['lock'], 'base_type'),
464
465
  ),
465
466
  help_text="Alarm group will be disarmed automatically whenever "
466
467
  "any of assigned locks changes it's state to unlocked. "
@@ -584,58 +585,20 @@ class WeatherForecastForm(BaseComponentForm):
584
585
  return obj
585
586
 
586
587
 
587
- class GateConfigForm(BaseComponentForm):
588
- open_closed_sensor = forms.ModelChoiceField(
589
- Component.objects.filter(base_type=BinarySensor.base_type),
590
- label="Open/Closed sensor",
591
- widget=autocomplete.ModelSelect2(
592
- url='autocomplete-component', attrs={'data-html': True},
593
- forward=(
594
- forward.Const([BinarySensor.base_type], 'base_type'),
595
- )
596
- )
597
- )
598
- action_switch = forms.ModelChoiceField(
599
- Component.objects.filter(base_type=Switch.base_type),
600
- widget=autocomplete.ModelSelect2(
601
- url='autocomplete-component', attrs={'data-html': True},
602
- forward=(
603
- forward.Const([Switch.base_type], 'base_type'),
604
- )
605
- )
606
- )
607
- action_method = forms.ChoiceField(
608
- required=True, choices=(
609
- ('click', "Click"),
610
- ('toggle', "Toggle"),
611
- ),
612
- help_text="Action switch method to initiate move/stop event on "
613
- "your gate."
614
- )
615
- gate_open_duration = forms.FloatField(
616
- label='Gate open duration', min_value=0.01, max_value=360000,
617
- initial=30,
618
- help_text="Time in seconds it takes for your gate to go "
619
- "from fully closed to fully open."
620
- )
621
-
622
-
623
588
 
624
589
  class ContourForm(forms.Form):
625
590
  uid = forms.CharField(widget=forms.HiddenInput(), required=False)
626
591
  color = forms.CharField(widget=forms.HiddenInput(), required=False)
627
592
 
628
593
  name = forms.CharField()
629
- switch = forms.ModelChoiceField(
630
- Component.objects.filter(
594
+ switch = Select2ModelChoiceField(
595
+ queryset=Component.objects.filter(
631
596
  base_type__in=(Switch.base_type, Dimmer.base_type)
632
597
  ),
633
- widget=autocomplete.ModelSelect2(
634
- url='autocomplete-component', attrs={'data-html': True},
635
- forward=(
636
- forward.Const([Switch.base_type], 'base_type'),
637
- )
638
- ),
598
+ url='autocomplete-component',
599
+ forward=(
600
+ forward.Const([Switch.base_type], 'base_type'),
601
+ )
639
602
  )
640
603
  runtime = forms.IntegerField(
641
604
  min_value=0,
@@ -748,11 +711,9 @@ class AlarmClockEventForm(forms.Form):
748
711
  uid = HiddenField(required=False)
749
712
  enabled = forms.BooleanField(initial=True)
750
713
  name = forms.CharField(max_length=30)
751
- component = forms.ModelChoiceField(
752
- Component.objects.all(),
753
- widget=autocomplete.ModelSelect2(
754
- url='autocomplete-component', attrs={'data-html': True},
755
- ),
714
+ component = Select2ModelChoiceField(
715
+ queryset=Component.objects.all(),
716
+ url='autocomplete-component'
756
717
  )
757
718
  play_action = forms.ChoiceField(
758
719
  initial='turn_on', choices=ACTION_METHODS
Binary file
simo/users/api.py CHANGED
@@ -219,7 +219,7 @@ class UserDeviceReport(InstanceMixin, viewsets.GenericViewSet):
219
219
  ).exclude(id=user_device.id).update(is_primary=False)
220
220
  user_device.save()
221
221
 
222
- speed_kmh = 0
222
+ speed_kmh = request.data.get('speed', 0) * 3.6
223
223
  for iu in request.user.instance_roles.filter(is_active=True):
224
224
  if location:
225
225
  iu.at_home = haversine_distance(
@@ -228,20 +228,6 @@ class UserDeviceReport(InstanceMixin, viewsets.GenericViewSet):
228
228
  elif not relay:
229
229
  iu.at_home = True
230
230
 
231
- if user_device.last_seen_location and iu.last_seen_location \
232
- and iu.last_seen > timezone.now() - datetime.timedelta(seconds=30):
233
- if user_device.last_seen_location == iu.last_seen_location:
234
- speed_kmh = iu.last_seen_speed_kmh
235
- else:
236
- seconds_passed = (timezone.now() - user_device.last_seen).seconds
237
- if not seconds_passed:
238
- speed_kmh = 0
239
- else:
240
- moved_distance = haversine_distance(
241
- iu.last_seen_location, user_device.last_seen_location
242
- )
243
- speed_mps = moved_distance / seconds_passed
244
- speed_kmh = speed_mps * 3.6
245
231
  iu.last_seen = user_device.last_seen
246
232
  iu.last_seen_location = user_device.last_seen_location
247
233
  iu.last_seen_speed_kmh = speed_kmh
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: simo
3
- Version: 2.5.8
3
+ Version: 2.5.9
4
4
  Summary: Smart Home on Steroids!
5
5
  Author-email: Simanas Venčkauskas <simanas@simo.io>
6
6
  Project-URL: Homepage, https://simo.io
@@ -46,20 +46,20 @@ simo/core/controllers.py,sha256=JvXdwXD7iotA7fIjZmBVshKLQGSt9Na48FMAyHRDh84,3561
46
46
  simo/core/dynamic_settings.py,sha256=bUs58XEZOCIEhg1TigR3LmYggli13KMryBZ9pC7ugAQ,1872
47
47
  simo/core/events.py,sha256=fH6d5HcPdDqT3R7CZPdo69qTszJ23j3GJt3IFOso3WA,4757
48
48
  simo/core/filters.py,sha256=ghtOZcrwNAkIyF5_G9Sn73NkiI71mXv0NhwCk4IyMIM,411
49
- simo/core/form_fields.py,sha256=9tIjiEN3IE55GPyB4tOlfkd51JDne3-h8pKhpL3tLFE,2220
50
- simo/core/forms.py,sha256=nL_trDNs7RLic11zLSen4EobtywGx8igUMYe7Ojuv1k,21988
49
+ simo/core/form_fields.py,sha256=wowWocYgxkKBr0WYzpKn4UvH4ScnImus56Tg2G8OPBc,2274
50
+ simo/core/forms.py,sha256=O40apPH7a4qX4WdCc10A1aoAaGWOpKqmjB8d-OhEKCo,21523
51
51
  simo/core/gateways.py,sha256=m0eS3XjVe34Dge6xtoCq16kFWCKJcdQrT0JW0REqoq8,3715
52
52
  simo/core/loggers.py,sha256=EBdq23gTQScVfQVH-xeP90-wII2DQFDjoROAW6ggUP4,1645
53
53
  simo/core/managers.py,sha256=n-b3I4uXzfHKTeB1VMjSaMsDUxp8FegFJwnbV1IsWQ4,3019
54
54
  simo/core/middleware.py,sha256=hExD7Vmw7eitk0vAjOwKzkwrtuw8YxpflF92j_CA2YY,3193
55
- simo/core/models.py,sha256=dqwnGUc-BKefkWx4SfGDhiytP7vxAGFcKOlEXz2VhHA,22594
55
+ simo/core/models.py,sha256=Z6WLvU4K-LWIXghCBwTyZAOW5n2hCFNU-pteiPnpOAQ,22617
56
56
  simo/core/permissions.py,sha256=v0iJM4LOeYoEfMiw3OLPYio272G1aUEAg_z9Wd1q5m0,2993
57
57
  simo/core/routing.py,sha256=X1_IHxyA-_Q7hw1udDoviVP4_FSBDl8GYETTC2zWTbY,499
58
- simo/core/serializers.py,sha256=Pa2lhJ6VgNalbH4awbKdGJCYAPNsu5WQWfo6Tz6LbOQ,20782
58
+ simo/core/serializers.py,sha256=JoMsGoeeTfnoNSdLPMlx4ZbaFyhtw5Kh4KpbAIUovX8,20869
59
59
  simo/core/signal_receivers.py,sha256=qCpzEUv5Bl9--K8fe08GVDmE6EBOj292YBia1TYDdSE,9267
60
60
  simo/core/socket_consumers.py,sha256=trRZvBGTJ7xIbfdmVvn7zoiWp_qssSkMZykDrI5YQyE,9783
61
61
  simo/core/storage.py,sha256=_5igjaoWZAiExGWFEJMElxUw55DzJG1jqFty33xe8BE,342
62
- simo/core/tasks.py,sha256=6Srcsq1szv7pU74Qp-Y4GUoRjvNPJI81jKsSWxqVcb8,15671
62
+ simo/core/tasks.py,sha256=M86_0KAK95hpfxD-mWLYtWU-6L7rHVkytlINjEdtimI,15632
63
63
  simo/core/todos.py,sha256=eYVXfLGiapkxKK57XuviSNe3WsUYyIWZ0hgQJk7ThKo,665
64
64
  simo/core/types.py,sha256=WJEq48mIbFi_5Alt4wxWMGXxNxUTXqfQU5koH7wqHHI,1108
65
65
  simo/core/views.py,sha256=3SRZr00fyLQf8ja3U-9eekKt-ld5TvU1WQqUWprXfQ4,2390
@@ -79,20 +79,20 @@ simo/core/__pycache__/controllers.cpython-38.pyc,sha256=s7onEMtWmHjvTGvWXIbpvWMW
79
79
  simo/core/__pycache__/dynamic_settings.cpython-38.pyc,sha256=wGpnscX1DxFpRl54MQURhjz2aD3NJohSzw9JCFnzh2Y,2384
80
80
  simo/core/__pycache__/events.cpython-38.pyc,sha256=yip7WSyX4pUy2wJE820W4fD7iwoIWGhdHfloFb_N0R8,5257
81
81
  simo/core/__pycache__/filters.cpython-38.pyc,sha256=VIMADCBiYhziIyRmxAyUDJluZvuZmiC4bNYWTRsGSao,721
82
- simo/core/__pycache__/form_fields.cpython-38.pyc,sha256=u0voKXkA64xbH6LY_-jMBHQS4mOJZZeuB9WTvtv9JWE,3433
83
- simo/core/__pycache__/forms.cpython-38.pyc,sha256=O_mCWAQHAJhiqcKLUina7tIFbw5uXwnLOYlJoWxqUDQ,17918
82
+ simo/core/__pycache__/form_fields.cpython-38.pyc,sha256=phMdhDFRmaFDhJSnasAg8VKNP6PxkPjFqkLwEuWicIs,3465
83
+ simo/core/__pycache__/forms.cpython-38.pyc,sha256=vqU3No0S56V1q1RrY7y5otT-o0p-5qAiffvqECG2Gg8,17525
84
84
  simo/core/__pycache__/gateways.cpython-38.pyc,sha256=D1ooHL-iSpQrxnD8uAl4xWFJmm-QWZfbkLiLlFOMtdU,4553
85
85
  simo/core/__pycache__/loggers.cpython-38.pyc,sha256=Z-cdQnC6XlIonPV4Sl4E52tP4NMEdPAiHK0cFaIL7I8,1623
86
86
  simo/core/__pycache__/managers.cpython-38.pyc,sha256=6RTIxyjOgpQGtAqcUyE2vFPS09w1V5Wmd_vOV7rHRRI,3370
87
87
  simo/core/__pycache__/middleware.cpython-38.pyc,sha256=iOSTXSQl3sEsa-9kx_6w5zbEByRtfzJHT6XkUIYMGdE,2469
88
- simo/core/__pycache__/models.cpython-38.pyc,sha256=lNpWQXIobV40Pxnq3p5PO8vSE6vX-nFrKARsWlk-76E,18508
88
+ simo/core/__pycache__/models.cpython-38.pyc,sha256=Nw2Fb11EwBdMVTHCpzkY6x0hRNCqRo0RtLNlRxS1VZE,18534
89
89
  simo/core/__pycache__/permissions.cpython-38.pyc,sha256=fH4iyqd9DdzRLEu2b621-FeM-napR0M7hzBUTHo9Q3g,2972
90
90
  simo/core/__pycache__/routing.cpython-38.pyc,sha256=3T3FPJ8Cn99xZCGvMyg2xjl7al-Shm9CelbSpkJtNP8,599
91
- simo/core/__pycache__/serializers.cpython-38.pyc,sha256=qIHxrurPk555mHc9P8Udg9eGv-ODw1FTmBS_jXrPuws,19220
91
+ simo/core/__pycache__/serializers.cpython-38.pyc,sha256=S4eNvJkMZO98FQXgNGbeKBJihG5LXdnhl0tm2tjjXTE,19266
92
92
  simo/core/__pycache__/signal_receivers.cpython-38.pyc,sha256=lBVca6zNPVn3Ev98ekjPGzBR1MJk4xI19CyMcm4lf6A,7056
93
93
  simo/core/__pycache__/socket_consumers.cpython-38.pyc,sha256=KqbO1cOewodVPcy0-htVefyUjCuELKV0o7fOfYqfgPc,8490
94
94
  simo/core/__pycache__/storage.cpython-38.pyc,sha256=9R1Xu0FJDflfRXUPsqEgt0SpwiP7FGk7HaR8s8XRyI8,721
95
- simo/core/__pycache__/tasks.cpython-38.pyc,sha256=qQZ1wP3ksyh6c6kmqMtkYxGs-MTTDhaqQDlYfK7vQ7c,10003
95
+ simo/core/__pycache__/tasks.cpython-38.pyc,sha256=rhf1RimWBxylGo8nk0orAJ4SqTsLF0OKW2TThpREp54,10472
96
96
  simo/core/__pycache__/todos.cpython-38.pyc,sha256=lOqGZ58siHM3isoJV4r7sg8igrfE9fFd-jSfeBa0AQI,253
97
97
  simo/core/__pycache__/views.cpython-38.pyc,sha256=K_QM967bIJeU02DJu0Dm7j8RiFDKn_TLzX77YzNkA7c,2495
98
98
  simo/core/__pycache__/widgets.cpython-38.pyc,sha256=sR0ZeHCHrhnNDBJuRrxp3zUsfBp0xrtF0xrK2TkQv1o,3520
@@ -10222,7 +10222,7 @@ simo/fleet/auto_urls.py,sha256=UX66eR2ykMqFgfIllW-RTdjup5-FieCWl_BVm3CcXKg,702
10222
10222
  simo/fleet/base_types.py,sha256=wL9RVkHr0gA7HI1wZq0pruGEIgvQqpfnCL4cC3ywsvw,102
10223
10223
  simo/fleet/ble.py,sha256=eHA_9ABjbmH1vUVCv9hiPXQL2GZZSEVwfO0xyI1S0nI,1081
10224
10224
  simo/fleet/controllers.py,sha256=fjri1GtCnflkkDpNqhTwy6i9CK6RDEB0Q_BtADzcG8E,29156
10225
- simo/fleet/forms.py,sha256=XTkQUxce-6VPr7-HRONJnIzWZizeIUeA6DGqqTACInQ,62980
10225
+ simo/fleet/forms.py,sha256=344V3bLxXQdHiYzZYnwuKlyvc_6tVz8Z6XbBuSRN88s,62598
10226
10226
  simo/fleet/gateways.py,sha256=lKEJW0MgaOEiNnijH50DNSVChvaUT3TA3UurcI57P8k,5677
10227
10227
  simo/fleet/managers.py,sha256=XOpDOA9L-f_550TNSyXnJbun2EmtGz1TenVTMlUSb8E,807
10228
10228
  simo/fleet/models.py,sha256=4ZHiT2yoUv3xnDzsMbPi0Z3DOvYzEfwYZw-jxFlqG30,16884
@@ -10239,7 +10239,7 @@ simo/fleet/__pycache__/auto_urls.cpython-38.pyc,sha256=Tc6a6BCXHjijP8U2jE2ghlJwn
10239
10239
  simo/fleet/__pycache__/base_types.cpython-38.pyc,sha256=deyPwjpT6xZiFxBGFnj5b7R-lbdOTh2krgpJhrcGVhc,274
10240
10240
  simo/fleet/__pycache__/ble.cpython-38.pyc,sha256=Nrof9w7cm4OlpFWHeVnmvvanh2_oF9oQ3TknJiV93-0,1267
10241
10241
  simo/fleet/__pycache__/controllers.cpython-38.pyc,sha256=jtFHr_uyjCCeuidL-o-hGaf21u0fnxK_O6hTRdY6lpc,24906
10242
- simo/fleet/__pycache__/forms.cpython-38.pyc,sha256=8ARAilbCnSUjwluL0mmknFcNvBRn8oZ7b2-z8Yer6ns,42498
10242
+ simo/fleet/__pycache__/forms.cpython-38.pyc,sha256=vGc539_6e6-Ap-qVPoHd78p2bThdVidhV8XUzW0wQu8,42296
10243
10243
  simo/fleet/__pycache__/gateways.cpython-38.pyc,sha256=0RKVn0ndreVKhsrukqeLPSdMnRrsQ_W7yeVeBkRLfIk,5058
10244
10244
  simo/fleet/__pycache__/managers.cpython-38.pyc,sha256=8uz-xpUiqbGDgXIZ_XRZtFb-Tju6NGxflGg-Ee4Yo6k,1310
10245
10245
  simo/fleet/__pycache__/models.cpython-38.pyc,sha256=ePD11IHU9AoiufsuwcgaXukCGkNE0dco-JPajXB96c0,13935
@@ -10336,8 +10336,8 @@ simo/fleet/templates/fleet/controllers_info/ENS160AirQualitySensor.md,sha256=3LS
10336
10336
  simo/generic/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10337
10337
  simo/generic/app_widgets.py,sha256=vwYYVRKzKZ9XTvWTfEbaH4zpoHiDIHP6qFTNgZI-xvY,979
10338
10338
  simo/generic/base_types.py,sha256=Bvf3lv6PXx_SwtwBH7qpkwysWuloNcKNRh3LiuZf-Dc,359
10339
- simo/generic/controllers.py,sha256=Xo0ZYQZfhDAwLq9H3uTrHWLj-HeLlxdXJ71dUESd41Y,49263
10340
- simo/generic/forms.py,sha256=nDrvNGbWTE_MSZmSXrKj1bSn-Ylo4w7Y1z-SZRKRYW8,30840
10339
+ simo/generic/controllers.py,sha256=kGYdcuU7kpbhb6QeuOo4l-W82Zldh8vpdMnJRMKCkgU,49378
10340
+ simo/generic/forms.py,sha256=oAvWeYzLyWiNLRnXGa2124ICRD7GpLwwsF6i6L56HCI,29044
10341
10341
  simo/generic/gateways.py,sha256=dUzRBxVDdsx70mrCvxSQwhJr0ydQ5NtoLHoYeVLJmtA,15224
10342
10342
  simo/generic/models.py,sha256=Adq7ipWK-renxJlNW-SZnAq2oGEOwKx8EdUWaKnfcVQ,7597
10343
10343
  simo/generic/routing.py,sha256=elQVZmgnPiieEuti4sJ7zITk1hlRxpgbotcutJJgC60,228
@@ -10345,8 +10345,8 @@ simo/generic/socket_consumers.py,sha256=K2OjphIhKJH48BvfFfoCOyCQZ1NmXb_phs6y1IP-
10345
10345
  simo/generic/__pycache__/__init__.cpython-38.pyc,sha256=mLu54WS9KIl-pHwVCBKpsDFIlOqml--JsOVzAUHg6cU,161
10346
10346
  simo/generic/__pycache__/app_widgets.cpython-38.pyc,sha256=dt7fSf38eDA5hVUvVfpytOKoAFLQJuFffoAfqhJUKNc,1846
10347
10347
  simo/generic/__pycache__/base_types.cpython-38.pyc,sha256=8YDxrsRFGqaeBfSF3Y1WmIGDRHGH1Ww7dSBkxRxkKyc,511
10348
- simo/generic/__pycache__/controllers.cpython-38.pyc,sha256=HBZDEFhJAwd6YFWul_sX4OoA1r1m0DK1MLxJk9u70rk,32674
10349
- simo/generic/__pycache__/forms.cpython-38.pyc,sha256=JM1UN13391udDpBvNsbZURX5onDhLrOLIun-kISmGac,22078
10348
+ simo/generic/__pycache__/controllers.cpython-38.pyc,sha256=DsxStuB6A2NhZOqx2tcgMisR4Y2Tkkr30PAU45DHiok,32819
10349
+ simo/generic/__pycache__/forms.cpython-38.pyc,sha256=dJiITb6CUlTPS8qzBOOyRx0Xcj5vQ_xIQXnX27Pvw30,21268
10350
10350
  simo/generic/__pycache__/gateways.cpython-38.pyc,sha256=IazhRBe-YZ9t7_wq1fULoyLnxn3frR967lAN9D7MbKY,11583
10351
10351
  simo/generic/__pycache__/models.cpython-38.pyc,sha256=MZpum7syAFxuulf47K7gtUlJJ7xRD-IBUBAwUM1ZRnw,5825
10352
10352
  simo/generic/__pycache__/routing.cpython-38.pyc,sha256=xtxTUTBTdivzFyA5Wh7k-hUj1WDO_FiRq6HYXdbr9Ks,382
@@ -10433,7 +10433,7 @@ simo/notifications/migrations/__pycache__/0002_notification_instance.cpython-38.
10433
10433
  simo/notifications/migrations/__pycache__/__init__.cpython-38.pyc,sha256=YMBRHVon2nWDtIUbghckjnC12sIg_ykPWhV5aM0tto4,178
10434
10434
  simo/users/__init__.py,sha256=6a7uBpCWB_DR7p54rbHusc0xvi1qfT1ZCCQGb6TiBh8,52
10435
10435
  simo/users/admin.py,sha256=Xr7faGeupUKkpo1QLRm84OS63u-5Rf2ir_nVEaAPBZw,6839
10436
- simo/users/api.py,sha256=8MpsNiN_Cl8301iEf8KHCmzmmId1mivtSJKEj93y7Uk,12822
10436
+ simo/users/api.py,sha256=FdBd3D_TSrTXk1zTPtLTqppbDsf_siTvTHROjWe3Wzo,12066
10437
10437
  simo/users/apps.py,sha256=cq0A8-U1HALEwev0TicgFhr4CAu7Icz8rwq0HfOaL4E,207
10438
10438
  simo/users/auth_backends.py,sha256=bBSNXQJ88TRXaQxyh1aETfmOIfiDr08Jnj8rSY9sHDk,4074
10439
10439
  simo/users/auto_urls.py,sha256=lcJvteBsbHQMJieZpDz-63tDYejLApqsW3CUnDakd7k,272
@@ -10450,7 +10450,7 @@ simo/users/utils.py,sha256=1HGSZyHRqQvdJ4RtAiZDg1juvgG8aOlrGXR7CcvsyQc,1886
10450
10450
  simo/users/views.py,sha256=dOQVvmlHG7ihWKJLFUBcqKOA0UDctlMKR0pTc36JZqg,3487
10451
10451
  simo/users/__pycache__/__init__.cpython-38.pyc,sha256=VFoDJE_SKKaPqqYaaBYd1Ndb1hjakkTo_u0EG_XJ1GM,211
10452
10452
  simo/users/__pycache__/admin.cpython-38.pyc,sha256=tL8b3f181AjcN2dSsDUPkqjpZziEOtVzI535SbnbDzc,7793
10453
- simo/users/__pycache__/api.cpython-38.pyc,sha256=OF-wQagTY0z45X7pjO7YiYX1kz4jkYagTpLHq1BUyyY,10411
10453
+ simo/users/__pycache__/api.cpython-38.pyc,sha256=As8xBUdgmpVeUYCRCR5ZH0aU9vLdEXgnZL6TR3wAhrY,10200
10454
10454
  simo/users/__pycache__/apps.cpython-38.pyc,sha256=dgbWL8CxzzISJQTmq_4IztPJ2UzykNVdqA2Ae1PmeGk,605
10455
10455
  simo/users/__pycache__/auth_backends.cpython-38.pyc,sha256=n5nx2QSXNj2idzRcGE6bAagMN-8qxoCs580H1EFZXls,3105
10456
10456
  simo/users/__pycache__/auto_urls.cpython-38.pyc,sha256=K-3sz2h-cEitoflSmZk1t0eUg5mQMMGLNZFREVwG7_o,430
@@ -10551,9 +10551,9 @@ simo/users/templates/invitations/expired_msg.html,sha256=47DEQpj8HBSa-_TImW-5JCe
10551
10551
  simo/users/templates/invitations/expired_suggestion.html,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10552
10552
  simo/users/templates/invitations/taken_msg.html,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10553
10553
  simo/users/templates/invitations/taken_suggestion.html,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10554
- simo-2.5.8.dist-info/LICENSE.md,sha256=M7wm1EmMGDtwPRdg7kW4d00h1uAXjKOT3HFScYQMeiE,34916
10555
- simo-2.5.8.dist-info/METADATA,sha256=MS1IN9xaTOEDIMAGVtan3qT0-ejhkz04LhraYddcASA,1923
10556
- simo-2.5.8.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
10557
- simo-2.5.8.dist-info/entry_points.txt,sha256=S9PwnUYmTSW7681GKDCxUbL0leRJIaRk6fDQIKgbZBA,135
10558
- simo-2.5.8.dist-info/top_level.txt,sha256=GmS1hrAbpVqn9OWZh6UX82eIOdRLgYA82RG9fe8v4Rs,5
10559
- simo-2.5.8.dist-info/RECORD,,
10554
+ simo-2.5.9.dist-info/LICENSE.md,sha256=M7wm1EmMGDtwPRdg7kW4d00h1uAXjKOT3HFScYQMeiE,34916
10555
+ simo-2.5.9.dist-info/METADATA,sha256=seKSLClAKBnMi8ejXmRi6A8gouqbRobDzkzLi4yiFvc,1923
10556
+ simo-2.5.9.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
10557
+ simo-2.5.9.dist-info/entry_points.txt,sha256=S9PwnUYmTSW7681GKDCxUbL0leRJIaRk6fDQIKgbZBA,135
10558
+ simo-2.5.9.dist-info/top_level.txt,sha256=GmS1hrAbpVqn9OWZh6UX82eIOdRLgYA82RG9fe8v4Rs,5
10559
+ simo-2.5.9.dist-info/RECORD,,
File without changes