simo 2.8.2__py3-none-any.whl → 2.8.4__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/automation/__pycache__/controllers.cpython-312.pyc +0 -0
- simo/automation/__pycache__/gateways.cpython-312.pyc +0 -0
- simo/automation/gateways.py +40 -31
- simo/backups/__pycache__/tasks.cpython-312.pyc +0 -0
- simo/core/__pycache__/controllers.cpython-312.pyc +0 -0
- simo/core/controllers.py +2 -0
- simo/fleet/__pycache__/tasks.cpython-312.pyc +0 -0
- simo/generic/__pycache__/forms.cpython-312.pyc +0 -0
- simo/generic/__pycache__/gateways.cpython-312.pyc +0 -0
- simo/generic/__pycache__/models.cpython-312.pyc +0 -0
- simo/generic/__pycache__/tasks.cpython-312.pyc +0 -0
- simo/generic/forms.py +6 -0
- simo/generic/gateways.py +0 -38
- simo/generic/models.py +41 -37
- simo/generic/tasks.py +80 -0
- simo/notifications/__pycache__/utils.cpython-312.pyc +0 -0
- simo/users/__pycache__/api.cpython-312.pyc +0 -0
- simo/users/__pycache__/models.cpython-312.pyc +0 -0
- simo/users/__pycache__/tasks.cpython-312.pyc +0 -0
- simo/users/models.py +16 -3
- {simo-2.8.2.dist-info → simo-2.8.4.dist-info}/METADATA +1 -1
- {simo-2.8.2.dist-info → simo-2.8.4.dist-info}/RECORD +26 -20
- {simo-2.8.2.dist-info → simo-2.8.4.dist-info}/LICENSE.md +0 -0
- {simo-2.8.2.dist-info → simo-2.8.4.dist-info}/WHEEL +0 -0
- {simo-2.8.2.dist-info → simo-2.8.4.dist-info}/entry_points.txt +0 -0
- {simo-2.8.2.dist-info → simo-2.8.4.dist-info}/top_level.txt +0 -0
|
Binary file
|
|
Binary file
|
simo/automation/gateways.py
CHANGED
|
@@ -6,7 +6,6 @@ import json
|
|
|
6
6
|
import time
|
|
7
7
|
import multiprocessing
|
|
8
8
|
import threading
|
|
9
|
-
import traceback
|
|
10
9
|
from django.conf import settings
|
|
11
10
|
from django.utils import timezone
|
|
12
11
|
from django.db import connection as db_connection
|
|
@@ -43,10 +42,7 @@ class ScriptRunHandler(multiprocessing.Process):
|
|
|
43
42
|
def run(self):
|
|
44
43
|
db_connection.connect()
|
|
45
44
|
self.component = Component.objects.get(id=self.component_id)
|
|
46
|
-
|
|
47
|
-
tz = pytz.timezone(self.component.zone.instance.timezone)
|
|
48
|
-
except:
|
|
49
|
-
tz = pytz.timezone('UTC')
|
|
45
|
+
tz = pytz.timezone(self.component.zone.instance.timezone)
|
|
50
46
|
timezone.activate(tz)
|
|
51
47
|
introduce_instance(self.component.zone.instance)
|
|
52
48
|
self.logger = get_component_logger(self.component)
|
|
@@ -235,8 +231,10 @@ class AutomationsGatewayHandler(GatesHandler, BaseObjectCommandsGatewayHandler):
|
|
|
235
231
|
def watch_scripts(self):
|
|
236
232
|
drop_current_instance()
|
|
237
233
|
# observe running scripts and drop the ones that are no longer alive
|
|
238
|
-
|
|
239
|
-
|
|
234
|
+
for id, data in list(self.running_scripts.items()):
|
|
235
|
+
if time.time() - data['start_time'] < 5:
|
|
236
|
+
continue
|
|
237
|
+
process = data['proc']
|
|
240
238
|
|
|
241
239
|
comp = Component.objects.filter(id=id).first()
|
|
242
240
|
if comp and comp.value == 'finished':
|
|
@@ -252,12 +250,18 @@ class AutomationsGatewayHandler(GatesHandler, BaseObjectCommandsGatewayHandler):
|
|
|
252
250
|
self.running_scripts.pop(id)
|
|
253
251
|
continue
|
|
254
252
|
else:
|
|
253
|
+
# it as been observed that is_alive might sometimes report false
|
|
254
|
+
# however the process is actually still running
|
|
255
|
+
process.kill()
|
|
255
256
|
self.last_death = time.time()
|
|
256
257
|
self.running_scripts.pop(id, None) # no longer running for sure!
|
|
257
258
|
if not comp or comp.value != 'running':
|
|
258
259
|
continue
|
|
259
260
|
|
|
260
261
|
if id not in self.terminating_scripts: # was not intentionaly terminated
|
|
262
|
+
if comp:
|
|
263
|
+
tz = pytz.timezone(comp.zone.instance.timezone)
|
|
264
|
+
timezone.activate(tz)
|
|
261
265
|
logger = get_component_logger(comp)
|
|
262
266
|
logger.log(logging.INFO, "-------DEAD!-------")
|
|
263
267
|
comp.value = 'error'
|
|
@@ -348,30 +352,35 @@ class AutomationsGatewayHandler(GatesHandler, BaseObjectCommandsGatewayHandler):
|
|
|
348
352
|
|
|
349
353
|
def start_script(self, component):
|
|
350
354
|
print("START SCRIPT %s" % str(component))
|
|
355
|
+
|
|
351
356
|
if component.id in self.running_scripts:
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
pass
|
|
357
|
-
self.running_scripts.pop(component.id, None)
|
|
358
|
-
elif component.id not in self.terminating_scripts \
|
|
359
|
-
and self.running_scripts[component.id].is_alive():
|
|
357
|
+
# it appears that the script is running and is perfectly healthy
|
|
358
|
+
# so we make sure it has correct value, do nothing else and return!
|
|
359
|
+
if component.id not in self.terminating_scripts \
|
|
360
|
+
and self.running_scripts[component.id]['proc'].is_alive():
|
|
360
361
|
if component.value != 'running':
|
|
361
362
|
component.value = 'running'
|
|
362
363
|
component.save()
|
|
363
364
|
return
|
|
364
|
-
else:
|
|
365
|
-
try:
|
|
366
|
-
self.running_scripts[component.id].kill()
|
|
367
|
-
except:
|
|
368
|
-
pass
|
|
369
365
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
366
|
+
# script is in terminating state or is no longer alive
|
|
367
|
+
# since starting of a new script was requested, we kill it viciously
|
|
368
|
+
# and continue on!
|
|
369
|
+
try:
|
|
370
|
+
self.running_scripts[component.id]['proc'].kill()
|
|
371
|
+
except:
|
|
372
|
+
pass
|
|
373
|
+
self.running_scripts.pop(component.id, None)
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
process = ScriptRunHandler(
|
|
377
|
+
component.id, multiprocessing.Event(), daemon=True
|
|
373
378
|
)
|
|
374
|
-
|
|
379
|
+
process.start()
|
|
380
|
+
self.running_scripts[component.id] = {
|
|
381
|
+
'proc': process, 'start_time': time.time()
|
|
382
|
+
}
|
|
383
|
+
|
|
375
384
|
|
|
376
385
|
def stop_script(self, component, stop_status='stopped'):
|
|
377
386
|
self.terminating_scripts.add(component.id)
|
|
@@ -389,17 +398,17 @@ class AutomationsGatewayHandler(GatesHandler, BaseObjectCommandsGatewayHandler):
|
|
|
389
398
|
elif stop_status == 'stopped':
|
|
390
399
|
logger.log(logging.INFO, "-------STOP-------")
|
|
391
400
|
|
|
392
|
-
if self.running_scripts[component.id].exit_in_use.is_set()\
|
|
393
|
-
and not self.running_scripts[component.id].exin_in_use_fail.is_set():
|
|
394
|
-
self.running_scripts[component.id].exit_event.set()
|
|
401
|
+
if self.running_scripts[component.id]['proc'].exit_in_use.is_set()\
|
|
402
|
+
and not self.running_scripts[component.id]['proc'].exin_in_use_fail.is_set():
|
|
403
|
+
self.running_scripts[component.id]['proc'].exit_event.set()
|
|
395
404
|
else:
|
|
396
|
-
self.running_scripts[component.id].terminate()
|
|
405
|
+
self.running_scripts[component.id]['proc'].terminate()
|
|
397
406
|
|
|
398
407
|
def kill():
|
|
399
408
|
start = time.time()
|
|
400
409
|
terminated = False
|
|
401
410
|
while start > time.time() - 2:
|
|
402
|
-
if not self.running_scripts[component.id].is_alive():
|
|
411
|
+
if not self.running_scripts[component.id]['proc'].is_alive():
|
|
403
412
|
terminated = True
|
|
404
413
|
break
|
|
405
414
|
time.sleep(0.1)
|
|
@@ -408,12 +417,12 @@ class AutomationsGatewayHandler(GatesHandler, BaseObjectCommandsGatewayHandler):
|
|
|
408
417
|
logger.log(logging.INFO, "-------GATEWAY KILL-------")
|
|
409
418
|
else:
|
|
410
419
|
logger.log(logging.INFO, "-------KILL!-------")
|
|
411
|
-
self.running_scripts[component.id].kill()
|
|
420
|
+
self.running_scripts[component.id]['proc'].kill()
|
|
412
421
|
|
|
413
422
|
component.set(stop_status)
|
|
414
423
|
self.terminating_scripts.remove(component.id)
|
|
415
424
|
# making sure it's fully killed along with it's child processes
|
|
416
|
-
self.running_scripts[component.id].kill()
|
|
425
|
+
self.running_scripts[component.id]['proc'].kill()
|
|
417
426
|
self.running_scripts.pop(component.id, None)
|
|
418
427
|
logger.handlers = []
|
|
419
428
|
|
|
Binary file
|
|
Binary file
|
simo/core/controllers.py
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
simo/generic/forms.py
CHANGED
|
@@ -157,6 +157,11 @@ class ThermostatConfigForm(BaseComponentForm):
|
|
|
157
157
|
|
|
158
158
|
class AlarmBreachEventForm(forms.Form):
|
|
159
159
|
uid = HiddenField(required=False)
|
|
160
|
+
threshold = forms.IntegerField(
|
|
161
|
+
label="Number of sensors breached",
|
|
162
|
+
min_value=1, max_value=5, initial=1,
|
|
163
|
+
help_text="React when at least given amount of sensors were breached."
|
|
164
|
+
)
|
|
160
165
|
component = Select2ModelChoiceField(
|
|
161
166
|
queryset=Component.objects.all(),
|
|
162
167
|
url='autocomplete-component',
|
|
@@ -173,6 +178,7 @@ class AlarmBreachEventForm(forms.Form):
|
|
|
173
178
|
help_text="Event will not fire if alarm group is disarmed "
|
|
174
179
|
"within given timeframe of seconds after the breach."
|
|
175
180
|
)
|
|
181
|
+
|
|
176
182
|
prefix = 'breach_events'
|
|
177
183
|
|
|
178
184
|
def clean(self):
|
simo/generic/gateways.py
CHANGED
|
@@ -185,8 +185,6 @@ class GenericGatewayHandler(
|
|
|
185
185
|
('watch_thermostats', 60),
|
|
186
186
|
('watch_alarm_clocks', 30),
|
|
187
187
|
('watch_watering', 60),
|
|
188
|
-
('watch_alarm_events', 1),
|
|
189
|
-
('watch_timers', 1),
|
|
190
188
|
('watch_main_states', 60),
|
|
191
189
|
('watch_groups', 60)
|
|
192
190
|
)
|
|
@@ -328,42 +326,6 @@ class GenericGatewayHandler(
|
|
|
328
326
|
other_group.refresh_status()
|
|
329
327
|
|
|
330
328
|
|
|
331
|
-
def watch_alarm_events(self):
|
|
332
|
-
from .controllers import AlarmGroup
|
|
333
|
-
drop_current_instance()
|
|
334
|
-
for alarm in Component.objects.filter(
|
|
335
|
-
controller_uid=AlarmGroup.uid, value='breached',
|
|
336
|
-
meta__breach_start__gt=0
|
|
337
|
-
):
|
|
338
|
-
for uid, event in alarm.controller.events_map.items():
|
|
339
|
-
if uid in alarm.meta.get('events_triggered', []):
|
|
340
|
-
continue
|
|
341
|
-
if time.time() - alarm.meta['breach_start'] < event['delay']:
|
|
342
|
-
continue
|
|
343
|
-
try:
|
|
344
|
-
getattr(event['component'], event['breach_action'])()
|
|
345
|
-
except Exception:
|
|
346
|
-
print(traceback.format_exc(), file=sys.stderr)
|
|
347
|
-
if not alarm.meta.get('events_triggered'):
|
|
348
|
-
alarm.meta['events_triggered'] = [uid]
|
|
349
|
-
else:
|
|
350
|
-
alarm.meta['events_triggered'].append(uid)
|
|
351
|
-
alarm.save(update_fields=['meta'])
|
|
352
|
-
|
|
353
|
-
def watch_timers(self):
|
|
354
|
-
drop_current_instance()
|
|
355
|
-
for component in Component.objects.filter(
|
|
356
|
-
meta__timer_to__gt=0
|
|
357
|
-
).filter(meta__timer_to__lt=time.time()):
|
|
358
|
-
component.meta['timer_to'] = 0
|
|
359
|
-
component.meta['timer_start'] = 0
|
|
360
|
-
component.save()
|
|
361
|
-
try:
|
|
362
|
-
component.controller._on_timer_end()
|
|
363
|
-
except Exception as e:
|
|
364
|
-
print(traceback.format_exc(), file=sys.stderr)
|
|
365
|
-
|
|
366
|
-
|
|
367
329
|
def set_get_day_evening_night_morning(self, state):
|
|
368
330
|
if state.value not in ('day', 'night', 'evening', 'morning'):
|
|
369
331
|
return
|
simo/generic/models.py
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import time
|
|
2
2
|
import sys
|
|
3
3
|
import traceback
|
|
4
|
-
from threading import Timer
|
|
5
4
|
from django.db.models.signals import pre_save, post_save, post_delete
|
|
6
5
|
from django.dispatch import receiver
|
|
7
|
-
from simo.core.models import
|
|
6
|
+
from simo.core.models import Component
|
|
8
7
|
from simo.users.models import InstanceUser
|
|
9
|
-
|
|
8
|
+
from .tasks import (
|
|
9
|
+
notify_users_on_alarm_group_breach,
|
|
10
|
+
fire_breach_events
|
|
11
|
+
)
|
|
10
12
|
|
|
11
13
|
|
|
12
14
|
@receiver(post_save, sender=Component)
|
|
@@ -21,7 +23,7 @@ def handle_alarm_groups(sender, instance, *args, **kwargs):
|
|
|
21
23
|
|
|
22
24
|
from .controllers import AlarmGroup
|
|
23
25
|
|
|
24
|
-
for
|
|
26
|
+
for ag in Component.objects.filter(
|
|
25
27
|
controller_uid=AlarmGroup.uid,
|
|
26
28
|
config__components__contains=instance.id,
|
|
27
29
|
).exclude(value='disarmed'):
|
|
@@ -30,48 +32,44 @@ def handle_alarm_groups(sender, instance, *args, **kwargs):
|
|
|
30
32
|
}
|
|
31
33
|
stats[instance.arm_status] += 1
|
|
32
34
|
for slave in Component.objects.filter(
|
|
33
|
-
pk__in=
|
|
35
|
+
pk__in=ag.config['components'],
|
|
34
36
|
).exclude(pk=instance.pk):
|
|
35
37
|
stats[slave.arm_status] += 1
|
|
36
|
-
alarm_group.config['stats'] = stats
|
|
37
|
-
alarm_group.save(update_fields=['config'])
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
print(f"STATS OF {ag} are: {stats}")
|
|
40
|
+
ag.config['stats'] = stats
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
if stats['disarmed'] == len(ag.config['components']):
|
|
40
44
|
alarm_group_value = 'disarmed'
|
|
41
|
-
elif stats['armed'] == len(
|
|
45
|
+
elif stats['armed'] == len(ag.config['components']):
|
|
42
46
|
alarm_group_value = 'armed'
|
|
43
47
|
elif stats['breached']:
|
|
44
|
-
if alarm_group.value != 'breached':
|
|
45
|
-
def notify_users_security_breach(alarm_group_component_id):
|
|
46
|
-
alarm_group_component = Component.objects.filter(
|
|
47
|
-
id=alarm_group_component_id, value='breached'
|
|
48
|
-
).first()
|
|
49
|
-
if not alarm_group_component:
|
|
50
|
-
return
|
|
51
|
-
breached_components = Component.objects.filter(
|
|
52
|
-
pk__in=alarm_group_component.config['components'],
|
|
53
|
-
arm_status='breached'
|
|
54
|
-
)
|
|
55
|
-
body = "Security Breach! " + '; '.join(
|
|
56
|
-
[str(c) for c in breached_components]
|
|
57
|
-
)
|
|
58
|
-
from simo.notifications.utils import notify_users
|
|
59
|
-
notify_users(
|
|
60
|
-
'alarm', str(alarm_group_component), body,
|
|
61
|
-
component=alarm_group_component
|
|
62
|
-
)
|
|
63
|
-
if alarm_group.config.get('notify_on_breach') is not None:
|
|
64
|
-
t = Timer(
|
|
65
|
-
# give it one second to finish with other db processes.
|
|
66
|
-
alarm_group.config['notify_on_breach'] + 1,
|
|
67
|
-
notify_users_security_breach, [alarm_group.id]
|
|
68
|
-
)
|
|
69
|
-
t.start()
|
|
70
48
|
alarm_group_value = 'breached'
|
|
71
49
|
else:
|
|
72
50
|
alarm_group_value = 'pending-arm'
|
|
73
51
|
|
|
74
|
-
|
|
52
|
+
print(f"{ag} value: {alarm_group_value}")
|
|
53
|
+
|
|
54
|
+
if alarm_group_value == 'breached' and instance.arm_status == 'breached':
|
|
55
|
+
if ag.value != 'breached':
|
|
56
|
+
ag.meta['breach_times'] = [time.time()]
|
|
57
|
+
else:
|
|
58
|
+
ag.meta['breach_times'].append(time.time())
|
|
59
|
+
|
|
60
|
+
ag.save(update_fields=['meta', 'config'])
|
|
61
|
+
ag.controller.set(alarm_group_value)
|
|
62
|
+
|
|
63
|
+
if alarm_group_value == 'breached' and instance.arm_status == 'breached':
|
|
64
|
+
for event in ag.config['breach_events']:
|
|
65
|
+
if event['uid'] in ag.meta.get('events_triggered', []):
|
|
66
|
+
continue
|
|
67
|
+
threshold = event.get('threshold', 1)
|
|
68
|
+
if len(ag.meta['breach_times']) < threshold:
|
|
69
|
+
continue
|
|
70
|
+
fire_breach_events.apply_async(
|
|
71
|
+
args=[ag.id], countdown=event['delay']
|
|
72
|
+
)
|
|
75
73
|
|
|
76
74
|
|
|
77
75
|
@receiver(pre_save, sender=Component)
|
|
@@ -85,8 +83,14 @@ def manage_alarm_groups(sender, instance, *args, **kwargs):
|
|
|
85
83
|
return
|
|
86
84
|
|
|
87
85
|
if instance.value == 'breached':
|
|
88
|
-
instance.meta['breach_start'] = time.time()
|
|
89
86
|
instance.meta['events_triggered'] = []
|
|
87
|
+
|
|
88
|
+
if instance.config.get('notify_on_breach') is not None:
|
|
89
|
+
notify_users_on_alarm_group_breach.apply_async(
|
|
90
|
+
args=[instance.id],
|
|
91
|
+
countdown=instance.config['notify_on_breach']
|
|
92
|
+
)
|
|
93
|
+
|
|
90
94
|
elif instance.get_dirty_fields()['value'] == 'breached' \
|
|
91
95
|
and instance.value == 'disarmed':
|
|
92
96
|
instance.meta['breach_start'] = None
|
simo/generic/tasks.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import sys, traceback, time
|
|
2
|
+
from celeryc import celery_app
|
|
3
|
+
from simo.core.middleware import drop_current_instance, drop_current_instance
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@celery_app.task
|
|
7
|
+
def notify_users_on_alarm_group_breach(ag_id):
|
|
8
|
+
from simo.core.models import Component
|
|
9
|
+
drop_current_instance()
|
|
10
|
+
ag = Component.objects.filter(id=ag_id).first()
|
|
11
|
+
if not ag:
|
|
12
|
+
return
|
|
13
|
+
if ag.value != 'breached':
|
|
14
|
+
# no longer breached, somebody disarmed it,
|
|
15
|
+
# no need to send any notifications
|
|
16
|
+
return
|
|
17
|
+
|
|
18
|
+
breached_components = Component.objects.filter(
|
|
19
|
+
pk__in=ag.config['components'],
|
|
20
|
+
arm_status='breached'
|
|
21
|
+
)
|
|
22
|
+
body = "Security Breach! " + '; '.join(
|
|
23
|
+
[str(c) for c in breached_components]
|
|
24
|
+
)
|
|
25
|
+
from simo.notifications.utils import notify_users
|
|
26
|
+
notify_users(
|
|
27
|
+
'alarm', str(ag), body, component=ag,
|
|
28
|
+
instance=ag.zone.instance
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@celery_app.task
|
|
33
|
+
def fire_breach_events(ag_id):
|
|
34
|
+
from simo.core.models import Component
|
|
35
|
+
drop_current_instance()
|
|
36
|
+
ag = Component.objects.filter(id=ag_id).first()
|
|
37
|
+
if not ag:
|
|
38
|
+
return
|
|
39
|
+
if ag.value != 'breached':
|
|
40
|
+
# no longer breached, somebody disarmed it,
|
|
41
|
+
# no need to send any notifications
|
|
42
|
+
return
|
|
43
|
+
for uid, event in ag.controller.events_map.items():
|
|
44
|
+
if uid in ag.meta.get('events_triggered', []):
|
|
45
|
+
continue
|
|
46
|
+
threshold = event.get('threshold', 1)
|
|
47
|
+
if len(ag.meta['breach_times']) < threshold:
|
|
48
|
+
continue
|
|
49
|
+
if time.time() - ag.meta['breach_times'][threshold - 1] < event['delay']:
|
|
50
|
+
continue
|
|
51
|
+
try:
|
|
52
|
+
getattr(event['component'], event['breach_action'])()
|
|
53
|
+
except Exception:
|
|
54
|
+
print(traceback.format_exc(), file=sys.stderr)
|
|
55
|
+
if not ag.meta.get('events_triggered'):
|
|
56
|
+
ag.meta['events_triggered'] = [uid]
|
|
57
|
+
else:
|
|
58
|
+
ag.meta['events_triggered'].append(uid)
|
|
59
|
+
ag.save(update_fields=['meta'])
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@celery_app.task
|
|
63
|
+
def watch_timers():
|
|
64
|
+
from simo.core.models import Component
|
|
65
|
+
drop_current_instance()
|
|
66
|
+
for component in Component.objects.filter(
|
|
67
|
+
meta__timer_to__gt=0
|
|
68
|
+
).filter(meta__timer_to__lt=time.time()):
|
|
69
|
+
component.meta['timer_to'] = 0
|
|
70
|
+
component.meta['timer_start'] = 0
|
|
71
|
+
component.save()
|
|
72
|
+
try:
|
|
73
|
+
component.controller._on_timer_end()
|
|
74
|
+
except Exception as e:
|
|
75
|
+
print(traceback.format_exc(), file=sys.stderr)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@celery_app.on_after_finalize.connect
|
|
79
|
+
def setup_periodic_tasks(sender, **kwargs):
|
|
80
|
+
sender.add_periodic_task(1, watch_timers.s())
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
simo/users/models.py
CHANGED
|
@@ -117,6 +117,7 @@ class InstanceUser(DirtyFieldsMixin, models.Model, OnChangeMixin):
|
|
|
117
117
|
class Meta:
|
|
118
118
|
unique_together = 'user', 'instance'
|
|
119
119
|
|
|
120
|
+
|
|
120
121
|
def __str__(self):
|
|
121
122
|
if self.role.instance:
|
|
122
123
|
return f"{self.user} is {self.role.name} on {self.instance}"
|
|
@@ -223,6 +224,8 @@ class User(AbstractBaseUser, SimoAdminMixin):
|
|
|
223
224
|
def __init__(self, *args, **kwargs):
|
|
224
225
|
super().__init__(*args, **kwargs)
|
|
225
226
|
self._is_active = None
|
|
227
|
+
self._instances = None
|
|
228
|
+
self._instance_roles = {}
|
|
226
229
|
|
|
227
230
|
def __str__(self):
|
|
228
231
|
return self.name
|
|
@@ -266,6 +269,8 @@ class User(AbstractBaseUser, SimoAdminMixin):
|
|
|
266
269
|
return self.is_active and self.is_master
|
|
267
270
|
|
|
268
271
|
def get_role(self, instance):
|
|
272
|
+
if instance.id in self._instance_roles:
|
|
273
|
+
return self._instance_roles[instance.id]
|
|
269
274
|
cache_key = f'user-{self.id}_instance-{instance.id}_role'
|
|
270
275
|
role = cache.get(cache_key)
|
|
271
276
|
if role is None:
|
|
@@ -276,7 +281,8 @@ class User(AbstractBaseUser, SimoAdminMixin):
|
|
|
276
281
|
).first()
|
|
277
282
|
if role:
|
|
278
283
|
cache.set(cache_key, role, 20)
|
|
279
|
-
|
|
284
|
+
self._instance_roles[instance.id] = role
|
|
285
|
+
return self._instance_roles[instance.id]
|
|
280
286
|
|
|
281
287
|
@property
|
|
282
288
|
def role_id(self):
|
|
@@ -323,6 +329,9 @@ class User(AbstractBaseUser, SimoAdminMixin):
|
|
|
323
329
|
if not self.is_active:
|
|
324
330
|
return Instance.objects.none()
|
|
325
331
|
|
|
332
|
+
if self._instances is not None:
|
|
333
|
+
return self._instances
|
|
334
|
+
|
|
326
335
|
cache_key = f'user-{self.id}_instances'
|
|
327
336
|
instances = cache.get(cache_key)
|
|
328
337
|
if instances is None:
|
|
@@ -336,7 +345,8 @@ class User(AbstractBaseUser, SimoAdminMixin):
|
|
|
336
345
|
], is_active=True)
|
|
337
346
|
cache.set(cache_key, instances, 10)
|
|
338
347
|
|
|
339
|
-
|
|
348
|
+
self._instances = instances
|
|
349
|
+
return self._instances
|
|
340
350
|
|
|
341
351
|
@property
|
|
342
352
|
def component_permissions(self):
|
|
@@ -347,6 +357,8 @@ class User(AbstractBaseUser, SimoAdminMixin):
|
|
|
347
357
|
|
|
348
358
|
@property
|
|
349
359
|
def is_active(self):
|
|
360
|
+
if self._is_active is not None:
|
|
361
|
+
return self._is_active
|
|
350
362
|
cache_key = f'user-{self.id}_is_active'
|
|
351
363
|
cached_value = cache.get(cache_key)
|
|
352
364
|
if cached_value is None:
|
|
@@ -372,7 +384,8 @@ class User(AbstractBaseUser, SimoAdminMixin):
|
|
|
372
384
|
self.instance_roles.filter(is_active=True).count()
|
|
373
385
|
)
|
|
374
386
|
cache.set(cache_key, cached_value, 20)
|
|
375
|
-
|
|
387
|
+
self._is_active = cached_value
|
|
388
|
+
return self._is_active
|
|
376
389
|
|
|
377
390
|
|
|
378
391
|
@is_active.setter
|
|
@@ -21,7 +21,7 @@ simo/automation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
21
21
|
simo/automation/app_widgets.py,sha256=gaqImMZjuMHm7nIb9a4D-Y3qipz_WhSPAHXcwGx4Uzs,199
|
|
22
22
|
simo/automation/controllers.py,sha256=66aGjlfgigcePXiLmDkbVrxbm-Z26klMKIiOvaMH71k,11545
|
|
23
23
|
simo/automation/forms.py,sha256=UWnkxw8pILPK0smRPTo4SLgsZl78zOySx7JIc30Bgtk,10228
|
|
24
|
-
simo/automation/gateways.py,sha256=
|
|
24
|
+
simo/automation/gateways.py,sha256=8qgBDhkM_xYGmeEbnH5WXs5m3ihMp8Yzx7yj6XG8DNM,16382
|
|
25
25
|
simo/automation/helpers.py,sha256=iP-fxxB8HsFQy3k2CjFubu86aMqvWgmh-p24DiyOrek,4330
|
|
26
26
|
simo/automation/models.py,sha256=zt-jkzyq5ddqGT864OkJzCsvov2vZ0nO4ez3hAeZkXg,934
|
|
27
27
|
simo/automation/serializers.py,sha256=PjyFrjdPK1mBsgbNhyqMi9SWzcymqTa742ipy0LhAN4,1996
|
|
@@ -30,11 +30,11 @@ simo/automation/__pycache__/__init__.cpython-312.pyc,sha256=rIs1rkBbUhjXjbUffmq2
|
|
|
30
30
|
simo/automation/__pycache__/__init__.cpython-38.pyc,sha256=YmP0xAD-mxpQHgdTZeC64sXChA8TriMZD1jckNYi3xg,164
|
|
31
31
|
simo/automation/__pycache__/app_widgets.cpython-312.pyc,sha256=6u8LsTpf32yn0L49eI4A0sVHlSwl6gOyPlReUwQKoOM,577
|
|
32
32
|
simo/automation/__pycache__/app_widgets.cpython-38.pyc,sha256=7DfUA9V_1MiwrOe_ta-ts8dYY8xXb9UMg2_9A3XoRcs,523
|
|
33
|
-
simo/automation/__pycache__/controllers.cpython-312.pyc,sha256=
|
|
33
|
+
simo/automation/__pycache__/controllers.cpython-312.pyc,sha256=QV63g3UlpMAA-yCaZxoe1B2hVeDGobfOtBA0K1h66Os,15052
|
|
34
34
|
simo/automation/__pycache__/controllers.cpython-38.pyc,sha256=CL-0Tq9B4-E36fYfWT1XEBTq1dkq1W8003f6MrBnQU0,8391
|
|
35
35
|
simo/automation/__pycache__/forms.cpython-312.pyc,sha256=63rU0rWZk-Rz5qoMZiXl743WPc9NVm5d8bSd8w52T4E,12347
|
|
36
36
|
simo/automation/__pycache__/forms.cpython-38.pyc,sha256=cpA5hA2Iz3JsPC0Dq01ki1I7S9c5DKRcXveHApI1dJo,7772
|
|
37
|
-
simo/automation/__pycache__/gateways.cpython-312.pyc,sha256=
|
|
37
|
+
simo/automation/__pycache__/gateways.cpython-312.pyc,sha256=mQWn7s8qqF-CIw9Zn80RbbaFJjTiCdaN_grMsfwa-nY,22405
|
|
38
38
|
simo/automation/__pycache__/gateways.cpython-38.pyc,sha256=nHujqChMCqqxHbZezP3MisavjKDhczqzFGurO10h-lc,11113
|
|
39
39
|
simo/automation/__pycache__/helpers.cpython-312.pyc,sha256=aDFtzBE72szi4gzVxK_NiAEC__wCmdztw0UKu2lVU58,5853
|
|
40
40
|
simo/automation/__pycache__/helpers.cpython-38.pyc,sha256=fNjSyn4Mfq7-JQx-bdsnj-rSxgu20dVJ9-5ZEMT6yiM,3627
|
|
@@ -70,6 +70,7 @@ simo/backups/__pycache__/dynamic_settings.cpython-312.pyc,sha256=Sf8IorKxo6r3R0Q
|
|
|
70
70
|
simo/backups/__pycache__/dynamic_settings.cpython-38.pyc,sha256=51gJFjn_XqQBRoHeubo6ppb9pNuFQKI5hAR0ms9flE8,731
|
|
71
71
|
simo/backups/__pycache__/models.cpython-312.pyc,sha256=45nfTQzX8YuKI67KEe0nAdlZEQHAZjJu44NUr4PC5WI,1746
|
|
72
72
|
simo/backups/__pycache__/models.cpython-38.pyc,sha256=zclX7HwOT4_izweyKNQ8LmgSHb3hNcYcfsSiwwfQoLY,1220
|
|
73
|
+
simo/backups/__pycache__/tasks.cpython-312.pyc,sha256=Vg95hEynVXVA89bkG0BN5Cw6KkuVCV4JCiKLsd7xI_8,18369
|
|
73
74
|
simo/backups/__pycache__/tasks.cpython-38.pyc,sha256=bKz_Rxt_H0lC0d9_4Dxqv7cirQDSH9LVurZZC0LU94s,9179
|
|
74
75
|
simo/backups/migrations/0001_initial.py,sha256=0LzCusTUyYf61ksiepdnqXIuYYNTNd_djh_Wa484HA0,770
|
|
75
76
|
simo/backups/migrations/0002_backuplog_backup_level_backup_size.py,sha256=w9T9MQWuecy91OZE1fMExriwPuXA8HMPKsPwXhmC8_k,1023
|
|
@@ -97,7 +98,7 @@ simo/core/auto_urls.py,sha256=FqKhH0fF7cGO6P2YrjblwG4JA2UkVXj3lreJUOB2Jq4,1194
|
|
|
97
98
|
simo/core/autocomplete_views.py,sha256=x3MKOZvXYS3xVQ-V1S7Liv_U5bxr-uc0gePa85wv5nA,4561
|
|
98
99
|
simo/core/base_types.py,sha256=WypW8hTfzveuTQtruGjLYAGQZIuczxTlW-SdRk3iQug,666
|
|
99
100
|
simo/core/context.py,sha256=LKw1I4iIRnlnzoTCuSLLqDX7crHdBnMo3hjqYvVmzFc,1557
|
|
100
|
-
simo/core/controllers.py,sha256=
|
|
101
|
+
simo/core/controllers.py,sha256=4u67VNwU4Zts64aVddqhoPt9ZRbpTmAHPCdu-pFfIX0,37407
|
|
101
102
|
simo/core/dynamic_settings.py,sha256=bUs58XEZOCIEhg1TigR3LmYggli13KMryBZ9pC7ugAQ,1872
|
|
102
103
|
simo/core/events.py,sha256=1_KIk5pJqdLPRQlCQ9xSyALst2Cn0b2lAEAJ3QjwIjE,4801
|
|
103
104
|
simo/core/filters.py,sha256=6wbn8C2WvKTTjtfMwwLBp2Fib1V0-DMpS4iqJd6jJQo,2540
|
|
@@ -141,7 +142,7 @@ simo/core/__pycache__/base_types.cpython-312.pyc,sha256=Lnq2NL9B5hfwJARJYC447Rdv
|
|
|
141
142
|
simo/core/__pycache__/base_types.cpython-38.pyc,sha256=CX-qlF7CefRi_mCE954wYa9rUFR88mOl6g7fybDRu7g,803
|
|
142
143
|
simo/core/__pycache__/context.cpython-312.pyc,sha256=8rsN2Er-Sx3rrVmO0Gk4cem3euGh0kTELXj667GGZ5E,2193
|
|
143
144
|
simo/core/__pycache__/context.cpython-38.pyc,sha256=NlTHt2GvXxA21AhBkeyOLfRFUuXw7wmwqyNhhcDl2cw,1373
|
|
144
|
-
simo/core/__pycache__/controllers.cpython-312.pyc,sha256=
|
|
145
|
+
simo/core/__pycache__/controllers.cpython-312.pyc,sha256=pNjKIaSvzEWJlI6_yQdmakyFYNgrxv6dyexVvoScUj4,52897
|
|
145
146
|
simo/core/__pycache__/controllers.cpython-38.pyc,sha256=LtrQQ8egOIOuQbAckeM-z8OfbzS4W8VQ3vBnryAm3iU,32086
|
|
146
147
|
simo/core/__pycache__/dynamic_settings.cpython-312.pyc,sha256=WUZ6XF4kZb6zPf541PkKmiQaBIw-r5C6F3EUUZiTEnE,3331
|
|
147
148
|
simo/core/__pycache__/dynamic_settings.cpython-38.pyc,sha256=wGpnscX1DxFpRl54MQURhjz2aD3NJohSzw9JCFnzh2Y,2384
|
|
@@ -10446,6 +10447,7 @@ simo/fleet/__pycache__/serializers.cpython-312.pyc,sha256=reKKBMohl7vi7iJ6wjRbOB
|
|
|
10446
10447
|
simo/fleet/__pycache__/serializers.cpython-38.pyc,sha256=l_FzORWCM1hcSZV0AaGRO-p0CMTcEfqnLGgbn2IVvI0,3648
|
|
10447
10448
|
simo/fleet/__pycache__/socket_consumers.cpython-312.pyc,sha256=1ZEKGEL8FDeALU-BLlap9rjHZKgc_FDtLr0INSWspGo,27221
|
|
10448
10449
|
simo/fleet/__pycache__/socket_consumers.cpython-38.pyc,sha256=lEC1SkY_KgRY0QoBUMPjnbFwSa7qmCf-4eNQ45hAy68,14141
|
|
10450
|
+
simo/fleet/__pycache__/tasks.cpython-312.pyc,sha256=1XDir2GWxl7c17n0ZuCxOFKsLjaCqXgqVT4TcDvzhm8,1817
|
|
10449
10451
|
simo/fleet/__pycache__/tasks.cpython-38.pyc,sha256=RoNxL2WUiW67s9O9DjaYVVjCBSZu2nje0Qn9FJkWVS0,1116
|
|
10450
10452
|
simo/fleet/__pycache__/utils.cpython-312.pyc,sha256=mrICzm_po8QGUaRddn0TvBp5RDLBe1Jklg7xZr0YduM,4995
|
|
10451
10453
|
simo/fleet/__pycache__/utils.cpython-38.pyc,sha256=obUd-X2Y-ybx4icqUWq_qwIxrP9yyarJjexWAfO4MTI,3344
|
|
@@ -10592,11 +10594,12 @@ simo/generic/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
10592
10594
|
simo/generic/app_widgets.py,sha256=y8W3jR76Hh26O9pPQyg2SophMbYIOtAWD33MPKbB8Mg,856
|
|
10593
10595
|
simo/generic/base_types.py,sha256=u3SlfpNYaCwkVBwomWgso4ODzL71ay9MhiAW-bxgnDU,341
|
|
10594
10596
|
simo/generic/controllers.py,sha256=NZmrJ1BYzAwHJ1vN-OMvqnH13ihvjUhZqdxq-l__1Jk,46268
|
|
10595
|
-
simo/generic/forms.py,sha256=
|
|
10596
|
-
simo/generic/gateways.py,sha256=
|
|
10597
|
-
simo/generic/models.py,sha256=
|
|
10597
|
+
simo/generic/forms.py,sha256=1Gt3FycP4Da8LbtOuOGTpLSf2dCDEE-Sdr_X3qlA1aw,26265
|
|
10598
|
+
simo/generic/gateways.py,sha256=kjqGMjlLXlGNreKLQtHoSnw4xan1IB8-KF8AqiZ82T0,16057
|
|
10599
|
+
simo/generic/models.py,sha256=59fkYowOX0imviIhA6uwupvuharrpBykmBm674rJNoI,7279
|
|
10598
10600
|
simo/generic/routing.py,sha256=elQVZmgnPiieEuti4sJ7zITk1hlRxpgbotcutJJgC60,228
|
|
10599
10601
|
simo/generic/socket_consumers.py,sha256=qesKZVhI56Kh7vdIUDD3hzDUi0FcXwIfcmE_a3YS6JQ,1772
|
|
10602
|
+
simo/generic/tasks.py,sha256=5jhi7Jv7lfaM3T8GArWKaDqQfuvdBsKqz5obN6NVUqk,2570
|
|
10600
10603
|
simo/generic/__pycache__/__init__.cpython-312.pyc,sha256=-34GwswSg1zc1becA8lPwUpnd4ek2IoduCsIUr0KTNo,167
|
|
10601
10604
|
simo/generic/__pycache__/__init__.cpython-38.pyc,sha256=mLu54WS9KIl-pHwVCBKpsDFIlOqml--JsOVzAUHg6cU,161
|
|
10602
10605
|
simo/generic/__pycache__/app_widgets.cpython-312.pyc,sha256=ywoKk91YSEZxpyt9haG509_c0G9DMJVpae_y1iiZxJU,1937
|
|
@@ -10605,16 +10608,17 @@ simo/generic/__pycache__/base_types.cpython-312.pyc,sha256=h8Mwu49i-zmwTbL33JaLJ
|
|
|
10605
10608
|
simo/generic/__pycache__/base_types.cpython-38.pyc,sha256=aV5NdIuvXR-ItKpI__MwcyPZHD6Z882TFdgYkPCkr1I,493
|
|
10606
10609
|
simo/generic/__pycache__/controllers.cpython-312.pyc,sha256=TgHV-dNMJqYuFOgeJTxeh4Mihb4lHnfhid0CmdhZCR4,52737
|
|
10607
10610
|
simo/generic/__pycache__/controllers.cpython-38.pyc,sha256=jJjwKVaDYyazrRGNjUFoY74nr_jX_DEnsC9KjyxZCgc,30427
|
|
10608
|
-
simo/generic/__pycache__/forms.cpython-312.pyc,sha256=
|
|
10611
|
+
simo/generic/__pycache__/forms.cpython-312.pyc,sha256=nTyrgmEmOrgORJSOxUI_i2O7wVUXCQL9MYhaT76Jmew,34869
|
|
10609
10612
|
simo/generic/__pycache__/forms.cpython-38.pyc,sha256=k8lz3taXdWAg5P9jcnw66mWH51pCc4SOsg32kVEtBCg,19416
|
|
10610
|
-
simo/generic/__pycache__/gateways.cpython-312.pyc,sha256
|
|
10613
|
+
simo/generic/__pycache__/gateways.cpython-312.pyc,sha256=ixeotxPeqHkINziDnnkymlKR2wYMb5UR2sxiioMQxhY,19309
|
|
10611
10614
|
simo/generic/__pycache__/gateways.cpython-38.pyc,sha256=GIeMT51oZU2OCFD4eUDFdSRRYE0Qf14AcOr_gdUqG94,12705
|
|
10612
|
-
simo/generic/__pycache__/models.cpython-312.pyc,sha256=
|
|
10615
|
+
simo/generic/__pycache__/models.cpython-312.pyc,sha256=ggaeX6BQa-0-KG50HadpRCWeW84Fbog0muT2gBkqLNQ,10190
|
|
10613
10616
|
simo/generic/__pycache__/models.cpython-38.pyc,sha256=MZpum7syAFxuulf47K7gtUlJJ7xRD-IBUBAwUM1ZRnw,5825
|
|
10614
10617
|
simo/generic/__pycache__/routing.cpython-312.pyc,sha256=_wQPZeAgwlGtnafw9VcabgqjyJxzDFywHBIFbGhzYRE,452
|
|
10615
10618
|
simo/generic/__pycache__/routing.cpython-38.pyc,sha256=xtxTUTBTdivzFyA5Wh7k-hUj1WDO_FiRq6HYXdbr9Ks,382
|
|
10616
10619
|
simo/generic/__pycache__/socket_consumers.cpython-312.pyc,sha256=zERTr2bHXLKSXCoIov6MaFRrgfeS1A0IHCXbaPfVvK4,2814
|
|
10617
10620
|
simo/generic/__pycache__/socket_consumers.cpython-38.pyc,sha256=FaVCf_uJI2uwj1Zz-jwsOXou65oV9gFCIB8e-YKquRk,1662
|
|
10621
|
+
simo/generic/__pycache__/tasks.cpython-312.pyc,sha256=zEyNpFVmEJoZdeYKNZ7cEmPtIyZMTwEqzZJZZwMYl-o,4494
|
|
10618
10622
|
simo/generic/migrations/0001_initial.py,sha256=7FpPcfpRU5ya0b8s2KbxR5a3npf92YruvZltUybjzys,676
|
|
10619
10623
|
simo/generic/migrations/0002_auto_20241126_0726.py,sha256=SX38JwP732QooOm5HM1Xo7Th_Mv_6YZloT3eozULOhs,922
|
|
10620
10624
|
simo/generic/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -10719,6 +10723,7 @@ simo/notifications/__pycache__/models.cpython-312.pyc,sha256=VKa2ZQxzBH22YAUlsmh
|
|
|
10719
10723
|
simo/notifications/__pycache__/models.cpython-38.pyc,sha256=PoqLuOnlaAWQQ-20AtqhvAlLSkakPmdn7J7wGvHNW3g,2449
|
|
10720
10724
|
simo/notifications/__pycache__/serializers.cpython-312.pyc,sha256=on-lu0Sj7vEl0_-2ul19Yk59gYHIFESXZKpA9RKNewQ,1226
|
|
10721
10725
|
simo/notifications/__pycache__/serializers.cpython-38.pyc,sha256=7-eRGKYuQ4g1SpKOMpz17SIiu1HmaMoYv-cJbaO9QGA,1028
|
|
10726
|
+
simo/notifications/__pycache__/utils.cpython-312.pyc,sha256=o-CnxcAtX7tHo2CdGBKjLTd1OU3z57zUCaWlYzEuv88,2423
|
|
10722
10727
|
simo/notifications/__pycache__/utils.cpython-38.pyc,sha256=4ZnI-pmWji84EXBkPrl4ir1kGjfanO4bH5--bLNRxCA,1648
|
|
10723
10728
|
simo/notifications/migrations/0001_initial.py,sha256=Zh69AQ-EKlQKfqfnMDVRcxvo1MxRY-TFLCdnNcgqi6g,2003
|
|
10724
10729
|
simo/notifications/migrations/0002_notification_instance.py,sha256=B3msbMeKvsuq-V7gvRADRjj5PFLayhi3pQvHZjqzO5g,563
|
|
@@ -10741,7 +10746,7 @@ simo/users/auto_urls.py,sha256=RSUW3ai5LbMTknS8M7M5aOnG_YlFOVQrnNVNH-fkwlg,357
|
|
|
10741
10746
|
simo/users/dynamic_settings.py,sha256=sEIsi4yJw3kH46Jq_aOkSuK7QTfQACGUE-lkyBogCaM,570
|
|
10742
10747
|
simo/users/managers.py,sha256=OHgEP85MBtdkdYxdstBd8RavTBT8F_2WyDxUJ9aCqqM,246
|
|
10743
10748
|
simo/users/middleware.py,sha256=tNPmnzo0eTrJ25SLHP7NotqYKI2cKnmv8hf6v5DLOWo,427
|
|
10744
|
-
simo/users/models.py,sha256=
|
|
10749
|
+
simo/users/models.py,sha256=EUsYX8PtPgRkpmCZXg0mRkYHV6e69TqGB-UkbZGe5Cw,20549
|
|
10745
10750
|
simo/users/permissions.py,sha256=IwtYS8yQdupWbYKR9VimSRDV3qCJ2jXP57Lyjpb2EQM,242
|
|
10746
10751
|
simo/users/serializers.py,sha256=REgXD9Af6nTRu_wUm1dt5YoCB6C_KNDaPqHZj726zZ0,2497
|
|
10747
10752
|
simo/users/sso_urls.py,sha256=gQOaPvGMYFD0NCVSwyoWO-mTEHe5j9sbzV_RK7kdvp0,251
|
|
@@ -10753,7 +10758,7 @@ simo/users/__pycache__/__init__.cpython-312.pyc,sha256=n0GE5zxjujBUkv1t1CswZ9Gby
|
|
|
10753
10758
|
simo/users/__pycache__/__init__.cpython-38.pyc,sha256=VFoDJE_SKKaPqqYaaBYd1Ndb1hjakkTo_u0EG_XJ1GM,211
|
|
10754
10759
|
simo/users/__pycache__/admin.cpython-312.pyc,sha256=lTVuZQwkuw1bzSn0A0kt-8H6ruKas_YPb_61AxSYSoo,11465
|
|
10755
10760
|
simo/users/__pycache__/admin.cpython-38.pyc,sha256=uL8TwAipkatZxanvQtBKKcOv8Fm3UvinBxsP0okrOZg,8443
|
|
10756
|
-
simo/users/__pycache__/api.cpython-312.pyc,sha256=
|
|
10761
|
+
simo/users/__pycache__/api.cpython-312.pyc,sha256=p8nEOe95slqWK0QxhAkmXXbyPiZd-0VcA-ejvFcS6Mo,18333
|
|
10757
10762
|
simo/users/__pycache__/api.cpython-38.pyc,sha256=zZ4DfNktgeVvLAtMpaPUv7AoAgbKr7SCt-4ghJk1zp4,10386
|
|
10758
10763
|
simo/users/__pycache__/apps.cpython-312.pyc,sha256=yLvyvcBuRr6jfIxwiA9sjQOE4VyHVUKl5FUVDPXuAkM,707
|
|
10759
10764
|
simo/users/__pycache__/apps.cpython-38.pyc,sha256=dgbWL8CxzzISJQTmq_4IztPJ2UzykNVdqA2Ae1PmeGk,605
|
|
@@ -10767,7 +10772,7 @@ simo/users/__pycache__/managers.cpython-312.pyc,sha256=A9-yF1dilDc1H_-BtrIw9USpH
|
|
|
10767
10772
|
simo/users/__pycache__/managers.cpython-38.pyc,sha256=O0Y8ABp42RAosrbODmYsPMaj9AyOPyJ-aqzuO0Qpi2s,679
|
|
10768
10773
|
simo/users/__pycache__/middleware.cpython-312.pyc,sha256=9pkiIkMriu0ExLK479hyPfaN-DOfp6WErqQlrZY1Mvk,927
|
|
10769
10774
|
simo/users/__pycache__/middleware.cpython-38.pyc,sha256=Tj4nVEAvxEW3xA63fBRiJWRJpz_M848ZOqbHioc_IPE,1149
|
|
10770
|
-
simo/users/__pycache__/models.cpython-312.pyc,sha256=
|
|
10775
|
+
simo/users/__pycache__/models.cpython-312.pyc,sha256=DownCK4FLYKjE-QMM1-ciZ0koLzwsnc_RFgyq5sIi8k,29596
|
|
10771
10776
|
simo/users/__pycache__/models.cpython-38.pyc,sha256=qNtAn_eWVmRTWhTxN8GtCc549dcJsTdaF7Uk19yNMgg,18330
|
|
10772
10777
|
simo/users/__pycache__/permissions.cpython-312.pyc,sha256=NCaxeIz4qmG_mF18lKTYXqOSUvJkFLnUZjfYIQFUCkU,718
|
|
10773
10778
|
simo/users/__pycache__/permissions.cpython-38.pyc,sha256=ez5NxoL_JUeeH6GsKhvFreuA3FCBgGf9floSypdXUtM,633
|
|
@@ -10777,6 +10782,7 @@ simo/users/__pycache__/sso_urls.cpython-312.pyc,sha256=FQLOFu310j7pOhTNlg2wyUybq
|
|
|
10777
10782
|
simo/users/__pycache__/sso_urls.cpython-38.pyc,sha256=uAwDozpOmrhUald-8tOHANILXkH7-TI8fNYXOtPkSY8,402
|
|
10778
10783
|
simo/users/__pycache__/sso_views.cpython-312.pyc,sha256=FZGDRdt7pb8qROe8ZAEGSTfeZtLCGNBvQiXpDtrc4dE,6011
|
|
10779
10784
|
simo/users/__pycache__/sso_views.cpython-38.pyc,sha256=PLRF6FYCxRhnmgnN_gUS-pdQlH7lofLU1Xhgw3vDO_Y,4019
|
|
10785
|
+
simo/users/__pycache__/tasks.cpython-312.pyc,sha256=cu-VV1HWXToHR875DFC2E9_x1cn1mD5mXYqltk0QOuw,1955
|
|
10780
10786
|
simo/users/__pycache__/tasks.cpython-38.pyc,sha256=XLMKt3suT7BlcXrJZoH9ZIhhtBuqyiW4lsOB9IbBkko,1225
|
|
10781
10787
|
simo/users/__pycache__/utils.cpython-312.pyc,sha256=dJtfFncpI2QtTSZoUUNCfUsDoofOOfVtMRXNm5ZXC20,3515
|
|
10782
10788
|
simo/users/__pycache__/utils.cpython-38.pyc,sha256=cgxUwEaBgxT360mw4E03J7u4vi3dhw6K3n7-8WNvyFU,1888
|
|
@@ -10924,9 +10930,9 @@ simo/users/templates/invitations/expired_msg.html,sha256=47DEQpj8HBSa-_TImW-5JCe
|
|
|
10924
10930
|
simo/users/templates/invitations/expired_suggestion.html,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10925
10931
|
simo/users/templates/invitations/taken_msg.html,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10926
10932
|
simo/users/templates/invitations/taken_suggestion.html,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10927
|
-
simo-2.8.
|
|
10928
|
-
simo-2.8.
|
|
10929
|
-
simo-2.8.
|
|
10930
|
-
simo-2.8.
|
|
10931
|
-
simo-2.8.
|
|
10932
|
-
simo-2.8.
|
|
10933
|
+
simo-2.8.4.dist-info/LICENSE.md,sha256=M7wm1EmMGDtwPRdg7kW4d00h1uAXjKOT3HFScYQMeiE,34916
|
|
10934
|
+
simo-2.8.4.dist-info/METADATA,sha256=R_KaZi61ln63mwg8ywrTAVIFTD5b_EI4tj1kGJtV6wI,2008
|
|
10935
|
+
simo-2.8.4.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
10936
|
+
simo-2.8.4.dist-info/entry_points.txt,sha256=S9PwnUYmTSW7681GKDCxUbL0leRJIaRk6fDQIKgbZBA,135
|
|
10937
|
+
simo-2.8.4.dist-info/top_level.txt,sha256=GmS1hrAbpVqn9OWZh6UX82eIOdRLgYA82RG9fe8v4Rs,5
|
|
10938
|
+
simo-2.8.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|