simo 2.0.21__py3-none-any.whl → 2.0.23__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
Binary file
Binary file
simo/core/admin.py CHANGED
@@ -269,7 +269,7 @@ class ComponentAdmin(admin.ModelAdmin):
269
269
  'alarm_category', 'arm_status'
270
270
  )
271
271
 
272
- search_fields = 'name',
272
+ search_fields = 'id', 'name', 'value', 'config', 'meta', 'notes'
273
273
  list_per_page = 100
274
274
  change_list_template = 'admin/component_change_list.html'
275
275
  inlines = ComponentPermissionInline,
simo/core/api.py CHANGED
@@ -5,7 +5,7 @@ import time
5
5
  from django.db.models import Q, Prefetch
6
6
  from django.utils.translation import gettext_lazy as _
7
7
  from django.utils import timezone
8
- from django.shortcuts import get_object_or_404
8
+ from django.http import HttpResponse, Http404
9
9
  from simo.core.utils.helpers import get_self_ip, search_queryset
10
10
  from rest_framework.pagination import PageNumberPagination
11
11
  from rest_framework import viewsets
@@ -14,7 +14,6 @@ from rest_framework.decorators import action
14
14
  from rest_framework.response import Response as RESTResponse
15
15
  from rest_framework.exceptions import ValidationError as APIValidationError
16
16
  from simo.core.utils.config_values import ConfigException
17
- from simo.users.middleware import introduce as introduce_user
18
17
  from .models import (
19
18
  Instance, Category, Zone, Component, Icon, ComponentHistory,
20
19
  HistoryAggregate, Gateway
@@ -23,7 +22,9 @@ from .serializers import (
23
22
  IconSerializer, CategorySerializer, ZoneSerializer,
24
23
  ComponentSerializer, ComponentHistorySerializer
25
24
  )
26
- from .permissions import IsInstanceSuperuser, InstanceSuperuserCanEdit
25
+ from .permissions import (
26
+ IsInstanceSuperuser, InstanceSuperuserCanEdit, ComponentPermission
27
+ )
27
28
 
28
29
 
29
30
  class InstanceMixin:
@@ -34,10 +35,10 @@ class InstanceMixin:
34
35
  slug=self.request.resolver_match.kwargs.get('instance_slug')
35
36
  )
36
37
  except Instance.DoesNotExist:
37
- raise APIValidationError(
38
+ return HttpResponse(
38
39
  f"Instance {self.request.resolver_match.kwargs.get('instance_slug')} "
39
40
  "is not found on this SIMO.io hub!",
40
- code=400
41
+ status=400
41
42
  )
42
43
  return super().dispatch(request, *args, **kwargs)
43
44
 
@@ -165,6 +166,7 @@ class ComponentViewSet(InstanceMixin, viewsets.ModelViewSet):
165
166
 
166
167
  def get_permissions(self):
167
168
  permissions = super().get_permissions()
169
+ permissions.append(ComponentPermission())
168
170
  permissions.append(InstanceSuperuserCanEdit())
169
171
  return permissions
170
172
 
@@ -210,7 +212,6 @@ class ComponentViewSet(InstanceMixin, viewsets.ModelViewSet):
210
212
  @action(detail=True, methods=['post'])
211
213
  def subcomponent(self, request, pk=None, *args, **kwargs):
212
214
  component = self.get_object()
213
- self.check_object_permissions(request, component)
214
215
  json_data = request.data
215
216
  subcomponent_id = json_data.pop('id', -1)
216
217
  try:
@@ -228,37 +229,16 @@ class ComponentViewSet(InstanceMixin, viewsets.ModelViewSet):
228
229
  self.check_object_permissions(self.request, subcomponent)
229
230
  return self.perform_controller_method(json_data, subcomponent)
230
231
 
231
-
232
- def check_object_permissions(self, request, component):
233
- super().check_object_permissions(request, component)
234
-
235
- if not component.controller:
236
- raise APIValidationError(
237
- _('Component has no controller assigned.'),
238
- code=400
239
- )
240
- if request.user.is_master:
241
- return
242
- user_role = request.user.get_role(self.instance)
243
- if user_role.is_superuser:
244
- return
245
- if not user_role.component_permissions.filter(
246
- write=True, component=component
247
- ).count():
248
- raise APIValidationError(
249
- _('You do not have permission to write to this component.'),
250
- code=403
251
- )
252
-
253
232
  @action(detail=True, methods=['post'])
254
233
  def controller(self, request, pk=None, *args, **kwargs):
255
234
  component = self.get_object()
256
- self.check_object_permissions(self.request, component)
257
235
  return self.perform_controller_method(request.data, component)
258
236
 
259
237
  @action(detail=False, methods=['post'])
260
238
  def control(self, request, *args, **kwargs):
261
- component = get_object_or_404(Component, id=request.data.pop('id', 0))
239
+ component = self.get_queryset().filter(id=request.data.pop('id', 0))
240
+ if not component:
241
+ raise Http404()
262
242
  self.check_object_permissions(self.request, component)
263
243
  return self.perform_controller_method(request.data, component)
264
244
 
simo/core/api_meta.py CHANGED
@@ -4,7 +4,8 @@ from rest_framework import serializers
4
4
  from rest_framework.metadata import SimpleMetadata
5
5
  from rest_framework.utils.field_mapping import ClassLookupDict
6
6
  from .serializers import (
7
- HiddenSerializerField, ComponentManyToManyRelatedField
7
+ HiddenSerializerField, ComponentManyToManyRelatedField,
8
+ TextAreaSerializerField
8
9
  )
9
10
 
10
11
 
@@ -36,6 +37,7 @@ class SIMOAPIMetadata(SimpleMetadata):
36
37
  serializers.ManyRelatedField: 'many related objects',
37
38
  ComponentManyToManyRelatedField: 'many related objects',
38
39
  HiddenSerializerField: 'hidden',
40
+ TextAreaSerializerField: 'textarea',
39
41
  })
40
42
 
41
43
  def get_field_info(self, field):
simo/core/forms.py CHANGED
@@ -34,7 +34,6 @@ class HiddenField(forms.CharField):
34
34
  super().__init__(widget=forms.HiddenInput())
35
35
 
36
36
 
37
-
38
37
  class HubConfigForm(forms.Form):
39
38
  name = forms.CharField(
40
39
  label=_("Hub Name"), required=True,
@@ -300,7 +299,6 @@ class ComponentAdminForm(forms.ModelForm):
300
299
  'category': autocomplete.ModelSelect2(
301
300
  url='autocomplete-category', attrs={'data-html': True}
302
301
  ),
303
- #'instance_methods': PythonCode
304
302
  }
305
303
 
306
304
  def __init__(self, *args, **kwargs):
@@ -334,6 +332,7 @@ class ComponentAdminForm(forms.ModelForm):
334
332
  #'instance_methods',
335
333
  'value_units',
336
334
  'alarm_category', 'arm_status',
335
+ 'notes'
337
336
  )
338
337
  base_fields = ['id', 'gateway', 'base_type', 'name']
339
338
  if cls.has_icon:
@@ -348,7 +347,7 @@ class ComponentAdminForm(forms.ModelForm):
348
347
 
349
348
  base_fields.append('show_in_app')
350
349
  base_fields.append('control')
351
- #base_fields.append('instance_methods')
350
+ base_fields.append('notes')
352
351
 
353
352
  fieldsets = [
354
353
  (_("Base settings"), {'fields': base_fields}),
@@ -0,0 +1,28 @@
1
+ # Generated by Django 3.2.9 on 2024-04-29 12:31
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('core', '0030_alter_instance_timezone'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AddField(
14
+ model_name='component',
15
+ name='notes',
16
+ field=models.TextField(blank=True, null=True),
17
+ ),
18
+ migrations.AlterField(
19
+ model_name='category',
20
+ name='header_image',
21
+ field=models.ImageField(blank=True, help_text='Will be cropped down to: 830x430', null=True, upload_to='categories'),
22
+ ),
23
+ migrations.AlterField(
24
+ model_name='instance',
25
+ name='cover_image',
26
+ field=models.ImageField(blank=True, null=True, upload_to='hub_covers'),
27
+ ),
28
+ ]
simo/core/models.py CHANGED
@@ -163,7 +163,7 @@ class Category(DirtyFieldsMixin, models.Model, SimoAdminMixin):
163
163
  help_text=_("All components automatically belongs to this category")
164
164
  )
165
165
  order = models.PositiveIntegerField(
166
- default=0, blank=False, null=False, db_index=True
166
+ blank=False, null=False, db_index=True
167
167
  )
168
168
  objects = CategoriesManager()
169
169
 
@@ -175,8 +175,13 @@ class Category(DirtyFieldsMixin, models.Model, SimoAdminMixin):
175
175
  def __str__(self):
176
176
  return self.name
177
177
 
178
-
179
178
  def save(self, *args, **kwargs):
179
+ if self.order is None:
180
+ last_cat = Category.objects.filter(instance=self.instance).last()
181
+ if last_cat:
182
+ self.order = last_cat.order + 1
183
+ else:
184
+ self.order = 0
180
185
  dirty_fields = self.get_dirty_fields()
181
186
  if 'all' in dirty_fields:
182
187
  if self.all:
@@ -361,6 +366,8 @@ class Component(DirtyFieldsMixin, models.Model, SimoAdminMixin, OnChangeMixin):
361
366
 
362
367
  show_in_app = models.BooleanField(default=True, db_index=True)
363
368
 
369
+ notes = models.TextField(null=True, blank=True)
370
+
364
371
  # Feature for global superusers.
365
372
  # Good candidate for reworking in to something more API oriented
366
373
  # instead of injecting the code directly.
simo/core/permissions.py CHANGED
@@ -1,11 +1,9 @@
1
- from rest_framework.permissions import BasePermission, SAFE_METHODS
1
+ from rest_framework.permissions import BasePermission, SAFE_METHODS, IsAuthenticated
2
2
  from .models import Instance, Category, Zone
3
3
 
4
4
 
5
5
  class InstancePermission(BasePermission):
6
- """
7
- Allows access only to user instances
8
- """
6
+ message = "You have no role in this SIMO.io instance."
9
7
 
10
8
  def has_permission(self, request, view):
11
9
  if not request.user.is_active:
@@ -24,6 +22,7 @@ class InstancePermission(BasePermission):
24
22
 
25
23
 
26
24
  class IsInstanceSuperuser(BasePermission):
25
+ message = "Only superusers are allowed to do this."
27
26
 
28
27
  def has_permission(self, request, view):
29
28
  if request.user.is_master:
@@ -33,6 +32,7 @@ class IsInstanceSuperuser(BasePermission):
33
32
 
34
33
 
35
34
  class InstanceSuperuserCanEdit(BasePermission):
35
+ message = "Only superusers are allowed to perform this action."
36
36
 
37
37
  def has_object_permission(self, request, view, obj):
38
38
 
@@ -47,3 +47,28 @@ class InstanceSuperuserCanEdit(BasePermission):
47
47
  if user_role.is_superuser:
48
48
  return True
49
49
  return request.method in SAFE_METHODS
50
+
51
+
52
+ class ComponentPermission(BasePermission):
53
+ message = "You do not have permission to do this on this component."
54
+
55
+ # TODO: clean this up once the app is tested and running 100% correctly for at least 6 months.
56
+ def has_object_permission(self, request, view, obj):
57
+ print(f"Check permission of {request.user} on {obj}")
58
+ if request.method in SAFE_METHODS:
59
+ print("THIS IS SAFE METHOD!")
60
+ return True
61
+ if request.user.is_master:
62
+ print("USER IS MASTER!")
63
+ return True
64
+ user_role = request.user.get_role(view.instance)
65
+ if user_role.is_superuser:
66
+ print("USER IS SUPERUSER!")
67
+ return True
68
+ if request.method == 'POST' and user_role.component_permissions.filter(
69
+ write=True, component=obj
70
+ ).count():
71
+ print("USER HAS RIGHT TO DO THIS!")
72
+ return True
73
+ print("USER IS NOT ALLOWED TO DO THIS!")
74
+ return False
simo/core/serializers.py CHANGED
@@ -93,6 +93,10 @@ class HiddenSerializerField(serializers.CharField):
93
93
  pass
94
94
 
95
95
 
96
+ class TextAreaSerializerField(serializers.CharField):
97
+ pass
98
+
99
+
96
100
  class ComponentFormsetField(FormSerializer):
97
101
 
98
102
  class Meta:
@@ -130,6 +134,7 @@ class ComponentFormsetField(FormSerializer):
130
134
  continue
131
135
 
132
136
  cls_type = form_field.__class__
137
+
133
138
  try:
134
139
  serializer_field_class = field_mapping[cls_type]
135
140
  except KeyError:
@@ -238,6 +243,7 @@ class ComponentSerializer(FormSerializer):
238
243
  meta = ObjectSerializerMethodField()
239
244
  arm_status = ObjectSerializerMethodField()
240
245
  battery_level = ObjectSerializerMethodField()
246
+ controller_methods = serializers.SerializerMethodField()
241
247
 
242
248
  class Meta:
243
249
  form = ComponentAdminForm
@@ -280,22 +286,25 @@ class ComponentSerializer(FormSerializer):
280
286
  form_field = form[field_name]
281
287
 
282
288
  cls = form_field.field.__class__
283
- try:
284
- serializer_field_class = field_mapping[cls]
285
- except KeyError:
286
- cls = form_field.field.__class__.__bases__[0]
289
+ if field_name == 'notes':
290
+ serializer_field_class = TextAreaSerializerField
291
+ else:
287
292
  try:
288
293
  serializer_field_class = field_mapping[cls]
289
294
  except KeyError:
290
- raise TypeError(
291
- "{field} is not mapped to a serializer field. "
292
- "Please add {field} to {serializer}.Meta.field_mapping. "
293
- "Currently mapped fields: {mapped}".format(
294
- field=form_field.field.__class__.__name__,
295
- serializer=self.__class__.__name__,
296
- mapped=', '.join(sorted([i.__name__ for i in field_mapping.keys()]))
295
+ cls = form_field.field.__class__.__bases__[0]
296
+ try:
297
+ serializer_field_class = field_mapping[cls]
298
+ except KeyError:
299
+ raise TypeError(
300
+ "{field} is not mapped to a serializer field. "
301
+ "Please add {field} to {serializer}.Meta.field_mapping. "
302
+ "Currently mapped fields: {mapped}".format(
303
+ field=form_field.field.__class__.__name__,
304
+ serializer=self.__class__.__name__,
305
+ mapped=', '.join(sorted([i.__name__ for i in field_mapping.keys()]))
306
+ )
297
307
  )
298
- )
299
308
 
300
309
  ret[field_name] = self._get_field(
301
310
  form_field.field, serializer_field_class
Binary file
simo/fleet/forms.py CHANGED
@@ -573,12 +573,6 @@ class ColonelPWMOutputConfigForm(ColonelComponentForm):
573
573
  ]
574
574
  )
575
575
  )
576
- frequency = forms.IntegerField(
577
- min_value=30, max_value=100000, required=True, initial=3000,
578
- help_text="PWM signal frequency in Hz. Works only with GPIO ports."
579
- "3000 Hz offers great performance in most use cases."
580
-
581
- )
582
576
  min = forms.FloatField(
583
577
  required=True, initial=0,
584
578
  help_text="Minimum component value"
@@ -723,9 +717,9 @@ class ColonelRGBLightConfigForm(ColonelComponentForm):
723
717
  return custom_timing
724
718
  custom_timing = custom_timing.strip().\
725
719
  strip('(').strip('[').rstrip(')').rstrip(']').split(',')
726
- if len(custom_timing.split(',')) != 4:
720
+ if len(custom_timing) != 4:
727
721
  raise forms.ValidationError("Tuple of 4 integers please.")
728
- for t in custom_timing.split(','):
722
+ for t in custom_timing:
729
723
  try:
730
724
  t = int(t)
731
725
  except:
@@ -734,7 +728,7 @@ class ColonelRGBLightConfigForm(ColonelComponentForm):
734
728
  raise forms.ValidationError(f"Intervals must be greater than 0.")
735
729
  if t > 100000:
736
730
  raise forms.ValidationError(f"{t} seems way to much!")
737
- return f"({custom_timing})"
731
+ return f"({','.join(custom_timing)})"
738
732
 
739
733
  def clean(self):
740
734
  super().clean()
simo/fleet/models.py CHANGED
@@ -327,10 +327,20 @@ def post_i2c_interface_delete(sender, instance, *args, **kwargs):
327
327
  pin.occupied_by_content_type = None
328
328
  pin.occupied_by_content_id = None
329
329
  pin.save()
330
- instance.scl_pin.occupied_by = instance
331
- instance.scl_pin.save()
332
- instance.sda_pin.occupied_by = instance
333
- instance.sda_pin.save()
330
+
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
334
344
 
335
345
 
336
346
  class Interface(models.Model):
simo/settings.py CHANGED
@@ -176,7 +176,6 @@ REST_FRAMEWORK = {
176
176
  'simo.core.api_auth.IsAuthenticated',
177
177
  ],
178
178
  'DEFAULT_PERMISSION_CLASSES': [
179
- 'rest_framework.permissions.IsAuthenticated',
180
179
  'simo.users.permissions.IsActivePermission',
181
180
  'simo.core.permissions.InstancePermission'
182
181
  ],
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: simo
3
- Version: 2.0.21
3
+ Version: 2.0.23
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
@@ -6,7 +6,7 @@ simo/cli.py,sha256=kB1dhZ30Pnq7mDawWGbX5WnCuoZ6qNMcnWH-c8XxcaU,2233
6
6
  simo/conf.py,sha256=H2BhXAV8MEDVXF8AbkaLSfR4ULd-9_bS4bnhE5sE5fg,112
7
7
  simo/on_http_start.py,sha256=PJQlKYeZbtGCxRjDV6zcCqyA5Ns9d5NND30Tb6vIav4,2358
8
8
  simo/scripting.py,sha256=PVIkGsiMDWj4CNTbOM3rq7pJ6ruavuns-ZMU7VudLa4,923
9
- simo/settings.py,sha256=hFXlZYuMSxlEVHuU19uJ0t4mK--70Zf3GOujVIUkurA,6894
9
+ simo/settings.py,sha256=yezM7QleJv73IFWMsPyGxqoI_c3JV21R1RsDcgweIfU,6840
10
10
  simo/urls.py,sha256=PgKGCZON3SAL3Hh9vVDWVJ9zDQO5ZPsRQLz_s5GWSV0,2328
11
11
  simo/wsgi.py,sha256=ci7BK1zCuqTwCUQi29su7xKeGEJHk0N3Oc8jGJRO6EY,165
12
12
  simo/__pycache__/__init__.cpython-38.pyc,sha256=j81de0BqHMr6bs0C7cuYrXl7HwtK_vv8hDEtAdSwDJc,153
@@ -14,7 +14,7 @@ simo/__pycache__/asgi.cpython-38.pyc,sha256=OemZ2InNbZCiJtD3MWSO8IiDSqp-LUyTLFjM
14
14
  simo/__pycache__/celeryc.cpython-38.pyc,sha256=eSRoaKwfYlxVaxAiwqpQ2ndEcx7W-VpZtbxRFSV8UYg,1653
15
15
  simo/__pycache__/conf.cpython-38.pyc,sha256=MYP2yk3ULxiYwZsZR6tCLjKnU-z03A3avzQzIn66y3k,273
16
16
  simo/__pycache__/on_http_start.cpython-38.pyc,sha256=cjgntuqsmandjyNtJoqch0LpxA-BNF3UR8vUxufYn9g,2067
17
- simo/__pycache__/settings.cpython-38.pyc,sha256=rWI5C5rSlAaDif8BtxwQ6vEww1YaU9xwR753YF8Y4xI,6132
17
+ simo/__pycache__/settings.cpython-38.pyc,sha256=m9O2hfu1sVkYxi9-MzM2D4DzOHL7ea9LOt1AJlV4LdI,6084
18
18
  simo/__pycache__/urls.cpython-38.pyc,sha256=sqfstQthcjXtv31Tad0RAlWWI2A0HH6HN0fW0VmmWQw,2050
19
19
  simo/__pycache__/wsgi.cpython-38.pyc,sha256=Wt9kKkH2Sg5LRL4NrVQQDYPIoDyTvnXwm6jZjnG3kIc,322
20
20
  simo/_hub_template/hub/asgi.py,sha256=ElN_fdeSkf0Ysa7pS9rJVmZ1HmLhFxb8jFaMLqe1220,126
@@ -25,10 +25,10 @@ simo/_hub_template/hub/settings.py,sha256=4QhvhbtLRxHvAntwqG_qeAAtpDUqKvN4jzw9u3
25
25
  simo/_hub_template/hub/supervisor.conf,sha256=IY3fdK0fDD2eAothB0n54xhjQj8LYoXIR96-Adda5Z8,1353
26
26
  simo/_hub_template/hub/urls.py,sha256=Ydm-1BkYAzWeEF-MKSDIFf-7aE4qNLPm48-SA51XgJQ,25
27
27
  simo/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
- simo/core/admin.py,sha256=5QFmf7ZOqkSEba2Fo-UfJp5dVe-vhKUtOUlAnfQGhno,17634
29
- simo/core/api.py,sha256=H4KJBiU377zEj8dJGSnBUKgyeyA2512WmSh9I0nH4WM,23760
28
+ simo/core/admin.py,sha256=cPOC2x45Gjf0T8MKtmE2wJZk7DAuHMsvvy5Nk3YUIG8,17675
29
+ simo/core/api.py,sha256=bAX-DXG5aJr3VvjqGKGtjVzel0E7Cp3l5tkA3uLhnFw,22969
30
30
  simo/core/api_auth.py,sha256=_3hG4e1eLKrcRnSAOB_xTL6cwtOJ2_7JS7GZU_iqTgA,1251
31
- simo/core/api_meta.py,sha256=dZkz7z-7GaMPVAsfQxOYCvpYaMPx_v2zynbY6JM8oD8,3477
31
+ simo/core/api_meta.py,sha256=ySmmhtVrWatI3yqnYPuP5ipapmJfyfEbl32w-7_W5O4,3551
32
32
  simo/core/app_widgets.py,sha256=EEQOto3fGR0syDqpJE38tQrx8DoTTyg26nF5kYzHY38,2018
33
33
  simo/core/auto_urls.py,sha256=0gu-IL7PHobrmKW6ksffiOkAYu-aIorykWdxRNtwGYo,1194
34
34
  simo/core/autocomplete_views.py,sha256=JT5LA2_Wtr60XYSAIqaXFKFYPjrmkEf6yunXD9y2zco,4022
@@ -38,15 +38,15 @@ simo/core/controllers.py,sha256=SHqdSRhF7G-hmRtcWEODkUs-b17y6gEHDdZM3V01uVs,2697
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=iI3tiHMbjSrjldH8W8OYh8qJyT8QTVtfYczTPINY1ds,22502
41
+ simo/core/forms.py,sha256=FnnK4pHZ8EaMvubN0NQbz2SdTTgMXvGM-heklzN4RZo,22465
42
42
  simo/core/gateways.py,sha256=s_c2W0v2_te89i6LS4Nj7F2wn9UwjZXPT7pfy6SToVo,3714
43
43
  simo/core/loggers.py,sha256=EBdq23gTQScVfQVH-xeP90-wII2DQFDjoROAW6ggUP4,1645
44
44
  simo/core/managers.py,sha256=WoQ4OX3akIvoroSYji-nLVqXBSJzCiC1u_IiWkKbKmA,2413
45
45
  simo/core/middleware.py,sha256=64PYjnyRnYf4sgMvPfR0oQqf9UEtxUwnhJe3RV6z_HI,2040
46
- simo/core/models.py,sha256=dPLPju4xxc46x9JciOTC65SFSPogboXyNxjwrewXB1Y,19400
47
- simo/core/permissions.py,sha256=UmFjGPDWtAUbaWxJsWORb2q6BREHqndv9mkSIpnmdLk,1379
46
+ simo/core/models.py,sha256=zqbPDYLHOsavrBzw5uOmUgcWBnBbjGLvezgETX-pD_E,19672
47
+ simo/core/permissions.py,sha256=b2qO_kNaCgWcv2VJbOp6AUXOc_cup7fOT3Up16rn77M,2495
48
48
  simo/core/routing.py,sha256=X1_IHxyA-_Q7hw1udDoviVP4_FSBDl8GYETTC2zWTbY,499
49
- simo/core/serializers.py,sha256=HMSMOA5IcIYDIVipvuozf6bymJ-drvABPjEs-4lPjsI,17251
49
+ simo/core/serializers.py,sha256=kmja52o-BUOcUTX2ZsKWixvKRZSXB7lGe866Q1ajlmo,17563
50
50
  simo/core/signal_receivers.py,sha256=EZ8NSYZxUgSaLS16YZdK7T__l8dl0joMRllOxx5PUt4,2809
51
51
  simo/core/socket_consumers.py,sha256=n7VE2Fvqt4iEAYLTRbTPOcI-7tszMAADu7gimBxB-Fg,9635
52
52
  simo/core/storage.py,sha256=YlxmdRs-zhShWtFKgpJ0qp2NDBuIkJGYC1OJzqkbttQ,572
@@ -56,10 +56,10 @@ simo/core/types.py,sha256=WJEq48mIbFi_5Alt4wxWMGXxNxUTXqfQU5koH7wqHHI,1108
56
56
  simo/core/views.py,sha256=hlAKpAbCbqI3a-uL5tDp532T2oLFiF0MBzKUJ_SNzo0,5833
57
57
  simo/core/widgets.py,sha256=J9e06C6I22F6xKic3VMgG7WeX07glAcl-4bF2Mg180A,2827
58
58
  simo/core/__pycache__/__init__.cpython-38.pyc,sha256=y0IW37wBUIGa3Eh_ZG28pRqHKoLiPyTgUX2OnbkEPlc,158
59
- simo/core/__pycache__/admin.cpython-38.pyc,sha256=D9Hu5xMkclP6xFjEOLB-mBOe0ZMuCF10uilsROKkvmM,13446
60
- simo/core/__pycache__/api.cpython-38.pyc,sha256=kH2ORVfI8xtwe7M1wdOEtaqJR_SqKR8V1wrCdhazVhc,18883
59
+ simo/core/__pycache__/admin.cpython-38.pyc,sha256=EqwHQhU8qQrjfSChyCZXN6J-1Eira9alYIqR45-3OnE,13475
60
+ simo/core/__pycache__/api.cpython-38.pyc,sha256=l3BUnLGBsM-FzqdaKi__1R9Z5DJnhgJrkmXQ2XQetT0,18372
61
61
  simo/core/__pycache__/api_auth.cpython-38.pyc,sha256=5UTBr3rDMERAfc0OuOVDwGeQkt6Q7GLBtZJAMBse1sg,1712
62
- simo/core/__pycache__/api_meta.cpython-38.pyc,sha256=7dDi_Aay7T4eSNYmEItMlb7yU91-5_pzEVg8GXXf4Qc,2783
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
64
64
  simo/core/__pycache__/auto_urls.cpython-38.pyc,sha256=SVl4fF0-yiq7e9gt08jIM6_rL4JYcR0cNHzR9jCEi1M,931
65
65
  simo/core/__pycache__/autocomplete_views.cpython-38.pyc,sha256=hJ6JILI1LqrAtpQMvxnLvljGdW1v1gpvBsD79vFkZ58,3972
@@ -69,15 +69,15 @@ simo/core/__pycache__/controllers.cpython-38.pyc,sha256=vGRcRD7VPUdpP2WMtn2IIrnk
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=iuxu0J6KHGL2TJl2nPIrVEkSFurM9N95kHFI-OAfT34,19047
72
+ simo/core/__pycache__/forms.cpython-38.pyc,sha256=U5pV_JyfoGwymn3tsws8FR2GrxihjKFa5dmtdzijexA,19071
73
73
  simo/core/__pycache__/gateways.cpython-38.pyc,sha256=XBiwMfBkjoQ2re6jvADJOwK0_0Aav-crzie9qtfqT9U,4599
74
74
  simo/core/__pycache__/loggers.cpython-38.pyc,sha256=Z-cdQnC6XlIonPV4Sl4E52tP4NMEdPAiHK0cFaIL7I8,1623
75
75
  simo/core/__pycache__/managers.cpython-38.pyc,sha256=5vstOMfm997CZBBkaSiaS7EojhLTWZlbeA_EQ8u-yfg,2554
76
76
  simo/core/__pycache__/middleware.cpython-38.pyc,sha256=bGOFJNEhJeLbpsZp8LYn1VA3paLF5HULHQ6IFKa7Juc,2022
77
- simo/core/__pycache__/models.cpython-38.pyc,sha256=J3VZfFaU-aMkptCoviPvAJzJ8zBptGHmt504zjf2TCI,17006
78
- simo/core/__pycache__/permissions.cpython-38.pyc,sha256=uygjPbfRQiEzyo5-McCxsuMDJLbDYO_TLu55U7bJbR0,1809
77
+ simo/core/__pycache__/models.cpython-38.pyc,sha256=meQeK2mVoSDP9cTMSZDW-Ofi53QzwjfHSUlAGlnhyfs,17127
78
+ simo/core/__pycache__/permissions.cpython-38.pyc,sha256=7iz0o_jIEhDM2FQd-P-3Y6sm4GQ0MRy4a5biBNkngOA,2680
79
79
  simo/core/__pycache__/routing.cpython-38.pyc,sha256=3T3FPJ8Cn99xZCGvMyg2xjl7al-Shm9CelbSpkJtNP8,599
80
- simo/core/__pycache__/serializers.cpython-38.pyc,sha256=YkAXdCwfPEysAQ5Nin9B48cZ5pK_2WZwyR5ZEwfJrC4,16666
80
+ simo/core/__pycache__/serializers.cpython-38.pyc,sha256=x-obnrdPnoYRhriP_51_Jy_0saCsasWdFQSH1kalVqo,16869
81
81
  simo/core/__pycache__/signal_receivers.cpython-38.pyc,sha256=sgjH_wv-1U99auH5uHb3or0qettPeHAlsz8P7B03ajY,2430
82
82
  simo/core/__pycache__/socket_consumers.cpython-38.pyc,sha256=NJUr7nRyHFvmAumxxWpsod5wzVVZM99rCEuJs1utHA4,8432
83
83
  simo/core/__pycache__/storage.cpython-38.pyc,sha256=BTkYH8QQyjqI0WOtJC8fHNtgu0YA1vjqZclXjC2vCVI,1116
@@ -172,6 +172,7 @@ simo/core/migrations/0027_remove_component_tags.py,sha256=V0o_lgKiVU3HfG5V8MTO-j
172
172
  simo/core/migrations/0028_rename_subcomponents_component_slaves.py,sha256=ioQcfO2_IIfPc_BZzETmSodsYstzygR9U0ULnhET6ac,373
173
173
  simo/core/migrations/0029_auto_20240229_1331.py,sha256=BYXPNwjXApAx7mxE5li3QssfksWTsSjDf_VPQ8iGV8c,1140
174
174
  simo/core/migrations/0030_alter_instance_timezone.py,sha256=XZuYr2eD9MvE21Jxfdat8RC3sbc7PaGUfNPaqqxBNzE,23248
175
+ simo/core/migrations/0031_auto_20240429_1231.py,sha256=kskD8dylxqg-l-ZQgxl6ZdZd7iNcJ52rOGPJFa9s-wk,829
175
176
  simo/core/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
176
177
  simo/core/migrations/__pycache__/0001_initial.cpython-38.pyc,sha256=w6GiBXVxWj30Bg4Sn_pFVeA041d-pCrkaq8mR3KuF70,5381
177
178
  simo/core/migrations/__pycache__/0002_load_icons.cpython-38.pyc,sha256=Nb9RrPjVYo_RpZ5PmzoaEIWGCeVt4kicpmGiKlBrpIw,2123
@@ -203,6 +204,7 @@ simo/core/migrations/__pycache__/0027_remove_component_tags.cpython-38.pyc,sha25
203
204
  simo/core/migrations/__pycache__/0028_rename_subcomponents_component_slaves.cpython-38.pyc,sha256=PSiMp1yDzZ36Ei36U2tcIlvyzGgB5RuvGImrJ__7Q5s,593
204
205
  simo/core/migrations/__pycache__/0029_auto_20240229_1331.cpython-38.pyc,sha256=nO6lBU-1SxMQv67jQs7cGgNnot722UVL9pj3UE14I1k,1086
205
206
  simo/core/migrations/__pycache__/0030_alter_instance_timezone.cpython-38.pyc,sha256=paRgdijg5o8QzluT4GjSEQIMk6UAF7XmSwjqknXPE_4,16333
207
+ simo/core/migrations/__pycache__/0031_auto_20240429_1231.cpython-38.pyc,sha256=Kl76gU1VUyTWMz9RIZUrbnb_xeteWLDSRfs_GtNCVow,863
206
208
  simo/core/migrations/__pycache__/__init__.cpython-38.pyc,sha256=VZmDQ57BTcebuM0KMhjiTOabgWZCBxQmSJzWZos9SO8,169
207
209
  simo/core/static/ansi_styles.css,sha256=4ieJGrjZPKyPSago9FdB_gflHoGE1vxCHi8qVn5tY-Y,37352
208
210
  simo/core/static/admin/Img/plus.svg,sha256=2NpSFPWqGIjpAQGFI7LDQHPKagEhYkJiJX95ufCoZaI,741
@@ -10169,10 +10171,10 @@ simo/fleet/auto_urls.py,sha256=X04oKJWA48wFW5iXg3PPROY2KDdHn_a99orQSE28QC4,518
10169
10171
  simo/fleet/base_types.py,sha256=wL9RVkHr0gA7HI1wZq0pruGEIgvQqpfnCL4cC3ywsvw,102
10170
10172
  simo/fleet/ble.py,sha256=eHA_9ABjbmH1vUVCv9hiPXQL2GZZSEVwfO0xyI1S0nI,1081
10171
10173
  simo/fleet/controllers.py,sha256=rTxRFf-LKWAZxzixrsLZHHm51BmMx9a1PLdgf6inlNM,20533
10172
- simo/fleet/forms.py,sha256=ucH9mkwHDA8iUaxVP5W9O8tyoKUb7N2V0KCVSD-oCWI,38237
10174
+ simo/fleet/forms.py,sha256=SOz0CnaRJ1jOOJLzvL2kOfacuaE7nOVlc6NtScSaR1Y,37965
10173
10175
  simo/fleet/gateways.py,sha256=KV5i5fxXIrlK-k6zyEkk83x11GJt-ELQ0npb4Ac83cM,3693
10174
10176
  simo/fleet/managers.py,sha256=XOpDOA9L-f_550TNSyXnJbun2EmtGz1TenVTMlUSb8E,807
10175
- simo/fleet/models.py,sha256=1Wb9xPc61OtoiYu9aS4KezchPGZTC4CDxa2dqVhltqM,14161
10177
+ simo/fleet/models.py,sha256=ve-97F1cwGt-AmwfSJK0d-57pP3NyZpeu0XlHu2oK28,14494
10176
10178
  simo/fleet/routing.py,sha256=cofGsVWXMfPDwsJ6HM88xxtRxHwERhJ48Xyxc8mxg5o,149
10177
10179
  simo/fleet/serializers.py,sha256=LgSTnSI_sUhFXmW9-669keM6gmaSI_teaqMHEab4aL4,1972
10178
10180
  simo/fleet/socket_consumers.py,sha256=Z-MooNN2HQccdhkynADJks5slbK9mGsnEpMLuA51H3I,18534
@@ -10186,10 +10188,10 @@ simo/fleet/__pycache__/auto_urls.cpython-38.pyc,sha256=SqyTuaz_kEBvx-bL46SclsZEE
10186
10188
  simo/fleet/__pycache__/base_types.cpython-38.pyc,sha256=deyPwjpT6xZiFxBGFnj5b7R-lbdOTh2krgpJhrcGVhc,274
10187
10189
  simo/fleet/__pycache__/ble.cpython-38.pyc,sha256=Nrof9w7cm4OlpFWHeVnmvvanh2_oF9oQ3TknJiV93-0,1267
10188
10190
  simo/fleet/__pycache__/controllers.cpython-38.pyc,sha256=l9bz18Qp33C12TJOKPSn9vIXnlBKnBusODNk7Fg64qA,18103
10189
- simo/fleet/__pycache__/forms.cpython-38.pyc,sha256=vFMihKqwLKQRO9GoErkNZwGmDPQd6OYywh9iiZ8rf84,27566
10191
+ simo/fleet/__pycache__/forms.cpython-38.pyc,sha256=0HmLdm3-2_4Zy_lIjUnGpnjf_gFz_KmmTFS1Vt4SY48,27398
10190
10192
  simo/fleet/__pycache__/gateways.cpython-38.pyc,sha256=YAcgTOqJbtjGI03lvEcU6keFfrwAHkObVmErYzfRvjk,3569
10191
10193
  simo/fleet/__pycache__/managers.cpython-38.pyc,sha256=8uz-xpUiqbGDgXIZ_XRZtFb-Tju6NGxflGg-Ee4Yo6k,1310
10192
- simo/fleet/__pycache__/models.cpython-38.pyc,sha256=DQtRR6kI9bt3atgJ0pArCVfSyuutqb6SpxYrsBg10cM,12277
10194
+ simo/fleet/__pycache__/models.cpython-38.pyc,sha256=pHNRUiPRjH0SLp14pzbSIxHi_-27SpZFgSh_7lzA8Wo,12359
10193
10195
  simo/fleet/__pycache__/routing.cpython-38.pyc,sha256=aPrCmxFKVyB8R8ZbJDwdPdFfvT7CvobovvZeq_mqRgY,314
10194
10196
  simo/fleet/__pycache__/serializers.cpython-38.pyc,sha256=gwLIoLQJwkFoDCjKC_ikksNCYea3-mF8hVHFeKlOkbc,3098
10195
10197
  simo/fleet/__pycache__/socket_consumers.cpython-38.pyc,sha256=RjzPD580096fby0HYLzZorm61zdZNOq5AHXmNAC4Yd8,13764
@@ -10446,8 +10448,8 @@ simo/users/templates/invitations/expired_msg.html,sha256=47DEQpj8HBSa-_TImW-5JCe
10446
10448
  simo/users/templates/invitations/expired_suggestion.html,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10447
10449
  simo/users/templates/invitations/taken_msg.html,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10448
10450
  simo/users/templates/invitations/taken_suggestion.html,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10449
- simo-2.0.21.dist-info/LICENSE.md,sha256=M7wm1EmMGDtwPRdg7kW4d00h1uAXjKOT3HFScYQMeiE,34916
10450
- simo-2.0.21.dist-info/METADATA,sha256=eBUGnUupmrbuL2W8Vyfk1VWkJLEztt_8CYOjOV18J8A,1730
10451
- simo-2.0.21.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
10452
- simo-2.0.21.dist-info/top_level.txt,sha256=GmS1hrAbpVqn9OWZh6UX82eIOdRLgYA82RG9fe8v4Rs,5
10453
- simo-2.0.21.dist-info/RECORD,,
10451
+ simo-2.0.23.dist-info/LICENSE.md,sha256=M7wm1EmMGDtwPRdg7kW4d00h1uAXjKOT3HFScYQMeiE,34916
10452
+ simo-2.0.23.dist-info/METADATA,sha256=n65-Fcht6N4VSA8W5igDDeUKN7Hq5YgU8XMTb5pmUcw,1730
10453
+ simo-2.0.23.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
10454
+ simo-2.0.23.dist-info/top_level.txt,sha256=GmS1hrAbpVqn9OWZh6UX82eIOdRLgYA82RG9fe8v4Rs,5
10455
+ simo-2.0.23.dist-info/RECORD,,
File without changes