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

Files changed (46) hide show
  1. simo/__pycache__/settings.cpython-38.pyc +0 -0
  2. simo/backups/__pycache__/admin.cpython-38.pyc +0 -0
  3. simo/backups/__pycache__/dynamic_settings.cpython-38.pyc +0 -0
  4. simo/backups/__pycache__/models.cpython-38.pyc +0 -0
  5. simo/backups/__pycache__/tasks.cpython-38.pyc +0 -0
  6. simo/backups/admin.py +64 -3
  7. simo/backups/dynamic_settings.py +0 -7
  8. simo/backups/migrations/0002_backuplog_backup_level_backup_size.py +32 -0
  9. simo/backups/migrations/0003_alter_backuplog_options_alter_backup_size.py +22 -0
  10. simo/backups/migrations/0004_alter_backup_options_alter_backuplog_options_and_more.py +29 -0
  11. simo/backups/migrations/__pycache__/0002_backuplog_backup_level_backup_size.cpython-38.pyc +0 -0
  12. simo/backups/migrations/__pycache__/0003_alter_backuplog_options_alter_backup_size.cpython-38.pyc +0 -0
  13. simo/backups/migrations/__pycache__/0004_alter_backup_options_alter_backuplog_options_and_more.cpython-38.pyc +0 -0
  14. simo/backups/models.py +9 -2
  15. simo/backups/tasks.py +255 -113
  16. simo/core/__pycache__/api.cpython-38.pyc +0 -0
  17. simo/core/__pycache__/context.cpython-38.pyc +0 -0
  18. simo/core/__pycache__/controllers.cpython-38.pyc +0 -0
  19. simo/core/__pycache__/models.cpython-38.pyc +0 -0
  20. simo/core/__pycache__/tasks.cpython-38.pyc +0 -0
  21. simo/core/__pycache__/views.cpython-38.pyc +0 -0
  22. simo/core/api.py +2 -7
  23. simo/core/controllers.py +8 -1
  24. simo/core/management/_hub_template/hub/supervisor.conf +4 -0
  25. simo/core/migrations/0042_alter_instance_timezone.py +18 -0
  26. simo/core/migrations/__pycache__/0042_alter_instance_timezone.cpython-38.pyc +0 -0
  27. simo/core/models.py +6 -2
  28. simo/core/tasks.py +8 -1
  29. simo/core/views.py +4 -12
  30. simo/fleet/__pycache__/forms.cpython-38.pyc +0 -0
  31. simo/fleet/forms.py +140 -176
  32. simo/fleet/migrations/0038_alter_colonel_type.py +18 -0
  33. simo/fleet/migrations/__pycache__/0038_alter_colonel_type.cpython-38.pyc +0 -0
  34. simo/notifications/__pycache__/models.cpython-38.pyc +0 -0
  35. simo/settings.py +1 -0
  36. simo/users/__pycache__/models.cpython-38.pyc +0 -0
  37. simo/users/__pycache__/serializers.cpython-38.pyc +0 -0
  38. simo/users/migrations/0033_alter_user_ssh_key.py +18 -0
  39. simo/users/migrations/__pycache__/0033_alter_user_ssh_key.cpython-38.pyc +0 -0
  40. simo/users/models.py +38 -16
  41. {simo-2.3.7.dist-info → simo-2.4.2.dist-info}/METADATA +2 -1
  42. {simo-2.3.7.dist-info → simo-2.4.2.dist-info}/RECORD +46 -33
  43. {simo-2.3.7.dist-info → simo-2.4.2.dist-info}/LICENSE.md +0 -0
  44. {simo-2.3.7.dist-info → simo-2.4.2.dist-info}/WHEEL +0 -0
  45. {simo-2.3.7.dist-info → simo-2.4.2.dist-info}/entry_points.txt +0 -0
  46. {simo-2.3.7.dist-info → simo-2.4.2.dist-info}/top_level.txt +0 -0
simo/fleet/forms.py CHANGED
@@ -195,17 +195,15 @@ class ControlForm(forms.Form):
195
195
 
196
196
 
197
197
  class ColonelBinarySensorConfigForm(ColonelComponentForm):
198
- pin = ColonelPinChoiceField(
198
+ pin = Select2ModelChoiceField(
199
199
  label='Port',
200
200
  queryset=ColonelPin.objects.filter(input=True),
201
- widget=autocomplete.ListSelect2(
202
- url='autocomplete-colonel-pins',
203
- forward=[
204
- forward.Self(),
205
- forward.Field('colonel'),
206
- forward.Const({'input': True}, 'filters')
207
- ]
208
- )
201
+ url='autocomplete-colonel-pins',
202
+ forward=[
203
+ forward.Self(),
204
+ forward.Field('colonel'),
205
+ forward.Const({'input': True}, 'filters')
206
+ ]
209
207
  )
210
208
  inverse = forms.TypedChoiceField(
211
209
  choices=((1, "Yes"), (0, "No")), coerce=int, initial=1,
@@ -283,17 +281,15 @@ class ColonelBinarySensorConfigForm(ColonelComponentForm):
283
281
 
284
282
 
285
283
  class ColonelButtonConfigForm(ColonelComponentForm):
286
- pin = ColonelPinChoiceField(
284
+ pin = Select2ModelChoiceField(
287
285
  label="Port",
288
286
  queryset=ColonelPin.objects.filter(input=True),
289
- widget=autocomplete.ListSelect2(
290
- url='autocomplete-colonel-pins',
291
- forward=[
292
- forward.Self(),
293
- forward.Field('colonel'),
294
- forward.Const({'input': True}, 'filters')
295
- ]
296
- )
287
+ url='autocomplete-colonel-pins',
288
+ forward=[
289
+ forward.Self(),
290
+ forward.Field('colonel'),
291
+ forward.Const({'input': True}, 'filters')
292
+ ]
297
293
  )
298
294
  action_method = forms.ChoiceField(
299
295
  label="Action method", initial='down',
@@ -322,19 +318,17 @@ class ColonelButtonConfigForm(ColonelComponentForm):
322
318
 
323
319
 
324
320
  class ColonelNumericSensorConfigForm(ColonelComponentForm, NumericSensorForm):
325
- pin = ColonelPinChoiceField(
321
+ pin = Select2ModelChoiceField(
326
322
  label="Port",
327
323
  queryset=ColonelPin.objects.filter(adc=True, input=True, native=True),
328
- widget=autocomplete.ListSelect2(
329
- url='autocomplete-colonel-pins',
330
- forward=[
331
- forward.Self(),
332
- forward.Field('colonel'),
333
- forward.Const(
334
- {'adc': True, 'native': True, 'input': True}, 'filters'
335
- )
336
- ]
337
- )
324
+ url='autocomplete-colonel-pins',
325
+ forward=[
326
+ forward.Self(),
327
+ forward.Field('colonel'),
328
+ forward.Const(
329
+ {'adc': True, 'native': True, 'input': True}, 'filters'
330
+ )
331
+ ]
338
332
  )
339
333
  attenuation = forms.TypedChoiceField(
340
334
  initial=0, coerce=int, choices=(
@@ -375,19 +369,17 @@ class ColonelNumericSensorConfigForm(ColonelComponentForm, NumericSensorForm):
375
369
 
376
370
 
377
371
  class DS18B20SensorConfigForm(ColonelComponentForm, NumericSensorForm):
378
- pin = ColonelPinChoiceField(
372
+ pin = Select2ModelChoiceField(
379
373
  label="Port",
380
374
  queryset=ColonelPin.objects.filter(input=True, native=True),
381
- widget=autocomplete.ListSelect2(
382
- url='autocomplete-colonel-pins',
383
- forward=[
384
- forward.Self(),
385
- forward.Field('colonel'),
386
- forward.Const(
387
- {'native': True, 'input': True}, 'filters'
388
- )
389
- ]
390
- )
375
+ url='autocomplete-colonel-pins',
376
+ forward=[
377
+ forward.Self(),
378
+ forward.Field('colonel'),
379
+ forward.Const(
380
+ {'native': True, 'input': True}, 'filters'
381
+ )
382
+ ]
391
383
  )
392
384
  read_frequency_s = forms.IntegerField(
393
385
  initial=60, min_value=1, max_value=60*60*24,
@@ -415,19 +407,17 @@ class DS18B20SensorConfigForm(ColonelComponentForm, NumericSensorForm):
415
407
 
416
408
 
417
409
  class ColonelDHTSensorConfigForm(ColonelComponentForm):
418
- pin = ColonelPinChoiceField(
410
+ pin = Select2ModelChoiceField(
419
411
  label="Port",
420
412
  queryset=ColonelPin.objects.filter(input=True, native=True),
421
- widget=autocomplete.ListSelect2(
422
- url='autocomplete-colonel-pins',
423
- forward=[
424
- forward.Self(),
425
- forward.Field('colonel'),
426
- forward.Const(
427
- {'native': True, 'input': True}, 'filters'
428
- )
429
- ]
430
- )
413
+ url='autocomplete-colonel-pins',
414
+ forward=[
415
+ forward.Self(),
416
+ forward.Field('colonel'),
417
+ forward.Const(
418
+ {'native': True, 'input': True}, 'filters'
419
+ )
420
+ ]
431
421
  )
432
422
  sensor_type = forms.TypedChoiceField(
433
423
  initial=11, coerce=int, choices=(
@@ -462,18 +452,16 @@ class ColonelDHTSensorConfigForm(ColonelComponentForm):
462
452
 
463
453
 
464
454
  class BME680SensorConfigForm(ColonelComponentForm):
465
- interface = ColonelInterfacesChoiceField(
455
+ interface = Select2ModelChoiceField(
466
456
  queryset=Interface.objects.filter(type='i2c'),
467
- widget=autocomplete.ListSelect2(
468
- url='autocomplete-interfaces',
469
- forward=[
470
- forward.Self(),
471
- forward.Field('colonel'),
472
- forward.Const(
473
- {'type': 'i2c'}, 'filters'
474
- )
475
- ]
476
- )
457
+ url='autocomplete-interfaces',
458
+ forward=[
459
+ forward.Self(),
460
+ forward.Field('colonel'),
461
+ forward.Const(
462
+ {'type': 'i2c'}, 'filters'
463
+ )
464
+ ]
477
465
  )
478
466
  i2c_address = forms.TypedChoiceField(
479
467
  coerce=int, initial=118,
@@ -511,18 +499,16 @@ class BME680SensorConfigForm(ColonelComponentForm):
511
499
 
512
500
 
513
501
  class MPC9808SensorConfigForm(ColonelComponentForm):
514
- interface = ColonelInterfacesChoiceField(
502
+ interface = Select2ModelChoiceField(
515
503
  queryset=Interface.objects.filter(type='i2c'),
516
- widget=autocomplete.ListSelect2(
517
- url='autocomplete-interfaces',
518
- forward=[
519
- forward.Self(),
520
- forward.Field('colonel'),
521
- forward.Const(
522
- {'type': 'i2c'}, 'filters'
523
- )
524
- ]
525
- )
504
+ url='autocomplete-interfaces',
505
+ forward=[
506
+ forward.Self(),
507
+ forward.Field('colonel'),
508
+ forward.Const(
509
+ {'type': 'i2c'}, 'filters'
510
+ )
511
+ ]
526
512
  )
527
513
  i2c_address = forms.TypedChoiceField(
528
514
  coerce=int, initial=24,
@@ -566,18 +552,16 @@ class MPC9808SensorConfigForm(ColonelComponentForm):
566
552
 
567
553
 
568
554
  class ENS160SensorConfigForm(ColonelComponentForm):
569
- interface = ColonelInterfacesChoiceField(
555
+ interface = Select2ModelChoiceField(
570
556
  queryset=Interface.objects.filter(type='i2c'),
571
- widget=autocomplete.ListSelect2(
572
- url='autocomplete-interfaces',
573
- forward=[
574
- forward.Self(),
575
- forward.Field('colonel'),
576
- forward.Const(
577
- {'type': 'i2c'}, 'filters'
578
- )
579
- ]
580
- )
557
+ url='autocomplete-interfaces',
558
+ forward=[
559
+ forward.Self(),
560
+ forward.Field('colonel'),
561
+ forward.Const(
562
+ {'type': 'i2c'}, 'filters'
563
+ )
564
+ ]
581
565
  )
582
566
  i2c_address = forms.TypedChoiceField(
583
567
  coerce=int, initial=83,
@@ -616,17 +600,15 @@ class ENS160SensorConfigForm(ColonelComponentForm):
616
600
 
617
601
 
618
602
  class ColonelTouchSensorConfigForm(ColonelComponentForm):
619
- pin = ColonelPinChoiceField(
603
+ pin = Select2ModelChoiceField(
620
604
  label="Port",
621
605
  queryset=ColonelPin.objects.filter(input=True, capacitive=True),
622
- widget=autocomplete.ListSelect2(
623
- url='autocomplete-colonel-pins',
624
- forward=[
625
- forward.Self(),
626
- forward.Field('colonel'),
627
- forward.Const({'input': True, 'capacitive': True}, 'filters')
628
- ]
629
- )
606
+ url='autocomplete-colonel-pins',
607
+ forward=[
608
+ forward.Self(),
609
+ forward.Field('colonel'),
610
+ forward.Const({'input': True, 'capacitive': True}, 'filters')
611
+ ]
630
612
  )
631
613
  threshold = forms.IntegerField(
632
614
  min_value=0, max_value=999999999, required=False, initial=1000,
@@ -740,17 +722,15 @@ class ColonelSwitchConfigForm(ColonelComponentForm):
740
722
 
741
723
 
742
724
  class ColonelPWMOutputConfigForm(ColonelComponentForm):
743
- output_pin = ColonelPinChoiceField(
725
+ output_pin = Select2ModelChoiceField(
744
726
  label="Port",
745
727
  queryset=ColonelPin.objects.filter(output=True),
746
- widget=autocomplete.ListSelect2(
747
- url='autocomplete-colonel-pins',
748
- forward=[
749
- forward.Self(),
750
- forward.Field('colonel'),
751
- forward.Const({'output': True}, 'filters')
752
- ]
753
- )
728
+ url='autocomplete-colonel-pins',
729
+ forward=[
730
+ forward.Self(),
731
+ forward.Field('colonel'),
732
+ forward.Const({'output': True}, 'filters')
733
+ ]
754
734
  )
755
735
  min = forms.FloatField(
756
736
  required=True, initial=0,
@@ -875,17 +855,15 @@ class ColonelPWMOutputConfigForm(ColonelComponentForm):
875
855
 
876
856
 
877
857
  class ColonelRGBLightConfigForm(ColonelComponentForm):
878
- output_pin = ColonelPinChoiceField(
858
+ output_pin = Select2ModelChoiceField(
879
859
  label="Port",
880
860
  queryset=ColonelPin.objects.filter(output=True, native=True),
881
- widget=autocomplete.ListSelect2(
882
- url='autocomplete-colonel-pins',
883
- forward=[
884
- forward.Self(),
885
- forward.Field('colonel'),
886
- forward.Const({'output': True, 'native': True}, 'filters')
887
- ]
888
- )
861
+ url='autocomplete-colonel-pins',
862
+ forward=[
863
+ forward.Self(),
864
+ forward.Field('colonel'),
865
+ forward.Const({'output': True, 'native': True}, 'filters')
866
+ ]
889
867
  )
890
868
  num_leds = forms.IntegerField(
891
869
  label=_("Number of leds"), min_value=1, max_value=2000
@@ -998,17 +976,15 @@ class ColonelRGBLightConfigForm(ColonelComponentForm):
998
976
 
999
977
 
1000
978
  class DualMotorValveForm(ColonelComponentForm):
1001
- open_pin = ColonelPinChoiceField(
979
+ open_pin = Select2ModelChoiceField(
1002
980
  label="Open Relay Port",
1003
981
  queryset=ColonelPin.objects.filter(output=True),
1004
- widget=autocomplete.ListSelect2(
1005
- url='autocomplete-colonel-pins',
1006
- forward=[
1007
- forward.Self(),
1008
- forward.Field('colonel'),
1009
- forward.Const({'output': True}, 'filters')
1010
- ]
1011
- )
982
+ url='autocomplete-colonel-pins',
983
+ forward=[
984
+ forward.Self(),
985
+ forward.Field('colonel'),
986
+ forward.Const({'output': True}, 'filters')
987
+ ]
1012
988
  )
1013
989
  open_action = forms.ChoiceField(
1014
990
  choices=(('HIGH', "HIGH"), ('LOW', "LOW")),
@@ -1017,17 +993,15 @@ class DualMotorValveForm(ColonelComponentForm):
1017
993
  required=True, min_value=0.01, max_value=1000000000,
1018
994
  initial=2, help_text="Time in seconds to open."
1019
995
  )
1020
- close_pin = ColonelPinChoiceField(
996
+ close_pin = Select2ModelChoiceField(
1021
997
  label="Close Relay Port",
1022
998
  queryset=ColonelPin.objects.filter(output=True),
1023
- widget=autocomplete.ListSelect2(
1024
- url='autocomplete-colonel-pins',
1025
- forward=[
1026
- forward.Self(),
1027
- forward.Field('colonel'),
1028
- forward.Const({'output': True}, 'filters')
1029
- ]
1030
- )
999
+ url='autocomplete-colonel-pins',
1000
+ forward=[
1001
+ forward.Self(),
1002
+ forward.Field('colonel'),
1003
+ forward.Const({'output': True}, 'filters')
1004
+ ]
1031
1005
  )
1032
1006
  close_action = forms.ChoiceField(
1033
1007
  choices=(('HIGH', "HIGH"), ('LOW', "LOW")),
@@ -1064,32 +1038,28 @@ class DualMotorValveForm(ColonelComponentForm):
1064
1038
 
1065
1039
 
1066
1040
  class BlindsConfigForm(ColonelComponentForm):
1067
- open_pin = ColonelPinChoiceField(
1041
+ open_pin = Select2ModelChoiceField(
1068
1042
  label="Open Relay Port",
1069
1043
  queryset=ColonelPin.objects.filter(output=True),
1070
- widget=autocomplete.ListSelect2(
1071
- url='autocomplete-colonel-pins',
1072
- forward=[
1073
- forward.Self(),
1074
- forward.Field('colonel'),
1075
- forward.Const({'output': True}, 'filters')
1076
- ]
1077
- )
1044
+ url='autocomplete-colonel-pins',
1045
+ forward=[
1046
+ forward.Self(),
1047
+ forward.Field('colonel'),
1048
+ forward.Const({'output': True}, 'filters')
1049
+ ]
1078
1050
  )
1079
1051
  open_action = forms.ChoiceField(
1080
1052
  choices=(('HIGH', "HIGH"), ('LOW', "LOW")),
1081
1053
  )
1082
- close_pin = ColonelPinChoiceField(
1054
+ close_pin = Select2ModelChoiceField(
1083
1055
  label="Close Relay Port",
1084
1056
  queryset=ColonelPin.objects.filter(output=True),
1085
- widget=autocomplete.ListSelect2(
1086
- url='autocomplete-colonel-pins',
1087
- forward=[
1088
- forward.Self(),
1089
- forward.Field('colonel'),
1090
- forward.Const({'output': True}, 'filters')
1091
- ]
1092
- )
1057
+ url='autocomplete-colonel-pins',
1058
+ forward=[
1059
+ forward.Self(),
1060
+ forward.Field('colonel'),
1061
+ forward.Const({'output': True}, 'filters')
1062
+ ]
1093
1063
  )
1094
1064
  close_action = forms.ChoiceField(
1095
1065
  choices=(('HIGH', "HIGH"), ('LOW', "LOW")),
@@ -1212,29 +1182,25 @@ class BlindsConfigForm(ColonelComponentForm):
1212
1182
 
1213
1183
 
1214
1184
  class BurglarSmokeDetectorConfigForm(ColonelComponentForm):
1215
- power_pin = ColonelPinChoiceField(
1185
+ power_pin = Select2ModelChoiceField(
1216
1186
  label="Power port",
1217
1187
  queryset=ColonelPin.objects.filter(output=True),
1218
- widget=autocomplete.ListSelect2(
1219
- url='autocomplete-colonel-pins',
1220
- forward=[
1221
- forward.Self(),
1222
- forward.Field('colonel'),
1223
- forward.Const({'output': True}, 'filters')
1224
- ]
1225
- )
1188
+ url='autocomplete-colonel-pins',
1189
+ forward=[
1190
+ forward.Self(),
1191
+ forward.Field('colonel'),
1192
+ forward.Const({'output': True}, 'filters')
1193
+ ]
1226
1194
  )
1227
- sensor_pin = ColonelPinChoiceField(
1195
+ sensor_pin = Select2ModelChoiceField(
1228
1196
  label="Sensor port",
1229
1197
  queryset=ColonelPin.objects.filter(input=True),
1230
- widget=autocomplete.ListSelect2(
1231
- url='autocomplete-colonel-pins',
1232
- forward=[
1233
- forward.Self(),
1234
- forward.Field('colonel'),
1235
- forward.Const({'input': True}, 'filters')
1236
- ]
1237
- )
1198
+ url='autocomplete-colonel-pins',
1199
+ forward=[
1200
+ forward.Self(),
1201
+ forward.Field('colonel'),
1202
+ forward.Const({'input': True}, 'filters')
1203
+ ]
1238
1204
  )
1239
1205
  sensor_inverse = forms.TypedChoiceField(
1240
1206
  choices=((0, "No"), (1, "Yes")), coerce=int, initial=0,
@@ -1306,18 +1272,16 @@ class TTLockConfigForm(ColonelComponentForm):
1306
1272
 
1307
1273
 
1308
1274
  class DALIDeviceConfigForm(ColonelComponentForm):
1309
- interface = ColonelInterfacesChoiceField(
1275
+ interface = Select2ModelChoiceField(
1310
1276
  queryset=Interface.objects.filter(type='dali'),
1311
- widget=autocomplete.ListSelect2(
1312
- url='autocomplete-interfaces',
1313
- forward=[
1314
- forward.Self(),
1315
- forward.Field('colonel'),
1316
- forward.Const(
1317
- {'type': 'dali'}, 'filters'
1318
- )
1319
- ]
1320
- )
1277
+ url='autocomplete-interfaces',
1278
+ forward=[
1279
+ forward.Self(),
1280
+ forward.Field('colonel'),
1281
+ forward.Const(
1282
+ {'type': 'dali'}, 'filters'
1283
+ )
1284
+ ]
1321
1285
  )
1322
1286
 
1323
1287
  def clean_interface(self):
@@ -0,0 +1,18 @@
1
+ # Generated by Django 4.2.10 on 2024-10-09 09:16
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('fleet', '0037_alter_colonelpin_options_alter_colonelpin_no_and_more'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AlterField(
14
+ model_name='colonel',
15
+ name='type',
16
+ field=models.CharField(choices=[('4-relays', '4 Relay'), ('ample-wall', 'Ample Wall'), ('game-changer', 'Game Changer'), ('game-changer-mini', 'Game Changer Mini')], default='ample-wall', max_length=20),
17
+ ),
18
+ ]
simo/settings.py CHANGED
@@ -65,6 +65,7 @@ INSTALLED_APPS = [
65
65
  'bootstrap4',
66
66
  'taggit',
67
67
  'actstream',
68
+ 'django_object_actions',
68
69
 
69
70
  'simo.core',
70
71
  'simo.users',
@@ -0,0 +1,18 @@
1
+ # Generated by Django 4.2.10 on 2024-10-09 09:16
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('users', '0032_remove_userdevice_user_alter_userdevice_users'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AlterField(
14
+ model_name='user',
15
+ name='ssh_key',
16
+ field=models.TextField(blank=True, help_text='Will be placed in /root/.ssh/authorized_keys if user is active and is master of a hub.', null=True),
17
+ ),
18
+ ]
simo/users/models.py CHANGED
@@ -252,10 +252,15 @@ class User(AbstractBaseUser, SimoAdminMixin):
252
252
  '''Used by API serializer to get users role on a given instance.'''
253
253
  if not self._instance:
254
254
  return None
255
- for role in self.roles.all():
256
- if role.instance == self._instance:
257
- return role.id
258
- return None
255
+ cache_key = f'user-{self.id}_instance-{self._instance.id}-role-id'
256
+ cached_val = cache.get(cache_key, 'expired')
257
+ if cached_val == 'expired':
258
+ for role in self.roles.all().select_related('instance'):
259
+ if role.instance == self._instance:
260
+ cached_val = role.id
261
+ cache.set(cache_key, role.id, 20)
262
+ return cached_val
263
+ return cached_val
259
264
 
260
265
  @role_id.setter
261
266
  def role_id(self, id):
@@ -290,7 +295,6 @@ class User(AbstractBaseUser, SimoAdminMixin):
290
295
  )
291
296
  ])
292
297
  cache.set(cache_key, instances, 10)
293
- print("INSTANCES: ", instances)
294
298
  return instances
295
299
 
296
300
  @property
@@ -301,26 +305,44 @@ class User(AbstractBaseUser, SimoAdminMixin):
301
305
 
302
306
  @property
303
307
  def is_active(self):
304
- if self.is_master and not self.instance_roles.all():
305
- # Master who have no roles on any instance are in GOD mode!
306
- # It can not be disabled by anybody, nor it is seen by anybody. :)
307
- return True
308
- if self._instance:
309
- return bool(
310
- self.instance_roles.filter(
311
- instance=self._instance, is_active=True
312
- ).first()
313
- )
314
- return any([ir.is_active for ir in self.instance_roles.all()])
308
+ if not self._instance:
309
+ cache_key = f'user-{self.id}_is_active'
310
+ else:
311
+ cache_key = f'user-{self.id}_is_active_instance-{self._instance.id}'
312
+ cached_value = cache.get(cache_key, 'expired')
313
+ if cached_value == 'expired':
314
+ if self.is_master and not self.instance_roles.all():
315
+ # Master who have no roles on any instance are in GOD mode!
316
+ # It can not be disabled by anybody, nor it is seen by anybody. :)
317
+ cached_value = True
318
+ elif self._instance:
319
+ cached_value = bool(
320
+ self.instance_roles.filter(
321
+ instance=self._instance, is_active=True
322
+ ).first()
323
+ )
324
+ else:
325
+ cached_value = any(
326
+ [ir.is_active for ir in self.instance_roles.all()]
327
+ )
328
+ cache.set(cache_key, cached_value, 20)
329
+ return cached_value
315
330
 
316
331
 
317
332
  @is_active.setter
318
333
  def is_active(self, val):
319
334
  if not self._instance:
320
335
  return
336
+
321
337
  self.instance_roles.filter(
322
338
  instance=self._instance
323
339
  ).update(is_active=bool(val))
340
+ cache_key = f'user-{self.id}_is_active_instance-{self._instance.id}'
341
+ try:
342
+ cache.delete(cache_key)
343
+ except:
344
+ pass
345
+
324
346
  rebuild_authorized_keys()
325
347
 
326
348
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: simo
3
- Version: 2.3.7
3
+ Version: 2.4.2
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
@@ -49,4 +49,5 @@ Requires-Dist: django-markdownify ==0.9.5
49
49
  Requires-Dist: django-activity-stream ==2.0.0
50
50
  Requires-Dist: gunicorn ==23.0.0
51
51
  Requires-Dist: python-crontab ==3.2.0
52
+ Requires-Dist: django-object-actions ==4.3.0
52
53