simo 2.0.32__py3-none-any.whl → 2.0.33__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 (60) hide show
  1. simo/cli.py +2 -17
  2. simo/core/__pycache__/admin.cpython-38.pyc +0 -0
  3. simo/core/__pycache__/api.cpython-38.pyc +0 -0
  4. simo/core/__pycache__/controllers.cpython-38.pyc +0 -0
  5. simo/core/__pycache__/forms.cpython-38.pyc +0 -0
  6. simo/core/__pycache__/managers.cpython-38.pyc +0 -0
  7. simo/core/__pycache__/models.cpython-38.pyc +0 -0
  8. simo/core/__pycache__/serializers.cpython-38.pyc +0 -0
  9. simo/core/__pycache__/signal_receivers.cpython-38.pyc +0 -0
  10. simo/core/__pycache__/tasks.cpython-38.pyc +0 -0
  11. simo/core/admin.py +2 -2
  12. simo/core/api.py +25 -2
  13. simo/core/controllers.py +11 -7
  14. simo/core/forms.py +1 -2
  15. simo/core/managers.py +1 -5
  16. simo/core/migrations/0003_create_default_zones_and_categories.py +2 -39
  17. simo/core/migrations/0004_create_generic.py +22 -21
  18. simo/core/migrations/0013_auto_20231003_0754.py +45 -43
  19. simo/core/migrations/0018_auto_20231005_0622.py +18 -16
  20. simo/core/migrations/0033_auto_20240509_0821.py +25 -0
  21. simo/core/migrations/0034_component_error_msg.py +18 -0
  22. simo/core/migrations/0035_remove_instance_share_location.py +17 -0
  23. simo/core/migrations/__pycache__/0003_create_default_zones_and_categories.cpython-38.pyc +0 -0
  24. simo/core/migrations/__pycache__/0004_create_generic.cpython-38.pyc +0 -0
  25. simo/core/migrations/__pycache__/0013_auto_20231003_0754.cpython-38.pyc +0 -0
  26. simo/core/migrations/__pycache__/0018_auto_20231005_0622.cpython-38.pyc +0 -0
  27. simo/core/migrations/__pycache__/0033_auto_20240509_0821.cpython-38.pyc +0 -0
  28. simo/core/migrations/__pycache__/0034_component_error_msg.cpython-38.pyc +0 -0
  29. simo/core/migrations/__pycache__/0035_remove_instance_share_location.cpython-38.pyc +0 -0
  30. simo/core/models.py +25 -23
  31. simo/core/serializers.py +5 -3
  32. simo/core/signal_receivers.py +82 -1
  33. simo/core/tasks.py +7 -4
  34. simo/fleet/__pycache__/admin.cpython-38.pyc +0 -0
  35. simo/fleet/__pycache__/controllers.cpython-38.pyc +0 -0
  36. simo/fleet/__pycache__/forms.cpython-38.pyc +0 -0
  37. simo/fleet/__pycache__/models.cpython-38.pyc +0 -0
  38. simo/fleet/__pycache__/serializers.cpython-38.pyc +0 -0
  39. simo/fleet/__pycache__/socket_consumers.cpython-38.pyc +0 -0
  40. simo/fleet/admin.py +25 -6
  41. simo/fleet/controllers.py +82 -37
  42. simo/fleet/forms.py +142 -10
  43. simo/fleet/migrations/0035_auto_20240514_0855.py +32 -0
  44. simo/fleet/migrations/__pycache__/0035_auto_20240514_0855.cpython-38.pyc +0 -0
  45. simo/fleet/models.py +96 -82
  46. simo/fleet/serializers.py +8 -1
  47. simo/fleet/socket_consumers.py +3 -15
  48. simo/users/__pycache__/models.cpython-38.pyc +0 -0
  49. simo/users/__pycache__/utils.cpython-38.pyc +0 -0
  50. simo/users/migrations/0003_create_roles_and_system_user.py +24 -23
  51. simo/users/migrations/0019_auto_20231221_1155.py +9 -8
  52. simo/users/migrations/__pycache__/0003_create_roles_and_system_user.cpython-38.pyc +0 -0
  53. simo/users/migrations/__pycache__/0019_auto_20231221_1155.cpython-38.pyc +0 -0
  54. simo/users/models.py +6 -7
  55. simo/users/utils.py +0 -4
  56. {simo-2.0.32.dist-info → simo-2.0.33.dist-info}/METADATA +1 -1
  57. {simo-2.0.32.dist-info → simo-2.0.33.dist-info}/RECORD +60 -52
  58. {simo-2.0.32.dist-info → simo-2.0.33.dist-info}/LICENSE.md +0 -0
  59. {simo-2.0.32.dist-info → simo-2.0.33.dist-info}/WHEEL +0 -0
  60. {simo-2.0.32.dist-info → simo-2.0.33.dist-info}/top_level.txt +0 -0
simo/fleet/forms.py CHANGED
@@ -647,9 +647,9 @@ class ColonelPWMOutputConfigForm(ColonelComponentForm):
647
647
 
648
648
  def clean(self):
649
649
  super().clean()
650
- if self.cleaned_data.get('output_pin'):
650
+ if 'output_pin' in self.cleaned_data:
651
651
  self._clean_pin('output_pin')
652
- if self.cleaned_data.get('controls'):
652
+ if 'controls' in self.cleaned_data:
653
653
  self._clean_controls()
654
654
  return self.cleaned_data
655
655
 
@@ -745,7 +745,9 @@ class ColonelRGBLightConfigForm(ColonelComponentForm):
745
745
  def clean(self):
746
746
  super().clean()
747
747
 
748
- self._clean_pin('output_pin')
748
+ if 'output_pin' in self.cleaned_data:
749
+ self._clean_pin('output_pin')
750
+
749
751
  if self.cleaned_data.get('controls'):
750
752
  self._clean_controls()
751
753
 
@@ -1034,23 +1036,153 @@ class DALIDeviceConfigForm(ColonelComponentForm):
1034
1036
  )
1035
1037
  )
1036
1038
 
1037
- def save(self, commit=True):
1039
+ def clean_interface(self):
1040
+ if not self.instance.pk:
1041
+ return self.cleaned_data['interface']
1042
+ if 'interface' in self.changed_data:
1043
+ raise forms.ValidationError(
1044
+ "Changing interface after component is created "
1045
+ "it is not allowed!"
1046
+ )
1047
+ return self.cleaned_data['interface']
1048
+
1049
+ def save(self, commit=True, update_colonel_config=True):
1038
1050
  if 'interface' in self.cleaned_data:
1039
1051
  self.instance.config['dali_interface'] = \
1040
1052
  self.cleaned_data['interface'].no
1041
- return super().save(commit=commit)
1042
1053
 
1054
+ # prevent immediate config update on colonel as dali devices are
1055
+ # added via pairing process, which uses finalization procedure.
1056
+ is_new = not self.instance.pk
1057
+ obj = super(BaseComponentForm, self).save(commit=commit)
1058
+ if commit:
1059
+ self.cleaned_data['colonel'].components.add(obj)
1060
+ if not is_new and update_colonel_config:
1061
+ GatewayObjectCommand(
1062
+ obj.gateway, self.cleaned_data['colonel'], id=obj.id,
1063
+ command='call', method='update_config', args=[
1064
+ obj.controller._get_colonel_config()
1065
+ ]
1066
+ ).publish()
1067
+ return obj
1043
1068
 
1044
- class DaliSwitchForm(DALIDeviceConfigForm, SwitchForm):
1045
1069
 
1070
+ class DaliLampForm(DALIDeviceConfigForm, BaseComponentForm):
1071
+ boot_update = forms.BooleanField(
1072
+ initial=False, required=False,
1073
+ help_text="Update device config on colonel boot."
1074
+ )
1075
+ fade_time = forms.TypedChoiceField(
1076
+ initial=3, choices=(
1077
+ (1, "0.7 s"), (2, "1.0 s"), (3, "1.4 s"), (4, "2.0 s"), (5, "2.8 s"),
1078
+ (6, "4.0 s"), (7, "5.7 s"), (8, "8.0 s")
1079
+ )
1080
+ )
1081
+ gear_min = forms.IntegerField(
1082
+ min_value=1, max_value=254, initial=90,
1083
+ help_text="Minimum level at which device starts operating up (1 - 254), "
1084
+ "SIMO.io DALI interface detects this value automatically when "
1085
+ "pairing a new device. <br>"
1086
+ "Most LED drivers we tested starts at 86. <br>"
1087
+ "If you set this value to low, you might start seeing device "
1088
+ "beings dying out when you hit it with lower value than it "
1089
+ "is capable of supplying."
1090
+ )
1046
1091
  auto_off = forms.FloatField(
1047
1092
  required=False, min_value=0.01, max_value=1000000000,
1048
- help_text="If provided, switch will be turned off after "
1093
+ help_text="If provided, lamp will be turned off after "
1049
1094
  "given amount of seconds after last turn on event."
1050
1095
  )
1051
- inverse = forms.BooleanField(
1052
- label=_("Inverse switch value"), required=False
1053
- )
1054
1096
 
1055
1097
 
1098
+ class DaliGearGroupForm(DALIDeviceConfigForm, BaseComponentForm):
1099
+ auto_off = forms.FloatField(
1100
+ required=False, min_value=0.01, max_value=1000000000,
1101
+ help_text="If provided, group will be turned off after "
1102
+ "given amount of seconds after last turn on event."
1103
+ )
1104
+ members = forms.ModelMultipleChoiceField(
1105
+ Component.objects.filter(
1106
+ controller_uid='simo.fleet.controllers.DALILamp',
1107
+ ),
1108
+ label="Members", required=True,
1109
+ widget=autocomplete.ModelSelect2Multiple(
1110
+ url='autocomplete-component', attrs={'data-html': True},
1111
+ forward=(
1112
+ forward.Const(
1113
+ ['simo.fleet.controllers.DALILamp', ], 'controller_uid'
1114
+ ),
1115
+ )
1116
+ )
1117
+ )
1056
1118
 
1119
+ def clean(self):
1120
+ if 'members' in self.cleaned_data:
1121
+ if len(self.cleaned_data['members']) < 2:
1122
+ raise forms.ValidationError("At least two members are required.")
1123
+ for member in self.cleaned_data['members']:
1124
+ if member.config['interface'] != self.cleaned_data['interface'].id:
1125
+ self.add_error(
1126
+ 'members', f"{member} belongs to other DALI interface."
1127
+ )
1128
+ self.group_addr = None
1129
+ if not self.instance.pk:
1130
+ from .controllers import DALIGearGroup
1131
+ occupied_addresses = set([
1132
+ int(c['config'].get('da', 0)) for c in Component.objects.filter(
1133
+ controller_uid=DALIGearGroup.uid,
1134
+ config__colonel=self.cleaned_data['colonel'].id,
1135
+ config__interface=self.cleaned_data['interface'].id,
1136
+ ).values('config')])
1137
+ for addr in range(16):
1138
+ if addr in occupied_addresses:
1139
+ continue
1140
+ self.group_addr = addr
1141
+ break
1142
+ if self.group_addr is None:
1143
+ self.add_error(
1144
+ 'interface',
1145
+ "Already has 16 groups. No more groups are allowed on DALI line."
1146
+ )
1147
+ else:
1148
+ self.group_addr = self.instance.config['da']
1149
+ return self.cleaned_data
1150
+
1151
+ def save(self, commit=True, update_colonel_config=True):
1152
+ old_members = self.instance.config.get('members', [])
1153
+ self.instance.config['da'] = self.group_addr
1154
+ is_new = not self.instance.pk
1155
+ obj = super().save(commit, update_colonel_config=False)
1156
+ if commit:
1157
+ self.cleaned_data['colonel'].components.add(obj)
1158
+ new_members = obj.config.get('members', [])
1159
+ for removed_member in Component.objects.filter(
1160
+ id__in=set(old_members) - set(new_members)
1161
+ ):
1162
+ self.controller._modify_member_group(
1163
+ removed_member, self.group_addr, remove=True
1164
+ )
1165
+ for member in Component.objects.filter(id__in=new_members):
1166
+ self.controller._modify_member_group(member, self.group_addr)
1167
+ if is_new:
1168
+ GatewayObjectCommand(
1169
+ obj.gateway, self.cleaned_data['colonel'],
1170
+ command='finalize',
1171
+ data={
1172
+ 'temp_id': 'none',
1173
+ 'permanent_id': obj.id,
1174
+ 'comp_config': {
1175
+ 'type': obj.controller_uid.split('.')[-1],
1176
+ 'family': self.controller.family,
1177
+ 'config': obj.config
1178
+ }
1179
+ }
1180
+ ).publish()
1181
+ else:
1182
+ GatewayObjectCommand(
1183
+ obj.gateway, self.cleaned_data['colonel'], id=obj.id,
1184
+ command='call', method='update_config', args=[
1185
+ obj.controller._get_colonel_config()
1186
+ ]
1187
+ ).publish()
1188
+ return obj
@@ -0,0 +1,32 @@
1
+ # Generated by Django 3.2.9 on 2024-05-14 08:55
2
+
3
+ from django.db import migrations, models
4
+ import django.db.models.deletion
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ dependencies = [
10
+ ('contenttypes', '0002_remove_content_type_name'),
11
+ ('fleet', '0034_auto_20240418_0735'),
12
+ ]
13
+
14
+ operations = [
15
+ migrations.CreateModel(
16
+ name='InterfaceAddress',
17
+ fields=[
18
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
19
+ ('address_type', models.CharField(choices=[('i2c', 'I2C'), ('dali-gear', 'DALI Gear'), ('dali-group', 'DALI Gear Group')], db_index=True, max_length=100)),
20
+ ('address', models.JSONField(db_index=True)),
21
+ ('occupied_by_id', models.PositiveIntegerField(null=True)),
22
+ ('interface', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='addresses', to='fleet.interface')),
23
+ ('occupied_by_content_type', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')),
24
+ ],
25
+ options={
26
+ 'unique_together': {('interface', 'address_type', 'address')},
27
+ },
28
+ ),
29
+ migrations.DeleteModel(
30
+ name='I2CInterface',
31
+ ),
32
+ ]
simo/fleet/models.py CHANGED
@@ -181,13 +181,13 @@ class Colonel(DirtyFieldsMixin, models.Model):
181
181
  pin.occupied_by = component
182
182
  pin.save()
183
183
 
184
- for interface in self.i2c_interfaces.all():
185
- if interface.sda_pin:
186
- interface.sda_pin.occupied_by = interface
187
- interface.sda_pin.save()
188
- if interface.scl_pin:
189
- interface.scl_pin.occupied_by = interface
190
- interface.scl_pin.save()
184
+ for interface in self.interfaces.all():
185
+ if interface.pin_a:
186
+ interface.pin_a.occupied_by = interface
187
+ interface.pin_a.save()
188
+ if interface.pin_b:
189
+ interface.pin_b.occupied_by = interface
190
+ interface.pin_b.save()
191
191
 
192
192
 
193
193
  def move_to(self, other_colonel):
@@ -259,6 +259,10 @@ def after_colonel_save(sender, instance, created, *args, **kwargs):
259
259
  capacitive=data.get('capacitive'), adc=data.get('adc'),
260
260
  native=data.get('native'), note=data.get('note')
261
261
  )
262
+ fleet_gateway, new = Gateway.objects.get_or_create(
263
+ type='simo.fleet.gateways.FleetGatewayHandler'
264
+ )
265
+ fleet_gateway.run()
262
266
 
263
267
 
264
268
  @receiver(pre_delete, sender=Component)
@@ -266,81 +270,34 @@ def post_component_delete(sender, instance, *args, **kwargs):
266
270
  if not instance.controller_uid.startswith('simo.fleet'):
267
271
  return
268
272
 
269
- affected_colonels = list(Colonel.objects.filter(components=instance))
270
-
271
- def update_colonel():
272
- for colonel in affected_colonels:
273
- print("Rebuild occupied pins for :", colonel)
274
- colonel.rebuild_occupied_pins()
275
- colonel.update_config()
276
-
277
- transaction.on_commit(update_colonel)
278
-
279
-
280
- i2c_interface_no_choices = (
281
- (0, "0 - Main"), (1, "1 - Secondary"),
282
- (2, "2 - Software"), (3, "3 - Software")
283
- )
284
-
285
-
286
- class I2CInterface(models.Model):
287
- colonel = models.ForeignKey(
288
- Colonel, on_delete=models.CASCADE, related_name='i2c_interfaces'
289
- )
290
- name = models.CharField(max_length=50)
291
- no = models.IntegerField(
292
- default=0, choices=i2c_interface_no_choices
293
- )
294
- scl_pin = models.ForeignKey(
295
- ColonelPin, on_delete=models.CASCADE, limit_choices_to={
296
- 'native': True, 'output': True,
297
- },
298
- null=True, related_name='i2c_scl'
299
- )
300
- sda_pin = models.ForeignKey(
301
- ColonelPin, on_delete=models.CASCADE, limit_choices_to={
302
- 'native': True, 'output': True,
303
- },
304
- null=True, related_name='i2c_sda'
305
- )
306
- freq = models.IntegerField(
307
- default=100000, help_text="100000 - is a good middle point!"
308
- )
309
-
310
- objects = InterfacesManager()
273
+ from .controllers import DALIGearGroup
274
+ if instance.controller_uid == DALIGearGroup.uid:
275
+ for comp in Component.objects.filter(
276
+ id__in=instance.config.get('members', [])
277
+ ):
278
+ instance.controller._modify_member_group(
279
+ comp, instance.config.get('da', 0), remove=True
280
+ )
311
281
 
312
- class Meta:
313
- unique_together = 'colonel', 'no'
282
+ elif instance.controller.family == 'dali':
283
+ colonel = Colonel.objects.filter(id=instance.config['colonel']).first()
284
+ if colonel:
285
+ GatewayObjectCommand(
286
+ instance.gateway, colonel, id=instance.id,
287
+ command='call', method='destroy',
288
+ ).publish()
314
289
 
315
- def __str__(self):
316
- return self.name
290
+ else:
317
291
 
292
+ affected_colonels = list(Colonel.objects.filter(components=instance))
318
293
 
319
- @receiver(post_delete, sender=I2CInterface)
320
- def post_i2c_interface_delete(sender, instance, *args, **kwargs):
321
- with transaction.atomic():
322
- ct = ContentType.objects.get_for_model(instance)
323
- for pin in ColonelPin.objects.filter(
324
- occupied_by_content_type=ct,
325
- occupied_by_id=instance.id
326
- ):
327
- pin.occupied_by_content_type = None
328
- pin.occupied_by_content_id = None
329
- pin.save()
294
+ def update_colonel():
295
+ for colonel in affected_colonels:
296
+ print("Rebuild occupied pins for :", colonel)
297
+ colonel.rebuild_occupied_pins()
298
+ colonel.update_config()
330
299
 
331
- # In an event of colonel deletion these pin no longer exist
332
- # at this point, therefore this trhows irrelevant exceptions
333
- # that we want to fail silenty
334
- try:
335
- instance.scl_pin.occupied_by = instance
336
- instance.scl_pin.save()
337
- except ColonelPin.DoesNotExist:
338
- pass
339
- try:
340
- instance.sda_pin.occupied_by = instance
341
- instance.sda_pin.save()
342
- except ColonelPin.DoesNotExist:
343
- pass
300
+ transaction.on_commit(update_colonel)
344
301
 
345
302
 
346
303
  class Interface(models.Model):
@@ -384,6 +341,49 @@ class Interface(models.Model):
384
341
  return super().save(*args, **kwargs)
385
342
 
386
343
 
344
+ def broadcast_reset(self):
345
+ gw = Gateway.objects.filter(type=FleetGatewayHandler.uid).first()
346
+ if not gw:
347
+ return
348
+ GatewayObjectCommand(
349
+ gw, self.colonel, command='broadcast_reset',
350
+ data={'interface': self.no}
351
+ ).publish()
352
+
353
+
354
+ class InterfaceAddress(models.Model):
355
+ interface = models.ForeignKey(
356
+ Interface, related_name='addresses', on_delete=models.CASCADE
357
+ )
358
+ address_type = models.CharField(
359
+ db_index=True, max_length=100, choices=(
360
+ ('i2c', "I2C"),
361
+ ('dali-gear', "DALI Gear"),
362
+ ('dali-group', "DALI Gear Group")
363
+ )
364
+ )
365
+ address = models.JSONField(db_index=True)
366
+ occupied_by_content_type = models.ForeignKey(
367
+ ContentType, on_delete=models.CASCADE, null=True
368
+ )
369
+ occupied_by_id = models.PositiveIntegerField(null=True)
370
+ occupied_by = GenericForeignKey(
371
+ "occupied_by_content_type", "occupied_by_id"
372
+ )
373
+
374
+ class Meta:
375
+ unique_together = 'interface', 'address_type', 'address'
376
+
377
+ def __str__(self):
378
+ addr = self.address
379
+ if self.address_type == 'i2c':
380
+ try:
381
+ addr = hex(int(self.address))
382
+ except:
383
+ pass
384
+ return f"{self.get_address_type_display()}: {addr}"
385
+
386
+
387
387
  @receiver(post_save, sender=Interface)
388
388
  def post_interface_save(sender, instance, created, *args, **kwargs):
389
389
  if created:
@@ -399,6 +399,24 @@ def post_interface_save(sender, instance, created, *args, **kwargs):
399
399
  instance.pin_b.save()
400
400
  instance.save()
401
401
 
402
+ if instance.type == 'i2c':
403
+ for addr in range(128):
404
+ InterfaceAddress.objects.create(
405
+ interface=instance, address_type='i2c',
406
+ address=addr,
407
+ )
408
+ elif instance.type == 'dali':
409
+ for addr in range(64):
410
+ InterfaceAddress.objects.create(
411
+ interface=instance, address_type='dali-gear',
412
+ address=addr,
413
+ )
414
+ for addr in range(16):
415
+ InterfaceAddress.objects.create(
416
+ interface=instance, address_type='dali-group',
417
+ address=addr,
418
+ )
419
+
402
420
 
403
421
  @receiver(post_delete, sender=Interface)
404
422
  def post_interface_delete(sender, instance, *args, **kwargs):
@@ -408,8 +426,4 @@ def post_interface_delete(sender, instance, *args, **kwargs):
408
426
  occupied_by_content_type=ct,
409
427
  occupied_by_id=instance.id
410
428
  ):
411
- pin.occupied_by_content_type = None
412
- pin.occupied_by_content_id = None
413
- pin.save()
414
-
415
-
429
+ pin.occupied_by_content_type = None
simo/fleet/serializers.py CHANGED
@@ -22,7 +22,14 @@ class ColonelPinSerializer(serializers.ModelSerializer):
22
22
  read_only_fields = fields
23
23
 
24
24
  def get_occupied(self, obj):
25
- return bool(obj.occupied_by)
25
+ try:
26
+ return bool(obj.occupied_by)
27
+ except AttributeError:
28
+ # apparently the item type that this pin was occupied by
29
+ # was deleted from the codebase, so we quickly fix it here. :)
30
+ obj.occupied_by = None
31
+ obj.save()
32
+ return False
26
33
 
27
34
 
28
35
  class ColonelInterfaceSerializer(serializers.ModelSerializer):
@@ -378,7 +378,7 @@ class FleetConsumer(AsyncWebsocketConsumer):
378
378
  component.change_init_fingerprint = fingerprint
379
379
  component.controller._receive_from_device(
380
380
  data['val'], bool(data.get('alive')),
381
- data.get('battery_level')
381
+ data.get('battery_level'), data.get('error_msg')
382
382
  )
383
383
  await sync_to_async(
384
384
  receive_val, thread_sensitive=True
@@ -404,28 +404,16 @@ class FleetConsumer(AsyncWebsocketConsumer):
404
404
 
405
405
  elif 'discovery-result' in data:
406
406
  def process_discovery_result():
407
- # check if component is already created
408
- if data['discovery-result'] == 'success':
409
- comp = Component.objects.filter(
410
- meta__finalization_data__temp_id=data['result']['id']
411
- ).first()
412
- if comp:
413
- return comp
414
-
415
407
  self.gateway.refresh_from_db()
416
408
  try:
417
409
  self.gateway.process_discovery(data)
418
410
  except Exception as e:
419
411
  print(traceback.format_exc(), file=sys.stderr)
420
412
 
421
- finished_comp = await sync_to_async(
413
+ await sync_to_async(
422
414
  process_discovery_result, thread_sensitive=True
423
415
  )()
424
- if finished_comp:
425
- await self.send_data({
426
- 'command': 'finalize',
427
- 'data': finished_comp.meta['finalization_data']
428
- })
416
+
429
417
 
430
418
  elif bytes_data:
431
419
  if not self.colonel_logger:
Binary file
@@ -4,29 +4,30 @@ from django.db import migrations
4
4
 
5
5
 
6
6
  def create_objects(apps, schema_editor):
7
-
8
- PermissionsRole = apps.get_model("users", "PermissionsRole")
9
- User = apps.get_model("users", "User")
10
-
11
- admin_role, new = PermissionsRole.objects.get_or_create(
12
- is_superuser=True, name='Admin'
13
- )
14
- user_role, new = PermissionsRole.objects.get_or_create(
15
- name='User', can_manage_users=False, is_default=True
16
- )
17
- PermissionsRole.objects.get_or_create(
18
- name='Guest'
19
- )
20
- User.objects.get_or_create(
21
- email='system@simo.io', defaults={
22
- 'role': admin_role, 'name': "System"
23
- }
24
- )
25
- User.objects.get_or_create(
26
- email='device@simo.io', defaults={
27
- 'role': user_role, 'name': "Device"
28
- }
29
- )
7
+ pass
8
+ # legacy
9
+ # PermissionsRole = apps.get_model("users", "PermissionsRole")
10
+ # User = apps.get_model("users", "User")
11
+ #
12
+ # admin_role, new = PermissionsRole.objects.get_or_create(
13
+ # is_superuser=True, name='Admin'
14
+ # )
15
+ # user_role, new = PermissionsRole.objects.get_or_create(
16
+ # name='User', can_manage_users=False, is_default=True
17
+ # )
18
+ # PermissionsRole.objects.get_or_create(
19
+ # name='Guest'
20
+ # )
21
+ # User.objects.get_or_create(
22
+ # email='system@simo.io', defaults={
23
+ # 'role': admin_role, 'name': "System"
24
+ # }
25
+ # )
26
+ # User.objects.get_or_create(
27
+ # email='device@simo.io', defaults={
28
+ # 'role': user_role, 'name': "Device"
29
+ # }
30
+ #)
30
31
 
31
32
  def delete_objects(apps, schema_editor):
32
33
  pass
@@ -4,14 +4,15 @@ from django.db import migrations
4
4
 
5
5
 
6
6
  def forwards_func(apps, schema_editor):
7
-
8
- PermissionsRole = apps.get_model("users", "PermissionsRole")
9
-
10
- for role in PermissionsRole.objects.filter(instance=None):
11
- for iu in role.instanceuser_set.all():
12
- iu.user.is_god = True
13
- iu.user.save()
14
- role.delete()
7
+ pass
8
+ # legacy
9
+ # PermissionsRole = apps.get_model("users", "PermissionsRole")
10
+ #
11
+ # for role in PermissionsRole.objects.filter(instance=None):
12
+ # for iu in role.instanceuser_set.all():
13
+ # iu.user.is_god = True
14
+ # iu.user.save()
15
+ # role.delete()
15
16
 
16
17
  def reverse_func(apps, schema_editor):
17
18
  pass
simo/users/models.py CHANGED
@@ -1,19 +1,15 @@
1
1
  import datetime
2
2
  import requests
3
3
  import subprocess
4
- import sys
5
4
  from django.urls import reverse
6
5
  from django.utils.translation import gettext_lazy as _
7
6
  from django.db import models
8
- from django.db.models import Q
9
7
  from django.db import transaction
10
8
  from django.db.models.signals import post_save, post_delete, m2m_changed
11
9
  from django.dispatch import receiver
12
- from model_utils import FieldTracker
13
10
  from dirtyfields import DirtyFieldsMixin
14
11
  from django.contrib.gis.geos import Point
15
12
  from geopy.distance import distance
16
- from django.core.exceptions import ValidationError
17
13
  from django.contrib.auth.models import (
18
14
  AbstractBaseUser, PermissionsMixin, UserManager as DefaultUserManager
19
15
  )
@@ -481,11 +477,12 @@ def rebuild_mqtt_acls_on_create(sender, instance, created, **kwargs):
481
477
  def create_component_permissions_comp(sender, instance, created, **kwargs):
482
478
  if created:
483
479
  for role in PermissionsRole.objects.filter(
484
- Q(instance__isnull=True) | Q(instance=instance.zone.instance)
480
+ instance=instance.zone.instance
485
481
  ):
486
482
  ComponentPermission.objects.get_or_create(
487
483
  component=instance, role=role, defaults={
488
- 'read': role.is_superuser, 'write': role.is_superuser
484
+ 'read': role.is_superuser or role.is_owner,
485
+ 'write': role.is_superuser or role.is_owner
489
486
  }
490
487
  )
491
488
  rebuild_mqtt_acls.delay()
@@ -575,4 +572,6 @@ class InstanceInvitation(models.Model):
575
572
  return response
576
573
 
577
574
  def get_absolute_url(self):
578
- return reverse('accept_invitation', kwargs={'token': self.token})
575
+ return reverse('accept_invitation', kwargs={'token': self.token})
576
+
577
+
simo/users/utils.py CHANGED
@@ -1,11 +1,7 @@
1
1
  import sys
2
- import os
3
- import pwd
4
- import grp
5
2
  import traceback
6
3
  import subprocess
7
4
  from django.template.loader import render_to_string
8
- from django.conf import settings
9
5
 
10
6
 
11
7
  def get_system_user():
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: simo
3
- Version: 2.0.32
3
+ Version: 2.0.33
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