simo 2.7.11__py3-none-any.whl → 2.7.13__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/auto_urls.py CHANGED
@@ -1,6 +1,6 @@
1
1
  from django.urls import path
2
2
  from .views import (
3
- get_timestamp, upgrade, restart, reboot, set_instance
3
+ get_timestamp, upgrade, restart, reboot, set_instance, delete_instance
4
4
  )
5
5
  from .autocomplete_views import (
6
6
  IconModelAutocomplete,
@@ -33,5 +33,6 @@ urlpatterns = [
33
33
  path('set-instance/<slug:instance_slug>/', set_instance, name='set-instance'),
34
34
  path('upgrade/', upgrade, name='upgrade'),
35
35
  path('restart/', restart, name='restart'),
36
- path('reboot/', reboot, name='reboot')
36
+ path('reboot/', reboot, name='reboot'),
37
+ path('delete-instance/', delete_instance, name='delete-instance')
37
38
  ]
simo/core/forms.py CHANGED
@@ -272,9 +272,9 @@ class ComponentAdminForm(forms.ModelForm):
272
272
  self.instance.config = self.controller.default_config
273
273
  self.instance.meta = self.controller.default_meta
274
274
 
275
- self.cleanup_missing_keys(kwargs.get("data"))
275
+ self.cleanup_missing_keys(kwargs.get("data"), kwargs.get("files"))
276
276
 
277
- def cleanup_missing_keys(self, data):
277
+ def cleanup_missing_keys(self, data, files=None):
278
278
  """
279
279
  Removes missing keys from fields on form submission.
280
280
  This avoids resetting fields that are not present in
@@ -289,6 +289,11 @@ class ComponentAdminForm(forms.ModelForm):
289
289
  return
290
290
 
291
291
  got_keys = list(data.keys())
292
+ if files:
293
+ try:
294
+ got_keys += list(files.keys())
295
+ except:
296
+ pass
292
297
  formset_fields = set()
293
298
  for key in got_keys:
294
299
  if key.endswith('-TOTAL_FORMS'):
@@ -0,0 +1,35 @@
1
+ # Generated by Django 4.2.10 on 2024-12-13 09:44
2
+
3
+ import django.core.files.storage
4
+ from django.db import migrations, models
5
+ import django.db.models.deletion
6
+
7
+
8
+ class Migration(migrations.Migration):
9
+
10
+ dependencies = [
11
+ ('core', '0047_alter_component_value_translation'),
12
+ ]
13
+
14
+ operations = [
15
+ migrations.CreateModel(
16
+ name='PublicFile',
17
+ fields=[
18
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
19
+ ('file', models.FileField(storage=django.core.files.storage.FileSystemStorage(base_url='/public_media/', location='/home/simanas/Projects/SIMO/_var/public_media'), upload_to='public_files')),
20
+ ('date_uploaded', models.DateTimeField(auto_now_add=True)),
21
+ ('meta', models.JSONField(default=dict)),
22
+ ('component', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='public_files', to='core.component')),
23
+ ],
24
+ ),
25
+ migrations.CreateModel(
26
+ name='PrivateFile',
27
+ fields=[
28
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
29
+ ('file', models.FileField(upload_to='private_files')),
30
+ ('date_uploaded', models.DateTimeField(auto_now_add=True)),
31
+ ('meta', models.JSONField(default=dict)),
32
+ ('component', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='private_files', to='core.component')),
33
+ ],
34
+ ),
35
+ ]
simo/core/models.py CHANGED
@@ -1,5 +1,6 @@
1
1
  import inspect
2
2
  import time
3
+ import os
3
4
  from collections.abc import Iterable
4
5
  from django.utils.text import slugify
5
6
  from django.utils.functional import cached_property
@@ -9,6 +10,8 @@ from django.db import models
9
10
  from django.db.models.signals import post_delete
10
11
  from django.dispatch import receiver
11
12
  from django.template.loader import render_to_string
13
+ from django.conf import settings
14
+ from django.core.files.storage import FileSystemStorage
12
15
  from timezone_utils.choices import ALL_TIMEZONES_CHOICES
13
16
  from location_field.models.plain import PlainLocationField
14
17
  from model_utils import FieldTracker
@@ -602,6 +605,35 @@ def is_in_alarm(self):
602
605
  return c_methods
603
606
 
604
607
 
608
+ class PublicFile(models.Model):
609
+ component = models.ForeignKey(
610
+ Component, on_delete=models.CASCADE, related_name='public_files'
611
+ )
612
+ file = models.FileField(
613
+ upload_to='public_files', storage=FileSystemStorage(
614
+ location=os.path.join(settings.VAR_DIR, 'public_media'),
615
+ base_url='/public_media/'
616
+ )
617
+ )
618
+ date_uploaded = models.DateTimeField(auto_now_add=True)
619
+ meta = models.JSONField(default=dict)
620
+
621
+ def get_absolute_url(self):
622
+ return self.file.url
623
+
624
+
625
+ class PrivateFile(models.Model):
626
+ component = models.ForeignKey(
627
+ Component, on_delete=models.CASCADE, related_name='private_files'
628
+ )
629
+ file = models.FileField(upload_to='private_files')
630
+ date_uploaded = models.DateTimeField(auto_now_add=True)
631
+ meta = models.JSONField(default=dict)
632
+
633
+ def get_absolute_url(self):
634
+ return self.file.url
635
+
636
+
605
637
  class ComponentHistory(models.Model):
606
638
  component = models.ForeignKey(
607
639
  Component, on_delete=models.CASCADE, related_name='history'
simo/core/serializers.py CHANGED
@@ -311,6 +311,7 @@ class ComponentSerializer(FormSerializer):
311
311
  Select2ListMultipleChoiceField: ComponentManyToManyRelatedField,
312
312
  Select2ModelMultipleChoiceField: ComponentManyToManyRelatedField,
313
313
  FormsetField: ComponentFormsetField,
314
+ SoundField: SoundSerializer
314
315
  }
315
316
 
316
317
 
@@ -8,7 +8,9 @@ from django.conf import settings
8
8
  from django.template.loader import render_to_string
9
9
  from actstream import action
10
10
  from simo.users.models import PermissionsRole
11
- from .models import Instance, Gateway, Component, Icon, Zone, Category
11
+ from .models import (
12
+ Instance, Gateway, Component, Icon, Zone, Category, PublicFile, PrivateFile
13
+ )
12
14
 
13
15
 
14
16
  @receiver(post_save, sender=Instance)
@@ -192,4 +194,13 @@ def gateway_post_save(sender, instance, created, *args, **kwargs):
192
194
 
193
195
  @receiver(post_delete, sender=Gateway)
194
196
  def gateway_post_delete(sender, instance, *args, **kwargs):
195
- instance.stop()
197
+ instance.stop()
198
+
199
+
200
+ @receiver(post_delete, sender=PublicFile)
201
+ @receiver(post_delete, sender=PrivateFile)
202
+ def delete_file_itself(sender, instance, *args, **kwargs):
203
+ try:
204
+ os.remove(instance.file.path)
205
+ except:
206
+ pass
simo/core/views.py CHANGED
@@ -2,9 +2,11 @@ import time
2
2
  import re
3
3
  from django.contrib.auth.decorators import login_required
4
4
  from django.urls import reverse
5
- from django.shortcuts import redirect
5
+ from django.shortcuts import redirect, get_object_or_404
6
6
  from django.views.decorators.csrf import csrf_exempt
7
- from django.http import HttpResponse, Http404, JsonResponse
7
+ from django.http import (
8
+ HttpResponse, Http404, JsonResponse, HttpResponseForbidden
9
+ )
8
10
  from django.contrib import messages
9
11
  from simo.conf import dynamic_settings
10
12
  from .models import Instance
@@ -69,6 +71,18 @@ def set_instance(request, instance_slug):
69
71
  return redirect(reverse('admin:index'))
70
72
 
71
73
 
74
+ @login_required
75
+ @csrf_exempt
76
+ def delete_instance(request):
77
+ if request.method != 'POST':
78
+ raise Http404()
79
+ if not request.user.is_master:
80
+ return HttpResponseForbidden()
81
+ instance = get_object_or_404(Instance, uid=request.GET['uid'])
82
+ instance.delete()
83
+ return HttpResponse('success')
84
+
85
+
72
86
  def hub_info(request):
73
87
  data = {"hub_uid": dynamic_settings['core__hub_uid']}
74
88
  if not Instance.objects.filter(is_active=True).count():
simo/generic/forms.py CHANGED
@@ -1,9 +1,12 @@
1
+ import os
2
+ import librosa
3
+ import tempfile
1
4
  from django import forms
2
5
  from django.forms import formset_factory
3
6
  from django.db.models import Q
4
7
  from django.utils.translation import gettext_lazy as _
5
8
  from simo.core.forms import HiddenField, BaseComponentForm
6
- from simo.core.models import Icon, Component
9
+ from simo.core.models import Icon, Component, PublicFile
7
10
  from simo.core.controllers import (
8
11
  NumericSensor, MultiSensor, Switch, Dimmer
9
12
  )
@@ -18,7 +21,6 @@ from simo.core.form_fields import (
18
21
  )
19
22
  from simo.core.forms import DimmerConfigForm, SwitchForm
20
23
  from simo.core.form_fields import SoundField
21
- from simo.multimedia.models import Sound
22
24
 
23
25
  ACTION_METHODS = (
24
26
  ('turn_on', "Turn ON"), ('turn_off', "Turn OFF"),
@@ -618,7 +620,7 @@ class AlarmClockConfigForm(BaseComponentForm):
618
620
 
619
621
 
620
622
  class AudioAlertConfigForm(BaseComponentForm):
621
- sound = SoundField()
623
+ sound = SoundField(required=False)
622
624
  loop = forms.BooleanField(initial=False, required=False)
623
625
  volume = forms.IntegerField(initial=30, min_value=2, max_value=100)
624
626
  players = Select2ModelMultipleChoiceField(
@@ -630,22 +632,59 @@ class AudioAlertConfigForm(BaseComponentForm):
630
632
  super().__init__(*args, **kwargs)
631
633
  self.basic_fields.extend(['sound', 'loop', 'volume', 'players'])
632
634
  if self.instance.id:
633
- sound = Sound.objects.filter(
634
- id=self.instance.config.get('sound_id', 0)
635
+ public_file = PublicFile.objects.filter(
636
+ component=self.instance
635
637
  ).first()
636
- if sound:
637
- self.fields['sound'].initial = sound.file
638
+ if public_file:
639
+ self.fields['sound'].help_text = f"Currently: {public_file.file}"
640
+
641
+ def clean_sound(self):
642
+ if not self.cleaned_data.get('sound'):
643
+ if not self.instance.pk:
644
+ raise forms.ValidationError("Please pick a sound!")
645
+ return
646
+ if self.cleaned_data['sound'].size > 1024 * 1024 * 50:
647
+ raise forms.ValidationError("No more than 50Mb please.")
648
+ temp_path = os.path.join(
649
+ tempfile.gettempdir(), self.cleaned_data['sound'].name
650
+ )
651
+ with open(temp_path, 'wb') as temp_file:
652
+ for chunk in self.cleaned_data['sound'].chunks():
653
+ temp_file.write(chunk)
654
+
655
+ try:
656
+ self.cleaned_data['sound'].duration = int(
657
+ librosa.core.get_duration(sr=22050, filename=temp_path)
658
+ )
659
+ except:
660
+ try:
661
+ os.remove(temp_path)
662
+ except:
663
+ pass
664
+ raise forms.ValidationError("This doesn't look like audio file!")
665
+ try:
666
+ os.remove(temp_path)
667
+ except:
668
+ pass
669
+
670
+ return self.cleaned_data['sound']
671
+
638
672
 
639
673
  def save(self, commit=True):
640
- if commit and self.cleaned_data['sound'] \
674
+ obj = super().save(commit=commit)
675
+ if commit and self.cleaned_data.get('sound') \
641
676
  and self.cleaned_data['sound'] != self.fields['sound'].initial:
642
- sound = Sound(
643
- name=self.cleaned_data['sound'].name,
644
- )
645
- sound.file.save(
677
+ public_file = PublicFile(component=obj)
678
+ public_file.file.save(
646
679
  self.cleaned_data['sound'].name, self.cleaned_data['sound'],
647
680
  save=True
648
681
  )
649
- self.instance.config['sound_id'] = sound.id
650
- self.cleaned_data.pop('sound')
651
- return super().save(commit=commit)
682
+ org = PublicFile.objects.filter(
683
+ id=self.instance.config.get('public_file_id', 0)
684
+ )
685
+ if org:
686
+ org.delete()
687
+ self.instance.config['public_file_id'] = public_file.id
688
+ self.instance.config['duration'] = self.cleaned_data['sound'].duration
689
+ self.instance.save()
690
+ return obj
simo/generic/gateways.py CHANGED
@@ -1,21 +1,17 @@
1
1
  import sys
2
- import logging
3
2
  import pytz
4
3
  import json
5
4
  import time
6
- import multiprocessing
7
5
  import threading
8
6
  import traceback
9
7
  from django.conf import settings
10
8
  from django.utils import timezone
11
- from django.db import connection as db_connection
12
- from django.db.models import Q
13
9
  import paho.mqtt.client as mqtt
14
- from simo.core.models import Component
10
+ from simo.core.utils.helpers import get_self_ip
11
+ from simo.core.models import Component, PublicFile
15
12
  from simo.core.middleware import introduce_instance, drop_current_instance
16
13
  from simo.core.gateways import BaseObjectCommandsGatewayHandler
17
14
  from simo.core.forms import BaseGatewayForm
18
- from simo.core.utils.logs import StreamToLogger
19
15
  from simo.core.events import GatewayObjectCommand, get_event_obj
20
16
  from simo.core.loggers import get_gw_logger, get_component_logger
21
17
 
@@ -138,12 +134,12 @@ class AudioAlertsHandler:
138
134
 
139
135
  def control_audio_alert(self, component, val):
140
136
  if val:
141
- from simo.multimedia.models import Sound
142
- sound = Sound.objects.filter(
143
- id=component.config.get('sound_id')
137
+ public_file = PublicFile.objects.filter(
138
+ component=component
144
139
  ).first()
145
- if not sound:
140
+ if not public_file:
146
141
  return
142
+ uri = f"http://{get_self_ip()}{public_file.get_absolute_url()}"
147
143
  loop = component.config.get('loop', False)
148
144
  for pl_id in component.config.get('players', []):
149
145
  player = Component.objects.filter(
@@ -152,14 +148,17 @@ class AudioAlertsHandler:
152
148
  if not player:
153
149
  continue
154
150
  player.play_alert(
155
- sound.id,
151
+ uri,
156
152
  component.config.get('loop', False),
157
153
  component.config.get('volume', 50)
158
154
  )
159
155
  if not loop:
160
156
  def set_done(comp):
161
157
  comp.set(False)
162
- threading.Timer(sound.length, set_done, args=[component])
158
+ threading.Timer(
159
+ component.config.get('duration', 1),
160
+ set_done, args=[component]
161
+ )
163
162
  component.set(True)
164
163
  else:
165
164
  for pl_id in component.config.get('players', []):
@@ -92,15 +92,15 @@ class BasePlayer(Switch):
92
92
  def play_alert(self, val, loop=False, volume=None):
93
93
  '''
94
94
  Plays alert and goes back to whatever was playing initially
95
- :param val: Sound.id or uri
95
+ :param val: uri
96
96
  :param loop: Repeat infinitely
97
97
  :param volume: volume at which to play
98
98
  :return:
99
99
  '''
100
- assert type(val) in (int, str)
100
+ assert type(val) == str
101
101
  if volume:
102
102
  assert 0 <= volume <= 100
103
- self.send({"alert": val, 'volume': volume})
103
+ self.send({"alert": val, 'loop': loop, 'volume': volume})
104
104
 
105
105
  def cancel_alert(self):
106
106
  '''Cancel alert if it's currently playing'''
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: simo
3
- Version: 2.7.11
3
+ Version: 2.7.13
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
@@ -66,7 +66,7 @@ simo/core/api_auth.py,sha256=vCxvczA8aWNcW0VyKs5WlC_ytlqeGP_H_hkKUNVkCwM,1247
66
66
  simo/core/api_meta.py,sha256=Bh-UOzANXl9Bg3FadFf7biM_olAr9GgjNJXu55SO6Og,5290
67
67
  simo/core/app_widgets.py,sha256=VxZzapuc-a29wBH7JzpvNF2SK1ECrgNUySId5ke1ffc,2509
68
68
  simo/core/apps.py,sha256=CsqpiQerhmrMsH-wGiG-gQgXd9qEkIi-LUaA9cXpKSw,425
69
- simo/core/auto_urls.py,sha256=FBDclIeRp5UVWomIUbRzUgY-AoMk-r2qC2htlwKD4Lo,1106
69
+ simo/core/auto_urls.py,sha256=FqKhH0fF7cGO6P2YrjblwG4JA2UkVXj3lreJUOB2Jq4,1194
70
70
  simo/core/autocomplete_views.py,sha256=x3MKOZvXYS3xVQ-V1S7Liv_U5bxr-uc0gePa85wv5nA,4561
71
71
  simo/core/base_types.py,sha256=WypW8hTfzveuTQtruGjLYAGQZIuczxTlW-SdRk3iQug,666
72
72
  simo/core/context.py,sha256=LKw1I4iIRnlnzoTCuSLLqDX7crHdBnMo3hjqYvVmzFc,1557
@@ -75,22 +75,22 @@ simo/core/dynamic_settings.py,sha256=bUs58XEZOCIEhg1TigR3LmYggli13KMryBZ9pC7ugAQ
75
75
  simo/core/events.py,sha256=1_KIk5pJqdLPRQlCQ9xSyALst2Cn0b2lAEAJ3QjwIjE,4801
76
76
  simo/core/filters.py,sha256=6wbn8C2WvKTTjtfMwwLBp2Fib1V0-DMpS4iqJd6jJQo,2540
77
77
  simo/core/form_fields.py,sha256=Os4bLhEt6fQpQ5JWPThs5XxMI2429kQNgr7SgDDr_y0,5063
78
- simo/core/forms.py,sha256=iPUBclY5aSX4fCy2VdAMi48De95lj17RiQaggAKmTnA,21860
78
+ simo/core/forms.py,sha256=osrwzw1nV-ObvFx7OMJaolqGU4xeZAOuhQRtirFjq6Q,22016
79
79
  simo/core/gateways.py,sha256=Y2BME6zSyeUq_e-hzEUF6gErCUCP6nFxedkLZKiLVOo,4141
80
80
  simo/core/loggers.py,sha256=EBdq23gTQScVfQVH-xeP90-wII2DQFDjoROAW6ggUP4,1645
81
81
  simo/core/managers.py,sha256=n-b3I4uXzfHKTeB1VMjSaMsDUxp8FegFJwnbV1IsWQ4,3019
82
82
  simo/core/middleware.py,sha256=eUFf6iP-Snx_0TE3MoXsSwqrd5IjlukqZk2GQGStRCo,3385
83
- simo/core/models.py,sha256=-zhQEvQRqacTjdqL4Gv7shlP1Uo09Rj3vHKqNONlxWU,22716
83
+ simo/core/models.py,sha256=i7FT-y4wdvwwMbvxxDO3F9VSwLXBVcjlSFawb-V7N8w,23709
84
84
  simo/core/permissions.py,sha256=2YNRot2qoHjHKWPGOpO4PBseecctPbTlUQpepnFkCRs,3027
85
85
  simo/core/routing.py,sha256=X1_IHxyA-_Q7hw1udDoviVP4_FSBDl8GYETTC2zWTbY,499
86
- simo/core/serializers.py,sha256=ikt1_UzEqJKPC1UFvvvf6q424VXkCY1oh61uXTDBeYc,22624
87
- simo/core/signal_receivers.py,sha256=y8p0BG3PzhHI3w2WXS8QXyxXc7YKUE_mmO2-QElvrP4,6276
86
+ simo/core/serializers.py,sha256=b_38QV6LXsHZ7KfzX-h6N_-RjyV94ZDAyR6tok9lCbs,22664
87
+ simo/core/signal_receivers.py,sha256=5qp607PdNlRHyw88YOXu7rSznHm3upEpWLxB0lmEa0s,6527
88
88
  simo/core/socket_consumers.py,sha256=Es_NmacQGZjsncBXDTEXR2yZbRs7mf2FKOBJjbZRGac,9607
89
89
  simo/core/storage.py,sha256=_5igjaoWZAiExGWFEJMElxUw55DzJG1jqFty33xe8BE,342
90
90
  simo/core/tasks.py,sha256=LMTkZQDGFus5L2Q8AGzYegjpnZKf9Klgo3V9BT5L2ng,16904
91
91
  simo/core/todos.py,sha256=eYVXfLGiapkxKK57XuviSNe3WsUYyIWZ0hgQJk7ThKo,665
92
92
  simo/core/types.py,sha256=WJEq48mIbFi_5Alt4wxWMGXxNxUTXqfQU5koH7wqHHI,1108
93
- simo/core/views.py,sha256=yx9I0byeVUa-LAOnklpWIYwpNNOf5m9fyjKBvj4YCh4,2475
93
+ simo/core/views.py,sha256=08H4Bm7KrHxB3p3ZKx1vrFR4d0DjCqMbqQosEsRWpkY,2841
94
94
  simo/core/widgets.py,sha256=J9e06C6I22F6xKic3VMgG7WeX07glAcl-4bF2Mg180A,2827
95
95
  simo/core/__pycache__/__init__.cpython-38.pyc,sha256=ZJFM_XN0RmJMULQulgA_wFiOnEtsMoedcOWnXjH-Y8o,208
96
96
  simo/core/__pycache__/admin.cpython-38.pyc,sha256=zdegA3IsH3QhT7Hm6Aj08F2sv-3h1X7VBcwN05fhVis,14155
@@ -99,7 +99,7 @@ simo/core/__pycache__/api_auth.cpython-38.pyc,sha256=mi3mu5qEKio_PvfQEvr3Q6AhdPL
99
99
  simo/core/__pycache__/api_meta.cpython-38.pyc,sha256=euP2EQsfTVCdvMT9NY10bK7xrz2dQouqQKZP0efN1ss,3906
100
100
  simo/core/__pycache__/app_widgets.cpython-38.pyc,sha256=oN657XMMZ6GYN9nblv7fX3kdnTEzSP9XV6PXM6Z0wl4,4358
101
101
  simo/core/__pycache__/apps.cpython-38.pyc,sha256=JL0BEqgXcSQvMlcK48PBpPfyDEkPMdO1Y0teqMRGirs,713
102
- simo/core/__pycache__/auto_urls.cpython-38.pyc,sha256=ib_ns5Ko8ybfrdJJWYVV1jevihxOFs39aBF4bez6Lzs,874
102
+ simo/core/__pycache__/auto_urls.cpython-38.pyc,sha256=mwS1N-R5kcT5uoDAGraKG9MWZpCM8_SA4L4eyZsVpYk,949
103
103
  simo/core/__pycache__/autocomplete_views.cpython-38.pyc,sha256=rZzjrxAir0HzN9uKPoXIY753f1CSdFWaSW7QBDen3qc,4448
104
104
  simo/core/__pycache__/base_types.cpython-38.pyc,sha256=CX-qlF7CefRi_mCE954wYa9rUFR88mOl6g7fybDRu7g,803
105
105
  simo/core/__pycache__/context.cpython-38.pyc,sha256=NlTHt2GvXxA21AhBkeyOLfRFUuXw7wmwqyNhhcDl2cw,1373
@@ -108,21 +108,21 @@ simo/core/__pycache__/dynamic_settings.cpython-38.pyc,sha256=wGpnscX1DxFpRl54MQU
108
108
  simo/core/__pycache__/events.cpython-38.pyc,sha256=1y8YaZsiDkBOeIWzH7SQz4holmMG_RLlMWi8kuSZcoE,5280
109
109
  simo/core/__pycache__/filters.cpython-38.pyc,sha256=WBBDwcDQwOmgbrRhyUxenSN80rU4Eq9jQ6RcrRGCP_o,2440
110
110
  simo/core/__pycache__/form_fields.cpython-38.pyc,sha256=WJgjOqvl5BZoVAGVtxGeDOOW-jn3_fdJwVYBx1IRJlY,5886
111
- simo/core/__pycache__/forms.cpython-38.pyc,sha256=Ye8lP-xG_yxMKOftzhXNPWfY4nkjNynBn-eJhLnb-GM,17754
111
+ simo/core/__pycache__/forms.cpython-38.pyc,sha256=frvM1b9KRqsjh4YbUlV1CW8yupUyXJTb2R4sVgwy2K0,17827
112
112
  simo/core/__pycache__/gateways.cpython-38.pyc,sha256=-_ugqnUOA1Cl6VfMqpV96n7ekVOEwYg_jNvoaZEcx9I,4815
113
113
  simo/core/__pycache__/loggers.cpython-38.pyc,sha256=Z-cdQnC6XlIonPV4Sl4E52tP4NMEdPAiHK0cFaIL7I8,1623
114
114
  simo/core/__pycache__/managers.cpython-38.pyc,sha256=6RTIxyjOgpQGtAqcUyE2vFPS09w1V5Wmd_vOV7rHRRI,3370
115
115
  simo/core/__pycache__/middleware.cpython-38.pyc,sha256=SgTLFNkKxvJ62hevSAVNZHgHdG_u2p7AZBhrj-jfFPs,2649
116
- simo/core/__pycache__/models.cpython-38.pyc,sha256=3nVIrsYP-ZCmkr5z71zivCdaZ8hqmh521BvlMK5gP6g,18468
116
+ simo/core/__pycache__/models.cpython-38.pyc,sha256=EG1RJ-kaOFupzHcrZnii6JQUuoQ1gbt6LN9xUMfHFo4,19610
117
117
  simo/core/__pycache__/permissions.cpython-38.pyc,sha256=UdtxCTXPEbe99vgZOfRz9wfKSYvUn9hSRbpIV9CJSyI,2988
118
118
  simo/core/__pycache__/routing.cpython-38.pyc,sha256=3T3FPJ8Cn99xZCGvMyg2xjl7al-Shm9CelbSpkJtNP8,599
119
- simo/core/__pycache__/serializers.cpython-38.pyc,sha256=SkPKqNRo4PBdHBqJ6KsX5SnJBkC6zk3SO8bpr4enwu4,20000
120
- simo/core/__pycache__/signal_receivers.cpython-38.pyc,sha256=v-qSKC9KX--aw9-vSNX8z8HXNFr0yOII0Aw8JxrYM0U,4920
119
+ simo/core/__pycache__/serializers.cpython-38.pyc,sha256=ciWQe19lrr5ImXByMZJI8JM47IlqmyAo1XcXfnVrU80,20018
120
+ simo/core/__pycache__/signal_receivers.cpython-38.pyc,sha256=R_h1GHkoZXR-nrwiRJWQ9xE69JB1R_mP_fNYIBX4lrE,5165
121
121
  simo/core/__pycache__/socket_consumers.cpython-38.pyc,sha256=Mr1-x-vGjBNffbz0S6AKpJCuzHJgRm8kXefv3qVVY_E,8397
122
122
  simo/core/__pycache__/storage.cpython-38.pyc,sha256=9R1Xu0FJDflfRXUPsqEgt0SpwiP7FGk7HaR8s8XRyI8,721
123
123
  simo/core/__pycache__/tasks.cpython-38.pyc,sha256=-J2is-l5XsfhamreN2TPQDTK-Jw6XGYL81bcVfjXsU8,11213
124
124
  simo/core/__pycache__/todos.cpython-38.pyc,sha256=lOqGZ58siHM3isoJV4r7sg8igrfE9fFd-jSfeBa0AQI,253
125
- simo/core/__pycache__/views.cpython-38.pyc,sha256=IRjbX3MwKkAb10sMIJ3esKZH8W-tHwnuzm-mLIT_NWc,2584
125
+ simo/core/__pycache__/views.cpython-38.pyc,sha256=kYKvEcyKicdkTcN0iEJ4pT101-KHiZfXWkidiu82Evw,2925
126
126
  simo/core/__pycache__/widgets.cpython-38.pyc,sha256=sR0ZeHCHrhnNDBJuRrxp3zUsfBp0xrtF0xrK2TkQv1o,3520
127
127
  simo/core/db_backend/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
128
128
  simo/core/db_backend/base.py,sha256=wY5jsZ8hQpwot3o-7JNtDe33xy-vlfMLXa011htDojI,601
@@ -238,6 +238,7 @@ simo/core/migrations/0044_alter_gateway_type.py,sha256=xoQvOSz_JrWHECAAII83518tm
238
238
  simo/core/migrations/0045_alter_instance_device_report_history_days_and_more.py,sha256=saR7gEbTDfXTcKnT1R193Aboqlg32HvDRI_JAvZ97TY,1360
239
239
  simo/core/migrations/0046_component_value_translation_alter_gateway_type.py,sha256=SUybDSLRCGevVS9mCQGvozO8LA43tKze3R5rCf1TQ4E,1282
240
240
  simo/core/migrations/0047_alter_component_value_translation.py,sha256=3GAgUBkZKUwWJMbDJxJJLBicC8eKkQ2ZJA__-xYpBCo,733
241
+ simo/core/migrations/0048_publicfile_privatefile.py,sha256=3NAP6f1ep66-CHJ7olSYOnXXeN7fTAg76lEjTJE1rL8,1601
241
242
  simo/core/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
242
243
  simo/core/migrations/__pycache__/0001_initial.cpython-38.pyc,sha256=w6GiBXVxWj30Bg4Sn_pFVeA041d-pCrkaq8mR3KuF70,5381
243
244
  simo/core/migrations/__pycache__/0002_load_icons.cpython-38.pyc,sha256=Nb9RrPjVYo_RpZ5PmzoaEIWGCeVt4kicpmGiKlBrpIw,2123
@@ -286,6 +287,7 @@ simo/core/migrations/__pycache__/0044_alter_gateway_type.cpython-38.pyc,sha256=K
286
287
  simo/core/migrations/__pycache__/0045_alter_instance_device_report_history_days_and_more.cpython-38.pyc,sha256=e1_hKjdokggnPndFkoBWrHQ18IkBi2W7WahcSjAbvKo,1278
287
288
  simo/core/migrations/__pycache__/0046_component_value_translation_alter_gateway_type.cpython-38.pyc,sha256=0WowsGpjyPX7Z341bhU4bXErZqqFZaL1gtiusabrD0I,1445
288
289
  simo/core/migrations/__pycache__/0047_alter_component_value_translation.cpython-38.pyc,sha256=ucmaVOjI3q78vBTiEC7Aq76CklWq3fsC24EiH-mIzGc,961
290
+ simo/core/migrations/__pycache__/0048_publicfile_privatefile.cpython-38.pyc,sha256=Xtycj-4jvkB8UjVdgZZRzTgYl68Cr-Jj9WqCAho1Ggk,1346
289
291
  simo/core/migrations/__pycache__/__init__.cpython-38.pyc,sha256=VZmDQ57BTcebuM0KMhjiTOabgWZCBxQmSJzWZos9SO8,169
290
292
  simo/core/static/ansi_styles.css,sha256=4ieJGrjZPKyPSago9FdB_gflHoGE1vxCHi8qVn5tY-Y,37352
291
293
  simo/core/static/admin/Img/plus.svg,sha256=2NpSFPWqGIjpAQGFI7LDQHPKagEhYkJiJX95ufCoZaI,741
@@ -10384,8 +10386,8 @@ simo/generic/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10384
10386
  simo/generic/app_widgets.py,sha256=y8W3jR76Hh26O9pPQyg2SophMbYIOtAWD33MPKbB8Mg,856
10385
10387
  simo/generic/base_types.py,sha256=u3SlfpNYaCwkVBwomWgso4ODzL71ay9MhiAW-bxgnDU,341
10386
10388
  simo/generic/controllers.py,sha256=gqL1Evt-8Cq8MeHG-eV8aCtQ4St_gylE52wnXwOQUWU,46285
10387
- simo/generic/forms.py,sha256=AIY9w8wNq2-Jwo2ekq2yjdSTZzqDFr70ZDcmT4kaU4Q,24381
10388
- simo/generic/gateways.py,sha256=Dm1Yb7JtSbqeo4AIbsHvFK3Og7ZCWHw71BZY8wwAkWY,17466
10389
+ simo/generic/forms.py,sha256=VVp0IflKabObfxWPrc4Q6oEs_Z8hEvwnmNB2gZmaCGQ,25732
10390
+ simo/generic/gateways.py,sha256=04FKHgg3_cWjhu-bxexgRFpKRavW6_StgugFcDYwkiQ,17471
10389
10391
  simo/generic/models.py,sha256=Adq7ipWK-renxJlNW-SZnAq2oGEOwKx8EdUWaKnfcVQ,7597
10390
10392
  simo/generic/routing.py,sha256=elQVZmgnPiieEuti4sJ7zITk1hlRxpgbotcutJJgC60,228
10391
10393
  simo/generic/socket_consumers.py,sha256=pyiqzfGxSKBNqfrfEJ_kCU0UbSC28XnvDn6QjKkbqyY,1767
@@ -10393,8 +10395,8 @@ simo/generic/__pycache__/__init__.cpython-38.pyc,sha256=mLu54WS9KIl-pHwVCBKpsDFI
10393
10395
  simo/generic/__pycache__/app_widgets.cpython-38.pyc,sha256=D9b13pbMlirgHmjDnQhfLIDGSVINoSouHb4SWOeCRrs,1642
10394
10396
  simo/generic/__pycache__/base_types.cpython-38.pyc,sha256=aV5NdIuvXR-ItKpI__MwcyPZHD6Z882TFdgYkPCkr1I,493
10395
10397
  simo/generic/__pycache__/controllers.cpython-38.pyc,sha256=jJjwKVaDYyazrRGNjUFoY74nr_jX_DEnsC9KjyxZCgc,30427
10396
- simo/generic/__pycache__/forms.cpython-38.pyc,sha256=rNLPZ5K45hNf4Hx7QvcNFkIdUrX1CSltDA-TwkDnbUs,18460
10397
- simo/generic/__pycache__/gateways.cpython-38.pyc,sha256=ZoHZv3_kjOqfFqzs4MmgZ22-u1d3mbBO9PmBycDVKU0,12823
10398
+ simo/generic/__pycache__/forms.cpython-38.pyc,sha256=JXaZ7iidF9GyfHY0DQSiox9rmGbEzU8c0tS9mpGNoDE,19342
10399
+ simo/generic/__pycache__/gateways.cpython-38.pyc,sha256=GIeMT51oZU2OCFD4eUDFdSRRYE0Qf14AcOr_gdUqG94,12705
10398
10400
  simo/generic/__pycache__/models.cpython-38.pyc,sha256=MZpum7syAFxuulf47K7gtUlJJ7xRD-IBUBAwUM1ZRnw,5825
10399
10401
  simo/generic/__pycache__/routing.cpython-38.pyc,sha256=xtxTUTBTdivzFyA5Wh7k-hUj1WDO_FiRq6HYXdbr9Ks,382
10400
10402
  simo/generic/__pycache__/socket_consumers.cpython-38.pyc,sha256=FaVCf_uJI2uwj1Zz-jwsOXou65oV9gFCIB8e-YKquRk,1662
@@ -10435,7 +10437,7 @@ simo/multimedia/api.py,sha256=mZ5BTggWdc_kL8P70JGC3rTCiZKPnxWYoyNcAQkFnX4,285
10435
10437
  simo/multimedia/app_widgets.py,sha256=g_IPx5bNmIS6JbaXXDCzYZYV2KVKAiYvWjH4oI30lWM,331
10436
10438
  simo/multimedia/auto_urls.py,sha256=9FUwlifvAM-AJbAVK6ZCjsQdAZfREuf_sf-kSYjMu7I,204
10437
10439
  simo/multimedia/base_types.py,sha256=dAP7_uh_b3A03yXBJZyQdRFucKIro4_RkIZ5yOaWXVE,151
10438
- simo/multimedia/controllers.py,sha256=pLrJew-WJloR7MGO6hlnIBCAalezasmHLcp1v0vBTWA,3539
10440
+ simo/multimedia/controllers.py,sha256=hEPdzVqnwzJXMYjuEsxKIYcaHK3asg3ur8m4XeEjEAA,3534
10439
10441
  simo/multimedia/forms.py,sha256=oMCVUXRNiESrY3w_uBLRRgjMjx8BrmNeVglzorA9QtY,239
10440
10442
  simo/multimedia/models.py,sha256=qUQaADxFegVMbcEdjyP72UCq5kkqJ5ZSe2lu9qyoom8,1169
10441
10443
  simo/multimedia/serializers.py,sha256=9DRGsJVJLKdqmOLiVHMY06bTTYxpABhDy1JB_klzsBw,383
@@ -10446,7 +10448,7 @@ simo/multimedia/__pycache__/api.cpython-38.pyc,sha256=lFGEB74vgyEM98B42wwcN9WvH8
10446
10448
  simo/multimedia/__pycache__/app_widgets.cpython-38.pyc,sha256=6pr3fz21tQ5ReC9SJ8VzheUZ0hpxDIClB0SA8YCwcPk,730
10447
10449
  simo/multimedia/__pycache__/auto_urls.cpython-38.pyc,sha256=JNI3pZNFC3NoJV4cxsy-Oe6Vc6TA9fpPU63hjlXWINE,357
10448
10450
  simo/multimedia/__pycache__/base_types.cpython-38.pyc,sha256=c4nmNziLs4RB3MAnxltn3W5XNW6PM5_vK_mm3Yvy42Y,324
10449
- simo/multimedia/__pycache__/controllers.cpython-38.pyc,sha256=aeAYsu7v1HH-Or9IDq41TI5SRMakuQYIIR1uwyci2zI,5011
10451
+ simo/multimedia/__pycache__/controllers.cpython-38.pyc,sha256=sbWvA-ro93qWxV6Qk8Pv_WPsq91tqlnFPIvCeQ0tvLA,4997
10450
10452
  simo/multimedia/__pycache__/forms.cpython-38.pyc,sha256=99h7Yj2jim3QOrqej00wiPufrCF3F--RoYvwa6lzhPI,697
10451
10453
  simo/multimedia/__pycache__/models.cpython-38.pyc,sha256=LmQ_XMA--wvLq11xYp24nU-y-kbx17iEZ10y9twCMJ0,1662
10452
10454
  simo/multimedia/__pycache__/serializers.cpython-38.pyc,sha256=n86txYSrkmN0Xlrr8dMwKSY7rEzMc1iovepCZi_Fcw8,886
@@ -10616,9 +10618,9 @@ simo/users/templates/invitations/expired_msg.html,sha256=47DEQpj8HBSa-_TImW-5JCe
10616
10618
  simo/users/templates/invitations/expired_suggestion.html,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10617
10619
  simo/users/templates/invitations/taken_msg.html,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10618
10620
  simo/users/templates/invitations/taken_suggestion.html,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10619
- simo-2.7.11.dist-info/LICENSE.md,sha256=M7wm1EmMGDtwPRdg7kW4d00h1uAXjKOT3HFScYQMeiE,34916
10620
- simo-2.7.11.dist-info/METADATA,sha256=Jf5swgKHP4RoINsccaxIEMqjQSMRULyR-3IVDej8sq0,1953
10621
- simo-2.7.11.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
10622
- simo-2.7.11.dist-info/entry_points.txt,sha256=S9PwnUYmTSW7681GKDCxUbL0leRJIaRk6fDQIKgbZBA,135
10623
- simo-2.7.11.dist-info/top_level.txt,sha256=GmS1hrAbpVqn9OWZh6UX82eIOdRLgYA82RG9fe8v4Rs,5
10624
- simo-2.7.11.dist-info/RECORD,,
10621
+ simo-2.7.13.dist-info/LICENSE.md,sha256=M7wm1EmMGDtwPRdg7kW4d00h1uAXjKOT3HFScYQMeiE,34916
10622
+ simo-2.7.13.dist-info/METADATA,sha256=Dfh3VdJ-c5kB_5cRNa-EIIfN--LMdgEHt7tVD9cyDqo,1953
10623
+ simo-2.7.13.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
10624
+ simo-2.7.13.dist-info/entry_points.txt,sha256=S9PwnUYmTSW7681GKDCxUbL0leRJIaRk6fDQIKgbZBA,135
10625
+ simo-2.7.13.dist-info/top_level.txt,sha256=GmS1hrAbpVqn9OWZh6UX82eIOdRLgYA82RG9fe8v4Rs,5
10626
+ simo-2.7.13.dist-info/RECORD,,
File without changes