simo 2.0.1__py3-none-any.whl → 2.0.3__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.
- simo/core/controllers.py +16 -1
- simo/core/socket_consumers.py +5 -0
- simo/core/utils/logs.py +34 -0
- simo/fleet/admin.py +10 -0
- simo/fleet/controllers.py +2 -0
- simo/fleet/migrations/0027_auto_20240306_0802.py +21 -21
- simo/fleet/models.py +18 -16
- simo/fleet/serializers.py +5 -0
- simo/fleet/socket_consumers.py +99 -92
- simo/generic/forms.py +6 -0
- simo/generic/models.py +15 -9
- simo/notifications/utils.py +1 -1
- {simo-2.0.1.dist-info → simo-2.0.3.dist-info}/METADATA +1 -1
- {simo-2.0.1.dist-info → simo-2.0.3.dist-info}/RECORD +17 -17
- {simo-2.0.1.dist-info → simo-2.0.3.dist-info}/LICENSE.md +0 -0
- {simo-2.0.1.dist-info → simo-2.0.3.dist-info}/WHEEL +0 -0
- {simo-2.0.1.dist-info → simo-2.0.3.dist-info}/top_level.txt +0 -0
simo/core/controllers.py
CHANGED
|
@@ -762,6 +762,12 @@ class Lock(Switch):
|
|
|
762
762
|
app_widget = LockWidget
|
|
763
763
|
admin_widget_template = 'admin/controller_widgets/lock.html'
|
|
764
764
|
|
|
765
|
+
UNLOCKED = 0
|
|
766
|
+
LOCKED = 1
|
|
767
|
+
LOCKING = 2
|
|
768
|
+
UNLOCKING = 3
|
|
769
|
+
FAULT = 4
|
|
770
|
+
|
|
765
771
|
def lock(self):
|
|
766
772
|
self.turn_on()
|
|
767
773
|
|
|
@@ -769,11 +775,20 @@ class Lock(Switch):
|
|
|
769
775
|
self.turn_off()
|
|
770
776
|
|
|
771
777
|
def _receive_from_device(self, value, is_alive=True):
|
|
772
|
-
if type(value)
|
|
778
|
+
if type(value) == bool:
|
|
773
779
|
if value:
|
|
774
780
|
value = 'locked'
|
|
775
781
|
else:
|
|
776
782
|
value = 'unlocked'
|
|
783
|
+
if type(value) == int:
|
|
784
|
+
values_map = {
|
|
785
|
+
self.UNLOCKED: 'unlocked',
|
|
786
|
+
self.LOCKED: 'locked',
|
|
787
|
+
self.LOCKING: 'locking',
|
|
788
|
+
self.UNLOCKING: 'unlocking',
|
|
789
|
+
self.FAULT: 'fault'
|
|
790
|
+
}
|
|
791
|
+
value = values_map.get(value, 'fault')
|
|
777
792
|
return super()._receive_from_device(value, is_alive=is_alive)
|
|
778
793
|
|
|
779
794
|
def _validate_val(self, value, occasion=None):
|
simo/core/socket_consumers.py
CHANGED
|
@@ -7,6 +7,7 @@ from django.conf import settings
|
|
|
7
7
|
from django.contrib.contenttypes.models import ContentType
|
|
8
8
|
from channels.generic.websocket import AsyncWebsocketConsumer, WebsocketConsumer
|
|
9
9
|
from simo.core.events import ObjectChangeEvent, get_event_obj
|
|
10
|
+
from simo.core.utils.logs import capture_socket_errors
|
|
10
11
|
import paho.mqtt.client as mqtt
|
|
11
12
|
from simo.users.middleware import introduce
|
|
12
13
|
from simo.core.models import Component, Gateway
|
|
@@ -14,6 +15,7 @@ from simo.core.utils.model_helpers import get_log_file_path
|
|
|
14
15
|
from simo.core.middleware import introduce_instance
|
|
15
16
|
|
|
16
17
|
|
|
18
|
+
@capture_socket_errors
|
|
17
19
|
class SIMOWebsocketConsumer(WebsocketConsumer):
|
|
18
20
|
headers = {}
|
|
19
21
|
|
|
@@ -24,6 +26,7 @@ class SIMOWebsocketConsumer(WebsocketConsumer):
|
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
|
|
29
|
+
@capture_socket_errors
|
|
27
30
|
class LogConsumer(AsyncWebsocketConsumer):
|
|
28
31
|
log_file = None
|
|
29
32
|
in_error = False
|
|
@@ -123,6 +126,7 @@ class LogConsumer(AsyncWebsocketConsumer):
|
|
|
123
126
|
self.log_file = None
|
|
124
127
|
|
|
125
128
|
|
|
129
|
+
@capture_socket_errors
|
|
126
130
|
class GatewayController(SIMOWebsocketConsumer):
|
|
127
131
|
gateway = None
|
|
128
132
|
_mqtt_client = None
|
|
@@ -178,6 +182,7 @@ class GatewayController(SIMOWebsocketConsumer):
|
|
|
178
182
|
pass
|
|
179
183
|
|
|
180
184
|
|
|
185
|
+
@capture_socket_errors
|
|
181
186
|
class ComponentController(SIMOWebsocketConsumer):
|
|
182
187
|
component = None
|
|
183
188
|
send_value = False
|
simo/core/utils/logs.py
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
from functools import wraps
|
|
3
|
+
from inspect import iscoroutinefunction
|
|
4
|
+
from logging import getLogger
|
|
5
|
+
from channels.exceptions import AcceptConnection, DenyConnection, StopConsumer
|
|
6
|
+
|
|
7
|
+
logger = getLogger()
|
|
2
8
|
|
|
3
9
|
|
|
4
10
|
class StreamToLogger(object):
|
|
@@ -28,3 +34,31 @@ class StreamToLogger(object):
|
|
|
28
34
|
if self.linebuf != '':
|
|
29
35
|
self.logger.log(self.log_level, self.linebuf.rstrip())
|
|
30
36
|
self.linebuf = ''
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def propagate_exceptions(func):
|
|
42
|
+
async def wrapper(*args, **kwargs): # we're wrapping an async function
|
|
43
|
+
try:
|
|
44
|
+
return await func(*args, **kwargs)
|
|
45
|
+
except (AcceptConnection, DenyConnection, StopConsumer): # these are handled by channels
|
|
46
|
+
raise
|
|
47
|
+
except Exception as exception: # any other exception
|
|
48
|
+
# avoid logging the same exception multiple times
|
|
49
|
+
if not getattr(exception, "caught", False):
|
|
50
|
+
setattr(exception, "caught", True)
|
|
51
|
+
logger.error(
|
|
52
|
+
"Exception occurred in {}:".format(func.__qualname__),
|
|
53
|
+
exc_info=exception,
|
|
54
|
+
)
|
|
55
|
+
raise # propagate the exception
|
|
56
|
+
return wraps(func)(wrapper)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def capture_socket_errors(consumer_class):
|
|
60
|
+
for method_name, method in list(consumer_class.__dict__.items()):
|
|
61
|
+
if iscoroutinefunction(method): # an async method
|
|
62
|
+
# wrap the method with a decorator that propagate exceptions
|
|
63
|
+
setattr(consumer_class, method_name, propagate_exceptions(method))
|
|
64
|
+
return consumer_class
|
simo/fleet/admin.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from threading import Timer
|
|
1
2
|
from django.contrib import admin
|
|
2
3
|
from django.utils.safestring import mark_safe
|
|
3
4
|
from django.template.loader import render_to_string
|
|
@@ -78,6 +79,15 @@ class ColonelAdmin(admin.ModelAdmin):
|
|
|
78
79
|
return qs
|
|
79
80
|
return qs.filter(instance__in=request.user.instances)
|
|
80
81
|
|
|
82
|
+
def save_model(self, request, obj, form, change):
|
|
83
|
+
super().save_model(request, obj, form, change)
|
|
84
|
+
# give it one second to finish up with atomic transaction and
|
|
85
|
+
# send update_config command.
|
|
86
|
+
def update_colonel_config(colonel):
|
|
87
|
+
colonel.update_config()
|
|
88
|
+
Timer(1, update_colonel_config, [obj]).start()
|
|
89
|
+
|
|
90
|
+
|
|
81
91
|
def has_add_permission(self, request):
|
|
82
92
|
return False
|
|
83
93
|
|
simo/fleet/controllers.py
CHANGED
|
@@ -308,6 +308,8 @@ class TTLock(FleeDeviceMixin, Lock):
|
|
|
308
308
|
@classmethod
|
|
309
309
|
def _process_discovery(cls, started_with, data):
|
|
310
310
|
if data['discover-ttlock'] == 'fail':
|
|
311
|
+
if data['result'] == 0:
|
|
312
|
+
return {'error': 'Internal Colonel error. See Colonel logs.'}
|
|
311
313
|
if data['result'] == 1:
|
|
312
314
|
return {'error': 'TTLock not found.'}
|
|
313
315
|
elif data['result'] == 2:
|
|
@@ -7,7 +7,7 @@ from django.db import migrations
|
|
|
7
7
|
|
|
8
8
|
def forwards_func(apps, schema_editor):
|
|
9
9
|
from simo.fleet.utils import GPIO_PINS
|
|
10
|
-
from django.contrib.contenttypes.models import ContentType
|
|
10
|
+
#from django.contrib.contenttypes.models import ContentType
|
|
11
11
|
|
|
12
12
|
Colonel = apps.get_model('fleet', "Colonel")
|
|
13
13
|
ColonelPin = apps.get_model("fleet", "ColonelPin")
|
|
@@ -26,18 +26,18 @@ def forwards_func(apps, schema_editor):
|
|
|
26
26
|
)
|
|
27
27
|
|
|
28
28
|
for i2c in colonel.i2c_interfaces.all():
|
|
29
|
-
ct = ContentType.objects.get_for_model(i2c)
|
|
29
|
+
#ct = ContentType.objects.get_for_model(i2c)
|
|
30
30
|
|
|
31
31
|
scl_pin = new_pins[colonel.id][i2c.scl_pin_no]
|
|
32
|
-
scl_pin.occupied_by_content_type = ct
|
|
33
|
-
scl_pin.occupied_by_id = i2c.id
|
|
32
|
+
#scl_pin.occupied_by_content_type = ct
|
|
33
|
+
#scl_pin.occupied_by_id = i2c.id
|
|
34
34
|
scl_pin.save()
|
|
35
35
|
i2c.scl_pin = scl_pin
|
|
36
36
|
i2c.scl_pin.save()
|
|
37
37
|
|
|
38
38
|
sda_pin = new_pins[colonel.id][i2c.sda_pin_no]
|
|
39
|
-
sda_pin.occupied_by_content_type = ct
|
|
40
|
-
sda_pin.occupied_by_id = i2c.id
|
|
39
|
+
#sda_pin.occupied_by_content_type = ct
|
|
40
|
+
#sda_pin.occupied_by_id = i2c.id
|
|
41
41
|
sda_pin.save()
|
|
42
42
|
|
|
43
43
|
i2c.sda_pin = sda_pin
|
|
@@ -48,7 +48,7 @@ def forwards_func(apps, schema_editor):
|
|
|
48
48
|
for comp in Component.objects.filter(
|
|
49
49
|
controller_uid__startswith='simo.fleet.'
|
|
50
50
|
):
|
|
51
|
-
ct = ContentType.objects.get_for_model(comp)
|
|
51
|
+
#ct = ContentType.objects.get_for_model(comp)
|
|
52
52
|
try:
|
|
53
53
|
colonel = Colonel.objects.get(id=comp.config['colonel'])
|
|
54
54
|
except Exception as e:
|
|
@@ -64,8 +64,8 @@ def forwards_func(apps, schema_editor):
|
|
|
64
64
|
|
|
65
65
|
pin = new_pins[colonel.id][comp.config['pin_no']]
|
|
66
66
|
comp.config['pin'] = pin.pk
|
|
67
|
-
pin.occupied_by_content_type = ct
|
|
68
|
-
pin.occupied_by_id = comp.id
|
|
67
|
+
#pin.occupied_by_content_type = ct
|
|
68
|
+
#pin.occupied_by_id = comp.id
|
|
69
69
|
pin.save()
|
|
70
70
|
if 'power_pin' in comp.config:
|
|
71
71
|
comp.config['power_pin_no'] = comp.config.pop('power_pin')
|
|
@@ -77,8 +77,8 @@ def forwards_func(apps, schema_editor):
|
|
|
77
77
|
|
|
78
78
|
pin = new_pins[colonel.id][comp.config['power_pin_no']]
|
|
79
79
|
comp.config['power_pin'] = pin.pk
|
|
80
|
-
pin.occupied_by_content_type = ct
|
|
81
|
-
pin.occupied_by_id = comp.id
|
|
80
|
+
#pin.occupied_by_content_type = ct
|
|
81
|
+
#pin.occupied_by_id = comp.id
|
|
82
82
|
pin.save()
|
|
83
83
|
if 'sensor_pin' in comp.config:
|
|
84
84
|
comp.config['sensor_pin_no'] = comp.config.pop('sensor_pin')
|
|
@@ -90,8 +90,8 @@ def forwards_func(apps, schema_editor):
|
|
|
90
90
|
|
|
91
91
|
pin = new_pins[colonel.id][comp.config['sensor_pin_no']]
|
|
92
92
|
comp.config['sensor_pin'] = pin.pk
|
|
93
|
-
pin.occupied_by_content_type = ct
|
|
94
|
-
pin.occupied_by_id = comp.id
|
|
93
|
+
#pin.occupied_by_content_type = ct
|
|
94
|
+
#pin.occupied_by_id = comp.id
|
|
95
95
|
pin.save()
|
|
96
96
|
if 'output_pin' in comp.config:
|
|
97
97
|
comp.config['output_pin_no'] = comp.config.pop('output_pin')
|
|
@@ -103,8 +103,8 @@ def forwards_func(apps, schema_editor):
|
|
|
103
103
|
|
|
104
104
|
pin = new_pins[colonel.id][comp.config['output_pin_no']]
|
|
105
105
|
comp.config['output_pin'] = pin.pk
|
|
106
|
-
pin.occupied_by_content_type = ct
|
|
107
|
-
pin.occupied_by_id = comp.id
|
|
106
|
+
#pin.occupied_by_content_type = ct
|
|
107
|
+
#pin.occupied_by_id = comp.id
|
|
108
108
|
pin.save()
|
|
109
109
|
if 'open_pin' in comp.config:
|
|
110
110
|
comp.config['open_pin_no'] = comp.config.pop('open_pin')
|
|
@@ -116,8 +116,8 @@ def forwards_func(apps, schema_editor):
|
|
|
116
116
|
|
|
117
117
|
pin = new_pins[colonel.id][comp.config['open_pin_no']]
|
|
118
118
|
comp.config['open_pin'] = pin.pk
|
|
119
|
-
pin.occupied_by_content_type = ct
|
|
120
|
-
pin.occupied_by_id = comp.id
|
|
119
|
+
#pin.occupied_by_content_type = ct
|
|
120
|
+
#pin.occupied_by_id = comp.id
|
|
121
121
|
pin.save()
|
|
122
122
|
if 'close_pin' in comp.config:
|
|
123
123
|
comp.config['close_pin_no'] = comp.config.pop('close_pin')
|
|
@@ -129,8 +129,8 @@ def forwards_func(apps, schema_editor):
|
|
|
129
129
|
|
|
130
130
|
pin = new_pins[colonel.id][comp.config['close_pin_no']]
|
|
131
131
|
comp.config['close_pin'] = pin.pk
|
|
132
|
-
pin.occupied_by_content_type = ct
|
|
133
|
-
pin.occupied_by_id = comp.id
|
|
132
|
+
#pin.occupied_by_content_type = ct
|
|
133
|
+
#pin.occupied_by_id = comp.id
|
|
134
134
|
pin.save()
|
|
135
135
|
|
|
136
136
|
if 'controls' in comp.config:
|
|
@@ -146,8 +146,8 @@ def forwards_func(apps, schema_editor):
|
|
|
146
146
|
pin = new_pins[colonel.id][updated_controls['pin_no']]
|
|
147
147
|
updated_controls['pin'] = pin.pk
|
|
148
148
|
|
|
149
|
-
pin.occupied_by_content_type = ct
|
|
150
|
-
pin.occupied_by_id = comp.id
|
|
149
|
+
#pin.occupied_by_content_type = ct
|
|
150
|
+
#pin.occupied_by_id = comp.id
|
|
151
151
|
pin.save()
|
|
152
152
|
|
|
153
153
|
comp.config['controls'][i] = updated_controls
|
simo/fleet/models.py
CHANGED
|
@@ -82,8 +82,12 @@ class Colonel(DirtyFieldsMixin, models.Model):
|
|
|
82
82
|
occupied_pins = models.JSONField(default=dict, blank=True)
|
|
83
83
|
|
|
84
84
|
logs_stream = models.BooleanField(
|
|
85
|
-
default=False, help_text="
|
|
86
|
-
"
|
|
85
|
+
default=False, help_text="ATENTION! Causes serious overhead and "
|
|
86
|
+
"significantly degrades the lifespan of a chip "
|
|
87
|
+
"due to a lot of writes to the memory. "
|
|
88
|
+
"It also causes Colonel websocket to run out of memory "
|
|
89
|
+
"and reset if a lot of data is being transmitted. "
|
|
90
|
+
"Leave this off, unleess you know what you are doing!"
|
|
87
91
|
)
|
|
88
92
|
pwm_frequency = models.IntegerField(default=1, choices=(
|
|
89
93
|
(0, "3kHz"), (1, "22kHz")
|
|
@@ -161,10 +165,8 @@ class Colonel(DirtyFieldsMixin, models.Model):
|
|
|
161
165
|
@transaction.atomic
|
|
162
166
|
def rebuild_occupied_pins(self):
|
|
163
167
|
for pin in ColonelPin.objects.filter(colonel=self):
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
pin.occupied_by_id = None
|
|
167
|
-
pin.save()
|
|
168
|
+
pin.occupied_by = None
|
|
169
|
+
pin.save()
|
|
168
170
|
|
|
169
171
|
for component in self.components.all():
|
|
170
172
|
try:
|
|
@@ -176,6 +178,12 @@ class Colonel(DirtyFieldsMixin, models.Model):
|
|
|
176
178
|
pin.occupied_by = component
|
|
177
179
|
pin.save()
|
|
178
180
|
|
|
181
|
+
for interface in self.i2c_interfaces.all():
|
|
182
|
+
interface.sda_pin.occupied_by = interface
|
|
183
|
+
interface.sda_pin.save()
|
|
184
|
+
interface.scl_pin.occupied_by = interface
|
|
185
|
+
interface.scl_pin.save()
|
|
186
|
+
|
|
179
187
|
|
|
180
188
|
@transaction.atomic()
|
|
181
189
|
def move_to(self, other_colonel):
|
|
@@ -190,13 +198,11 @@ class Colonel(DirtyFieldsMixin, models.Model):
|
|
|
190
198
|
self.components.remove(component)
|
|
191
199
|
other_colonel.components.add(component)
|
|
192
200
|
|
|
193
|
-
self.rebuild_occupied_pins()
|
|
194
|
-
other_colonel.rebuild_occupied_pins()
|
|
195
|
-
|
|
196
201
|
other_colonel.i2c_interfaces.all().delete()
|
|
197
202
|
|
|
198
203
|
for i2c_interface in self.i2c_interfaces.all():
|
|
199
204
|
I2CInterface.objects.create(
|
|
205
|
+
no=i2c_interface.no,
|
|
200
206
|
colonel=other_colonel, name=i2c_interface.name,
|
|
201
207
|
freq=i2c_interface.freq,
|
|
202
208
|
scl_pin=ColonelPin.objects.get(
|
|
@@ -207,6 +213,8 @@ class Colonel(DirtyFieldsMixin, models.Model):
|
|
|
207
213
|
),
|
|
208
214
|
)
|
|
209
215
|
|
|
216
|
+
self.rebuild_occupied_pins()
|
|
217
|
+
other_colonel.rebuild_occupied_pins()
|
|
210
218
|
self.update_config()
|
|
211
219
|
other_colonel.update_config()
|
|
212
220
|
|
|
@@ -263,10 +271,7 @@ class ColonelPin(models.Model):
|
|
|
263
271
|
|
|
264
272
|
@receiver(post_save, sender=Colonel)
|
|
265
273
|
def after_colonel_save(sender, instance, created, *args, **kwargs):
|
|
266
|
-
if
|
|
267
|
-
return
|
|
268
|
-
|
|
269
|
-
def after_update():
|
|
274
|
+
if created:
|
|
270
275
|
for no, data in GPIO_PINS.get(instance.type).items():
|
|
271
276
|
ColonelPin.objects.get_or_create(
|
|
272
277
|
colonel=instance, no=no,
|
|
@@ -280,9 +285,6 @@ def after_colonel_save(sender, instance, created, *args, **kwargs):
|
|
|
280
285
|
scl_pin=ColonelPin.objects.get(colonel=instance, no=4),
|
|
281
286
|
sda_pin=ColonelPin.objects.get(colonel=instance, no=15),
|
|
282
287
|
)
|
|
283
|
-
instance.update_config()
|
|
284
|
-
|
|
285
|
-
transaction.on_commit(after_update)
|
|
286
288
|
|
|
287
289
|
|
|
288
290
|
@receiver(pre_delete, sender=Component)
|
simo/fleet/serializers.py
CHANGED
|
@@ -45,3 +45,8 @@ class ColonelSerializer(serializers.ModelSerializer):
|
|
|
45
45
|
for pin in obj.pins.all():
|
|
46
46
|
result.append(ColonelPinSerializer(pin).data)
|
|
47
47
|
return result
|
|
48
|
+
|
|
49
|
+
def update(self, instance, validated_data):
|
|
50
|
+
instance = super().update(instance, validated_data)
|
|
51
|
+
instance.update_config()
|
|
52
|
+
return instance
|
simo/fleet/socket_consumers.py
CHANGED
|
@@ -13,15 +13,18 @@ import paho.mqtt.client as mqtt
|
|
|
13
13
|
from channels.generic.websocket import AsyncWebsocketConsumer
|
|
14
14
|
from asgiref.sync import sync_to_async
|
|
15
15
|
from simo.core.utils.model_helpers import get_log_file_path
|
|
16
|
+
from simo.core.utils.logs import capture_socket_errors
|
|
16
17
|
from simo.core.events import GatewayObjectCommand, get_event_obj
|
|
17
18
|
from simo.core.models import Gateway, Instance, Component
|
|
18
19
|
from simo.conf import dynamic_settings
|
|
19
20
|
from simo.users.models import Fingerprint
|
|
21
|
+
|
|
20
22
|
from .gateways import FleetGatewayHandler
|
|
21
23
|
from .models import Colonel
|
|
22
24
|
from .controllers import TTLock
|
|
23
25
|
|
|
24
26
|
|
|
27
|
+
@capture_socket_errors
|
|
25
28
|
class FleetConsumer(AsyncWebsocketConsumer):
|
|
26
29
|
colonel = None
|
|
27
30
|
colonel_logger = None
|
|
@@ -345,106 +348,110 @@ class FleetConsumer(AsyncWebsocketConsumer):
|
|
|
345
348
|
|
|
346
349
|
|
|
347
350
|
async def receive(self, text_data=None, bytes_data=None):
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
351
|
+
try:
|
|
352
|
+
if text_data:
|
|
353
|
+
print(f"{self.colonel}: {text_data}")
|
|
354
|
+
data = json.loads(text_data)
|
|
355
|
+
if 'get_config' in data:
|
|
356
|
+
config = await self.get_config_data()
|
|
357
|
+
print("Send config: ", config)
|
|
358
|
+
await self.send_data({
|
|
359
|
+
'command': 'set_config', 'data': config
|
|
360
|
+
}, compress=True)
|
|
361
|
+
elif 'comp' in data:
|
|
359
362
|
try:
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
await sync_to_async(
|
|
379
|
-
receive_val, thread_sensitive=True
|
|
380
|
-
)(data['val'])
|
|
381
|
-
|
|
382
|
-
if 'options' in data:
|
|
383
|
-
def receive_options(val):
|
|
384
|
-
component.meta['options'] = val
|
|
385
|
-
component.save()
|
|
386
|
-
await sync_to_async(
|
|
387
|
-
receive_options, thread_sensitive=True
|
|
388
|
-
)(data['options'])
|
|
389
|
-
|
|
390
|
-
if 'codes' in data and component.controller_uid == TTLock.uid:
|
|
391
|
-
def save_codes(codes):
|
|
392
|
-
component.meta['codes'] = codes
|
|
393
|
-
for code in codes:
|
|
394
|
-
Fingerprint.objects.get_or_create(
|
|
395
|
-
value=f"ttlock-{component.id}-code-{str(code)}",
|
|
396
|
-
defaults={'type': "TTLock code"}
|
|
397
|
-
)
|
|
398
|
-
component.save()
|
|
399
|
-
await sync_to_async(
|
|
400
|
-
save_codes, thread_sensitive=True
|
|
401
|
-
)(data['codes'])
|
|
402
|
-
if 'fingerprints' in data and component.controller_uid == TTLock.uid:
|
|
403
|
-
def save_codes(codes):
|
|
404
|
-
component.meta['fingerprints'] = codes
|
|
405
|
-
for code in codes:
|
|
406
|
-
Fingerprint.objects.get_or_create(
|
|
407
|
-
value=f"ttlock-{component.id}-finger-{str(code)}",
|
|
408
|
-
defaults={'type': "TTLock Fingerprint"}
|
|
363
|
+
try:
|
|
364
|
+
id=int(data['comp'])
|
|
365
|
+
except:
|
|
366
|
+
return
|
|
367
|
+
|
|
368
|
+
component = await sync_to_async(
|
|
369
|
+
Component.objects.get, thread_sensitive=True
|
|
370
|
+
)(id=id)
|
|
371
|
+
|
|
372
|
+
if 'val' in data:
|
|
373
|
+
def receive_val(val):
|
|
374
|
+
if data.get('actor'):
|
|
375
|
+
fingerprint = Fingerprint.objects.filter(
|
|
376
|
+
value=f"ttlock-{component.id}-{data.get('actor')}",
|
|
377
|
+
).first()
|
|
378
|
+
component.change_init_fingerprint = fingerprint
|
|
379
|
+
component.controller._receive_from_device(
|
|
380
|
+
val, bool(data.get('alive'))
|
|
409
381
|
)
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
382
|
+
await sync_to_async(
|
|
383
|
+
receive_val, thread_sensitive=True
|
|
384
|
+
)(data['val'])
|
|
385
|
+
|
|
386
|
+
if 'options' in data:
|
|
387
|
+
def receive_options(val):
|
|
388
|
+
component.meta['options'] = val
|
|
389
|
+
component.save()
|
|
390
|
+
await sync_to_async(
|
|
391
|
+
receive_options, thread_sensitive=True
|
|
392
|
+
)(data['options'])
|
|
393
|
+
|
|
394
|
+
if 'codes' in data and component.controller_uid == TTLock.uid:
|
|
395
|
+
def save_codes(codes):
|
|
396
|
+
component.meta['codes'] = codes
|
|
397
|
+
for code in codes:
|
|
398
|
+
Fingerprint.objects.get_or_create(
|
|
399
|
+
value=f"ttlock-{component.id}-code-{str(code)}",
|
|
400
|
+
defaults={'type': "TTLock code"}
|
|
401
|
+
)
|
|
402
|
+
component.save()
|
|
403
|
+
await sync_to_async(
|
|
404
|
+
save_codes, thread_sensitive=True
|
|
405
|
+
)(data['codes'])
|
|
406
|
+
if 'fingerprints' in data and component.controller_uid == TTLock.uid:
|
|
407
|
+
def save_codes(codes):
|
|
408
|
+
component.meta['fingerprints'] = codes
|
|
409
|
+
for code in codes:
|
|
410
|
+
Fingerprint.objects.get_or_create(
|
|
411
|
+
value=f"ttlock-{component.id}-finger-{str(code)}",
|
|
412
|
+
defaults={'type': "TTLock Fingerprint"}
|
|
413
|
+
)
|
|
414
|
+
component.save()
|
|
415
|
+
await sync_to_async(
|
|
416
|
+
save_codes, thread_sensitive=True
|
|
417
|
+
)(data['fingerprints'])
|
|
418
|
+
|
|
427
419
|
except Exception as e:
|
|
428
420
|
print(traceback.format_exc(), file=sys.stderr)
|
|
429
|
-
self.gateway.finish_discovery()
|
|
430
421
|
|
|
431
|
-
|
|
432
|
-
process_discovery_result
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
422
|
+
elif 'discover-ttlock' in data:
|
|
423
|
+
def process_discovery_result():
|
|
424
|
+
self.gateway.refresh_from_db()
|
|
425
|
+
if self.gateway.discovery.get('finished'):
|
|
426
|
+
return Component.objects.filter(
|
|
427
|
+
meta__finalization_data__temp_id=data['result']['id']
|
|
428
|
+
).first()
|
|
429
|
+
try:
|
|
430
|
+
self.gateway.process_discovery(data)
|
|
431
|
+
except Exception as e:
|
|
432
|
+
print(traceback.format_exc(), file=sys.stderr)
|
|
433
|
+
self.gateway.finish_discovery()
|
|
434
|
+
|
|
435
|
+
finished_comp = await sync_to_async(
|
|
436
|
+
process_discovery_result, thread_sensitive=True
|
|
437
|
+
)()
|
|
438
|
+
if finished_comp:
|
|
439
|
+
await self.send_data({
|
|
440
|
+
'command': 'finalize',
|
|
441
|
+
'data': finished_comp.meta['finalization_data']
|
|
442
|
+
})
|
|
439
443
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
444
|
+
elif bytes_data:
|
|
445
|
+
if not self.colonel_logger:
|
|
446
|
+
await self.start_logger()
|
|
443
447
|
|
|
444
|
-
|
|
445
|
-
|
|
448
|
+
for logline in bytes_data.decode(errors='replace').split('\n'):
|
|
449
|
+
self.colonel_logger.log(logging.INFO, logline)
|
|
450
|
+
|
|
451
|
+
await self.log_colonel_connected()
|
|
452
|
+
except Exception as e:
|
|
453
|
+
print(traceback.format_exc(), file=sys.stderr)
|
|
446
454
|
|
|
447
|
-
await self.log_colonel_connected()
|
|
448
455
|
|
|
449
456
|
|
|
450
457
|
async def log_colonel_connected(self):
|
simo/generic/forms.py
CHANGED
|
@@ -167,6 +167,12 @@ class AlarmGroupConfigForm(BaseComponentForm):
|
|
|
167
167
|
required=False,
|
|
168
168
|
help_text="Defines if this is your main/top global alarm group."
|
|
169
169
|
)
|
|
170
|
+
notify_on_breach = forms.IntegerField(
|
|
171
|
+
required=False, min_value=0,
|
|
172
|
+
help_text="Notify active users if "
|
|
173
|
+
"not disarmed within given number of seconds "
|
|
174
|
+
"after the breached."
|
|
175
|
+
)
|
|
170
176
|
has_alarm = False
|
|
171
177
|
|
|
172
178
|
|
simo/generic/models.py
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
from django.db import models
|
|
2
1
|
from threading import Timer
|
|
3
|
-
from django.utils.translation import gettext_lazy as _
|
|
4
2
|
from django.db.models.signals import pre_save, post_save, post_delete
|
|
5
3
|
from django.dispatch import receiver
|
|
6
4
|
from simo.core.models import Instance, Component
|
|
@@ -20,7 +18,8 @@ def handle_alarm_groups(sender, instance, *args, **kwargs):
|
|
|
20
18
|
|
|
21
19
|
for alarm_group in Component.objects.filter(
|
|
22
20
|
controller_uid=AlarmGroup.uid,
|
|
23
|
-
config__components__contains=instance.id
|
|
21
|
+
config__components__contains=instance.id,
|
|
22
|
+
config__notify_on_breach__gt=-1
|
|
24
23
|
).exclude(value='disarmed'):
|
|
25
24
|
stats = {
|
|
26
25
|
'disarmed': 0, 'pending-arm': 0, 'armed': 0, 'breached': 0
|
|
@@ -33,7 +32,6 @@ def handle_alarm_groups(sender, instance, *args, **kwargs):
|
|
|
33
32
|
alarm_group.config['stats'] = stats
|
|
34
33
|
alarm_group.save(update_fields=['config'])
|
|
35
34
|
|
|
36
|
-
alarm_group_value = alarm_group.value
|
|
37
35
|
if stats['disarmed'] == len(alarm_group.config['components']):
|
|
38
36
|
alarm_group_value = 'disarmed'
|
|
39
37
|
elif stats['armed'] == len(alarm_group.config['components']):
|
|
@@ -41,9 +39,11 @@ def handle_alarm_groups(sender, instance, *args, **kwargs):
|
|
|
41
39
|
elif stats['breached']:
|
|
42
40
|
if alarm_group.value != 'breached':
|
|
43
41
|
def notify_users_security_breach(alarm_group_component_id):
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
alarm_group_component = Component.objects.filter(
|
|
43
|
+
id=alarm_group_component_id, value='breached'
|
|
44
|
+
).first()
|
|
45
|
+
if not alarm_group_component:
|
|
46
|
+
return
|
|
47
47
|
breached_components = Component.objects.filter(
|
|
48
48
|
pk__in=alarm_group_component.config['components'],
|
|
49
49
|
arm_status='breached'
|
|
@@ -51,16 +51,22 @@ def handle_alarm_groups(sender, instance, *args, **kwargs):
|
|
|
51
51
|
body = "Security Breach! " + '; '.join(
|
|
52
52
|
[str(c) for c in breached_components]
|
|
53
53
|
)
|
|
54
|
+
from simo.notifications.utils import notify_users
|
|
54
55
|
notify_users(
|
|
56
|
+
alarm_group_component.zone.instance,
|
|
55
57
|
'alarm', str(alarm_group_component), body,
|
|
56
58
|
component=alarm_group_component
|
|
57
59
|
)
|
|
58
|
-
t = Timer(
|
|
60
|
+
t = Timer(
|
|
61
|
+
# give it one second to finish with other db processes.
|
|
62
|
+
alarm_group.config['notify_on_breach'] + 1,
|
|
63
|
+
notify_users_security_breach, [alarm_group.id]
|
|
64
|
+
)
|
|
59
65
|
t.start()
|
|
60
66
|
alarm_group_value = 'breached'
|
|
61
67
|
else:
|
|
62
68
|
alarm_group_value = 'pending-arm'
|
|
63
|
-
alarm_group.set(alarm_group_value)
|
|
69
|
+
alarm_group.controller.set(alarm_group_value)
|
|
64
70
|
|
|
65
71
|
|
|
66
72
|
@receiver(post_save, sender=Component)
|
simo/notifications/utils.py
CHANGED
|
@@ -14,7 +14,7 @@ def notify_users(instance, severity, title, body=None, component=None, users=Non
|
|
|
14
14
|
)
|
|
15
15
|
if not users:
|
|
16
16
|
users = User.objects.filter(
|
|
17
|
-
|
|
17
|
+
instance_roles__instance=instance,
|
|
18
18
|
instance_roles__is_active=True
|
|
19
19
|
)
|
|
20
20
|
for user in users:
|
|
@@ -34,7 +34,7 @@ simo/core/auto_urls.py,sha256=0gu-IL7PHobrmKW6ksffiOkAYu-aIorykWdxRNtwGYo,1194
|
|
|
34
34
|
simo/core/autocomplete_views.py,sha256=JT5LA2_Wtr60XYSAIqaXFKFYPjrmkEf6yunXD9y2zco,4022
|
|
35
35
|
simo/core/base_types.py,sha256=yqbIZqBksrAkEuHRbt6iExwPDDy0K5II2NzRCkmOvMU,589
|
|
36
36
|
simo/core/context.py,sha256=98PXAMie43faRVBFkOG22uNpvGRNprcGhzjBFkrxaRY,1367
|
|
37
|
-
simo/core/controllers.py,sha256=
|
|
37
|
+
simo/core/controllers.py,sha256=7M28j0I2Eh-Q7jIXZ7FMkNQoA7xluu67NSXhJXaW4gs,27018
|
|
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
|
|
@@ -48,7 +48,7 @@ simo/core/permissions.py,sha256=UmFjGPDWtAUbaWxJsWORb2q6BREHqndv9mkSIpnmdLk,1379
|
|
|
48
48
|
simo/core/routing.py,sha256=X1_IHxyA-_Q7hw1udDoviVP4_FSBDl8GYETTC2zWTbY,499
|
|
49
49
|
simo/core/serializers.py,sha256=bkfXZgUzbXZOrJY69VIevBHNLWRd7DmgyFRh4arr-gs,15810
|
|
50
50
|
simo/core/signal_receivers.py,sha256=EZ8NSYZxUgSaLS16YZdK7T__l8dl0joMRllOxx5PUt4,2809
|
|
51
|
-
simo/core/socket_consumers.py,sha256=
|
|
51
|
+
simo/core/socket_consumers.py,sha256=n7VE2Fvqt4iEAYLTRbTPOcI-7tszMAADu7gimBxB-Fg,9635
|
|
52
52
|
simo/core/storage.py,sha256=YlxmdRs-zhShWtFKgpJ0qp2NDBuIkJGYC1OJzqkbttQ,572
|
|
53
53
|
simo/core/tasks.py,sha256=se27V-noW02v4ZY2PMv0AJkXNsY3NtJ4G43__KLW7Kg,11005
|
|
54
54
|
simo/core/todos.py,sha256=eYVXfLGiapkxKK57XuviSNe3WsUYyIWZ0hgQJk7ThKo,665
|
|
@@ -10110,7 +10110,7 @@ simo/core/utils/form_fields.py,sha256=UOzYdPd71qgCw1H3qH01u85YjrOlETPJAHOJrZKhyD
|
|
|
10110
10110
|
simo/core/utils/form_widgets.py,sha256=Zxn9jJqPle9Q_BKNJnyTDn7MosYwNp1TFu5LoKs0bfc,408
|
|
10111
10111
|
simo/core/utils/formsets.py,sha256=1u34QGZ2P67cxZD2uUJS3lAf--E8XsiiqFmZ4P41Vw4,6463
|
|
10112
10112
|
simo/core/utils/helpers.py,sha256=TOWy3slspaEYEhe9zDcb0RgzHUYslF6LZDlrWPGSqUI,3791
|
|
10113
|
-
simo/core/utils/logs.py,sha256=
|
|
10113
|
+
simo/core/utils/logs.py,sha256=Zn9JQxqCH9Odx2J1BWT84nFCfkJ4Z4p5X8psdll7hNc,2366
|
|
10114
10114
|
simo/core/utils/mixins.py,sha256=X6kUPKAi_F-uw7tgm8LEaYalBXpvDA-yrLNFCGr2rks,259
|
|
10115
10115
|
simo/core/utils/model_helpers.py,sha256=3IzJeOvBoYdUJVXCJkY20npOZXPjNPAiEFvuT0OPhwA,884
|
|
10116
10116
|
simo/core/utils/relay.py,sha256=i1xy_nPTgY5Xn0l2W4lNI3xeVUpDQTUUfV3M8h2DeBg,457
|
|
@@ -10120,18 +10120,18 @@ simo/core/utils/validators.py,sha256=FRO6_K5HAO1OaC-LosApZjh-W3EA-IJZ53HnwEJgqiI
|
|
|
10120
10120
|
simo/core/utils/__pycache__/api.cpython-38.pyc,sha256=CuJq9GKQC8gbeCxmH2wQHZUmkIihVILSEIAoVYCFwH0,1575
|
|
10121
10121
|
simo/core/utils/__pycache__/serialization.cpython-38.pyc,sha256=zOo2M97bAC0Ed-iiNoVtcHvgdAYR8RwrF2bUwuf8Pus,1145
|
|
10122
10122
|
simo/fleet/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10123
|
-
simo/fleet/admin.py,sha256=
|
|
10123
|
+
simo/fleet/admin.py,sha256=Vxp3yTNRVoDif_hwwBW3weeMNakQ3iZo-GC0VvdScfQ,5699
|
|
10124
10124
|
simo/fleet/api.py,sha256=Hxn84xI-Q77HxjINgRbjSJQOv9jii4OL20LxK0VSrS8,2499
|
|
10125
10125
|
simo/fleet/auto_urls.py,sha256=gAXTWUvsWkQHRdZGM_W_5iJBEsM4lY063kIx3f5LUqs,578
|
|
10126
10126
|
simo/fleet/ble.py,sha256=eHA_9ABjbmH1vUVCv9hiPXQL2GZZSEVwfO0xyI1S0nI,1081
|
|
10127
|
-
simo/fleet/controllers.py,sha256=
|
|
10127
|
+
simo/fleet/controllers.py,sha256=N8Qzdp2RPFrpZ_l9O4u8VjHWoY_WTWGg76s3V3oJqEs,13999
|
|
10128
10128
|
simo/fleet/forms.py,sha256=UGj1mK2Zbl2LRlvLtEDObeGfC2wcuHleRbePo1_Vx6I,34972
|
|
10129
10129
|
simo/fleet/gateways.py,sha256=xFsmF_SXYXK_kMJOCHkiInPJ_0VcPWz-kJDoMup2lT8,1576
|
|
10130
10130
|
simo/fleet/managers.py,sha256=kpfvvfdH4LDxddIBDpdAb5gsVk8Gb0-L9biFcj9OFPs,807
|
|
10131
|
-
simo/fleet/models.py,sha256=
|
|
10131
|
+
simo/fleet/models.py,sha256=Ro0ZkYB3a7ZhczVQOxjAobCRECIdN0Nj0yb5EBybvW0,12809
|
|
10132
10132
|
simo/fleet/routing.py,sha256=cofGsVWXMfPDwsJ6HM88xxtRxHwERhJ48Xyxc8mxg5o,149
|
|
10133
|
-
simo/fleet/serializers.py,sha256=
|
|
10134
|
-
simo/fleet/socket_consumers.py,sha256=
|
|
10133
|
+
simo/fleet/serializers.py,sha256=zEpXAXxjk4Rf1JhlNnLTrs20qJggqjvIySbeHVo4Tt4,1505
|
|
10134
|
+
simo/fleet/socket_consumers.py,sha256=o8yr27AYxKFStQGyXZrk7PP1P2fUgSjsWp76DojWqbM,19415
|
|
10135
10135
|
simo/fleet/utils.py,sha256=D0EGFbDmW8zyhyxf5ozGtRpo4Sy5Ov6ZixukBK_e2Do,3462
|
|
10136
10136
|
simo/fleet/views.py,sha256=PbdZpsM_7-oyKzuDX1A5WULNABA1_B7ISF70UJX97FE,1662
|
|
10137
10137
|
simo/fleet/__pycache__/__init__.cpython-38.pyc,sha256=pIZE7EL6-cuJ3pQtaSwjKLrKLsTYelp1k9sRhXKLh6s,159
|
|
@@ -10175,7 +10175,7 @@ simo/fleet/migrations/0023_colonel_is_authorized.py,sha256=IoyPUR45axv9V6QsntPTj
|
|
|
10175
10175
|
simo/fleet/migrations/0024_colonel_pwm_frequency.py,sha256=nfTDs3GeIEkkiuQMB3_tc8TuyZSNOdQ2KhnJKTtQ9XE,498
|
|
10176
10176
|
simo/fleet/migrations/0025_auto_20240130_1334.py,sha256=dHcQnlF7LBp6ikx_s5AB_I19sQ-iMB8XJ1X1yoWVvBs,825
|
|
10177
10177
|
simo/fleet/migrations/0026_rename_i2cinterface_scl_pin_and_more.py,sha256=SEQxhgFsxWaUmfXaMOHHj0q8EqF9bt9PTOVuVa6wrQs,2752
|
|
10178
|
-
simo/fleet/migrations/0027_auto_20240306_0802.py,sha256=
|
|
10178
|
+
simo/fleet/migrations/0027_auto_20240306_0802.py,sha256=kOtLlCbbkqZu1zwzNZyA5rE_0TifF7tURdLCu_hzTLE,5972
|
|
10179
10179
|
simo/fleet/migrations/0028_remove_i2cinterface_scl_pin_no_and_more.py,sha256=CeoFQPIu3jb4Gtyu5FhRjhl_mKhowMoEdtFU1oWD4UE,450
|
|
10180
10180
|
simo/fleet/migrations/0029_alter_i2cinterface_scl_pin_and_more.py,sha256=aquymc1ZcUVgdrqZxl3viN6kiCHtw4HjgL6n4d0cSpQ,886
|
|
10181
10181
|
simo/fleet/migrations/0030_colonelpin_label_alter_colonel_type.py,sha256=5T5bmQxPZrG0UseCLd7ssV-AeFF3O4T_DFxLu3whSqg,706
|
|
@@ -10217,9 +10217,9 @@ simo/generic/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
10217
10217
|
simo/generic/app_widgets.py,sha256=E_pnpA1hxMIhenRCrHoQ5cik06jm2BAHCkl_eo-OudU,1264
|
|
10218
10218
|
simo/generic/base_types.py,sha256=djymox_boXTHX1BTTCLXrCH7ED-uAsV_idhaDOc3OLI,409
|
|
10219
10219
|
simo/generic/controllers.py,sha256=Fs95mRBJgT5ZNMnxxwQJ_94TJ7Y_O0IygWSgfjLZzU0,51912
|
|
10220
|
-
simo/generic/forms.py,sha256=
|
|
10220
|
+
simo/generic/forms.py,sha256=HYctQMudlMpQRxkI67C6NShaM1dAzo0J-qbu-AFpozU,20194
|
|
10221
10221
|
simo/generic/gateways.py,sha256=aHU0mA0ADNVzw3EXb8paXI5DI-gNd4CrGtaV5qDhILY,15499
|
|
10222
|
-
simo/generic/models.py,sha256
|
|
10222
|
+
simo/generic/models.py,sha256=d00Q-UXtt7mG9MdPpZ3mXnxKjwlTI2FgCEklZpGBk7s,3629
|
|
10223
10223
|
simo/generic/routing.py,sha256=elQVZmgnPiieEuti4sJ7zITk1hlRxpgbotcutJJgC60,228
|
|
10224
10224
|
simo/generic/socket_consumers.py,sha256=NfTQGYtVAc864IoogZRxf_0xpDPM0eMCWn0SlKA5P7Y,1751
|
|
10225
10225
|
simo/generic/static/weather_icons/01d@2x.png,sha256=TZfWi6Rfddb2P-oldWWcjUiuCHiU9Yrc5hyrQAhF26I,948
|
|
@@ -10266,7 +10266,7 @@ simo/notifications/admin.py,sha256=y_gmHYXbDh98LUUa-lp9DilTIgM6-pIujWPQPLQsJo8,8
|
|
|
10266
10266
|
simo/notifications/api.py,sha256=GXQpq68ULBaJpU8w3SJKaCKuxYGWYehKnGeocGB1RVc,1783
|
|
10267
10267
|
simo/notifications/models.py,sha256=VZcvweii59j89nPKlWeUSJ44Qz3ZLjJ6mXN6uB9F1Sw,2506
|
|
10268
10268
|
simo/notifications/serializers.py,sha256=altDEAPWwOhxRcEzE9-34jL8EFpyf3vPoEdAPoVLfGc,523
|
|
10269
|
-
simo/notifications/utils.py,sha256=
|
|
10269
|
+
simo/notifications/utils.py,sha256=5CtKOvX0vbWLXFvJD_8WfWulMEp1FuMwrfCGDLHySdA,907
|
|
10270
10270
|
simo/notifications/migrations/0001_initial.py,sha256=Zh69AQ-EKlQKfqfnMDVRcxvo1MxRY-TFLCdnNcgqi6g,2003
|
|
10271
10271
|
simo/notifications/migrations/0002_notification_instance.py,sha256=B3msbMeKvsuq-V7gvRADRjj5PFLayhi3pQvHZjqzO5g,563
|
|
10272
10272
|
simo/notifications/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -10343,8 +10343,8 @@ simo/users/templates/invitations/expired_msg.html,sha256=47DEQpj8HBSa-_TImW-5JCe
|
|
|
10343
10343
|
simo/users/templates/invitations/expired_suggestion.html,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10344
10344
|
simo/users/templates/invitations/taken_msg.html,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10345
10345
|
simo/users/templates/invitations/taken_suggestion.html,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10346
|
-
simo-2.0.
|
|
10347
|
-
simo-2.0.
|
|
10348
|
-
simo-2.0.
|
|
10349
|
-
simo-2.0.
|
|
10350
|
-
simo-2.0.
|
|
10346
|
+
simo-2.0.3.dist-info/LICENSE.md,sha256=M7wm1EmMGDtwPRdg7kW4d00h1uAXjKOT3HFScYQMeiE,34916
|
|
10347
|
+
simo-2.0.3.dist-info/METADATA,sha256=kIgoVnqVIeZ9Paw4f7G6PA41Nwe3dafpPmpEbRV5JU4,1669
|
|
10348
|
+
simo-2.0.3.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
10349
|
+
simo-2.0.3.dist-info/top_level.txt,sha256=GmS1hrAbpVqn9OWZh6UX82eIOdRLgYA82RG9fe8v4Rs,5
|
|
10350
|
+
simo-2.0.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|