simo 2.6.9__py3-none-any.whl → 2.7.2__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/__pycache__/settings.cpython-38.pyc +0 -0
- simo/automation/__pycache__/controllers.cpython-38.pyc +0 -0
- simo/automation/__pycache__/gateways.cpython-38.pyc +0 -0
- simo/automation/controllers.py +18 -1
- simo/automation/gateways.py +130 -1
- simo/core/__pycache__/api_meta.cpython-38.pyc +0 -0
- simo/core/__pycache__/autocomplete_views.cpython-38.pyc +0 -0
- simo/core/__pycache__/controllers.cpython-38.pyc +0 -0
- simo/core/__pycache__/form_fields.cpython-38.pyc +0 -0
- simo/core/__pycache__/forms.cpython-38.pyc +0 -0
- simo/core/__pycache__/gateways.cpython-38.pyc +0 -0
- simo/core/__pycache__/middleware.cpython-38.pyc +0 -0
- simo/core/__pycache__/serializers.cpython-38.pyc +0 -0
- simo/core/__pycache__/signal_receivers.cpython-38.pyc +0 -0
- simo/core/api_meta.py +6 -2
- simo/core/autocomplete_views.py +4 -3
- simo/core/controllers.py +42 -4
- simo/core/form_fields.py +92 -1
- simo/core/forms.py +10 -4
- simo/core/gateways.py +11 -1
- simo/core/serializers.py +8 -1
- simo/core/signal_receivers.py +7 -83
- simo/core/utils/__pycache__/converters.cpython-38.pyc +0 -0
- simo/core/utils/converters.py +59 -0
- simo/fleet/__pycache__/auto_urls.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/controllers.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/forms.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/models.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/views.cpython-38.pyc +0 -0
- simo/fleet/auto_urls.py +5 -0
- simo/fleet/forms.py +58 -4
- simo/fleet/views.py +37 -6
- simo/generic/__pycache__/controllers.cpython-38.pyc +0 -0
- simo/generic/__pycache__/forms.cpython-38.pyc +0 -0
- simo/generic/__pycache__/gateways.cpython-38.pyc +0 -0
- simo/generic/controllers.py +132 -1
- simo/generic/forms.py +118 -10
- simo/generic/gateways.py +157 -2
- simo/users/__pycache__/admin.cpython-38.pyc +0 -0
- simo/users/__pycache__/api.cpython-38.pyc +0 -0
- simo/users/__pycache__/auto_urls.cpython-38.pyc +0 -0
- simo/users/__pycache__/views.cpython-38.pyc +0 -0
- simo/users/admin.py +9 -0
- simo/users/api.py +2 -0
- simo/users/auto_urls.py +6 -3
- simo/users/views.py +20 -1
- {simo-2.6.9.dist-info → simo-2.7.2.dist-info}/METADATA +1 -1
- {simo-2.6.9.dist-info → simo-2.7.2.dist-info}/RECORD +52 -50
- {simo-2.6.9.dist-info → simo-2.7.2.dist-info}/LICENSE.md +0 -0
- {simo-2.6.9.dist-info → simo-2.7.2.dist-info}/WHEEL +0 -0
- {simo-2.6.9.dist-info → simo-2.7.2.dist-info}/entry_points.txt +0 -0
- {simo-2.6.9.dist-info → simo-2.7.2.dist-info}/top_level.txt +0 -0
simo/generic/forms.py
CHANGED
|
@@ -16,7 +16,7 @@ from simo.core.form_fields import (
|
|
|
16
16
|
Select2ModelChoiceField, Select2ListChoiceField,
|
|
17
17
|
Select2ModelMultipleChoiceField
|
|
18
18
|
)
|
|
19
|
-
|
|
19
|
+
from simo.core.forms import DimmerConfigForm, SwitchForm
|
|
20
20
|
|
|
21
21
|
ACTION_METHODS = (
|
|
22
22
|
('turn_on', "Turn ON"), ('turn_off', "Turn OFF"),
|
|
@@ -26,6 +26,46 @@ ACTION_METHODS = (
|
|
|
26
26
|
)
|
|
27
27
|
|
|
28
28
|
|
|
29
|
+
class ControlForm(forms.Form):
|
|
30
|
+
button = Select2ModelChoiceField(
|
|
31
|
+
queryset=Component.objects.filter(base_type='button'),
|
|
32
|
+
url='autocomplete-component'
|
|
33
|
+
)
|
|
34
|
+
prefix = 'controls'
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class DimmableLightsGroupConfigForm(DimmerConfigForm):
|
|
38
|
+
slaves = Select2ModelMultipleChoiceField(
|
|
39
|
+
queryset=Component.objects.filter(
|
|
40
|
+
base_type__in=('dimmer', 'switch')
|
|
41
|
+
),
|
|
42
|
+
url='autocomplete-component',
|
|
43
|
+
forward=(forward.Const(['dimmer', 'switch'], 'base_type'),),
|
|
44
|
+
required=False
|
|
45
|
+
)
|
|
46
|
+
controls = FormsetField(
|
|
47
|
+
formset_factory(
|
|
48
|
+
ControlForm, can_delete=True, can_order=True, extra=0, max_num=999
|
|
49
|
+
)
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class SwitchGroupConfigForm(SwitchForm):
|
|
54
|
+
slaves = Select2ModelMultipleChoiceField(
|
|
55
|
+
queryset=Component.objects.filter(
|
|
56
|
+
base_type__in=('dimmer', 'switch')
|
|
57
|
+
),
|
|
58
|
+
url='autocomplete-component',
|
|
59
|
+
forward=(forward.Const(['dimmer', 'switch'], 'base_type'),),
|
|
60
|
+
required=False
|
|
61
|
+
)
|
|
62
|
+
controls = FormsetField(
|
|
63
|
+
formset_factory(
|
|
64
|
+
ControlForm, can_delete=True, can_order=True, extra=0, max_num=999
|
|
65
|
+
)
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
29
69
|
class ThermostatConfigForm(BaseComponentForm):
|
|
30
70
|
temperature_sensor = Select2ModelChoiceField(
|
|
31
71
|
queryset=Component.objects.filter(
|
|
@@ -425,21 +465,89 @@ class StateSelectForm(BaseComponentForm):
|
|
|
425
465
|
states = FormsetField(
|
|
426
466
|
formset_factory(StateForm, can_delete=True, can_order=True, extra=0)
|
|
427
467
|
)
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
class MainStateSelectForm(BaseComponentForm):
|
|
471
|
+
weekdays_morning_hour = forms.IntegerField(
|
|
472
|
+
initial=6, min_value=3, max_value=12
|
|
473
|
+
)
|
|
474
|
+
weekends_morning_hour = forms.IntegerField(
|
|
475
|
+
initial=6, min_value=3, max_value=12
|
|
476
|
+
)
|
|
477
|
+
away_on_no_action = forms.IntegerField(
|
|
478
|
+
required=False, initial=40, min_value=1, max_value=360,
|
|
479
|
+
help_text="Set state to Away if nobody is at home "
|
|
480
|
+
"(requires location and fitness permissions on mobile app) "
|
|
481
|
+
"and there were "
|
|
482
|
+
"no action for more than given amount of minutes from any "
|
|
483
|
+
"security alarm acategory sensors (motion sensors, door sensors etc..).<br>"
|
|
484
|
+
"No value disables this behavior."
|
|
432
485
|
)
|
|
486
|
+
sleeping_phones_hour = forms.IntegerField(
|
|
487
|
+
initial=True, required=False, min_value=18, max_value=24,
|
|
488
|
+
help_text='Set mode to "Sleep" if it is later than given hour '
|
|
489
|
+
'and all home owners phones who are at home are put on charge '
|
|
490
|
+
'(requires location and fitness permissions on mobile app). <br>'
|
|
491
|
+
'Set back to regular state as soon as none of the home owners phones '
|
|
492
|
+
'are on charge and it is morning hour or past it.'
|
|
493
|
+
)
|
|
494
|
+
states = FormsetField(
|
|
495
|
+
formset_factory(StateForm, can_delete=True, can_order=True, extra=0)
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
def clean(self):
|
|
499
|
+
if not self.instance.id:
|
|
500
|
+
if Component.objects.filter(
|
|
501
|
+
controller_uid='simo.generic.controllers.MainState'
|
|
502
|
+
).count():
|
|
503
|
+
raise forms.ValidationError("Main state already exists!")
|
|
504
|
+
|
|
505
|
+
formset_errors = {}
|
|
506
|
+
required_states = {
|
|
507
|
+
'morning', 'day', 'evening', 'night', 'sleep', 'away', 'vacation'
|
|
508
|
+
}
|
|
509
|
+
found_states = set()
|
|
510
|
+
for i, state in enumerate(self.cleaned_data.get('states', [])):
|
|
511
|
+
if state.get('slug') in found_states:
|
|
512
|
+
formset_errors['i'] = {'slug': "Duplicate!"}
|
|
513
|
+
continue
|
|
514
|
+
found_states.add(state.get('slug'))
|
|
515
|
+
|
|
516
|
+
errors_list = []
|
|
517
|
+
if formset_errors:
|
|
518
|
+
for i, control in enumerate(self.cleaned_data['states']):
|
|
519
|
+
errors_list.append(formset_errors.get(i, {}))
|
|
520
|
+
if errors_list:
|
|
521
|
+
self._errors['states'] = errors_list
|
|
522
|
+
if 'states' in self.cleaned_data:
|
|
523
|
+
del self.cleaned_data['states']
|
|
524
|
+
|
|
525
|
+
else:
|
|
526
|
+
missing_states = required_states - found_states
|
|
527
|
+
if missing_states:
|
|
528
|
+
if len(missing_states) == 1:
|
|
529
|
+
self.add_error(
|
|
530
|
+
'states',
|
|
531
|
+
f'"{list(missing_states)[0]}" state is missing!'
|
|
532
|
+
)
|
|
533
|
+
self.add_error(
|
|
534
|
+
'states',
|
|
535
|
+
f"Required states are missing: {list(missing_states)}!"
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
return self.cleaned_data
|
|
433
539
|
|
|
434
540
|
def save(self, commit=True):
|
|
435
|
-
if commit
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
541
|
+
if commit:
|
|
542
|
+
for s in Component.objects.filter(
|
|
543
|
+
base_type='state-select', config__is_main=True
|
|
544
|
+
).exclude(id=self.instance.id):
|
|
545
|
+
s.config['is_main'] = False
|
|
546
|
+
s.save()
|
|
440
547
|
return super().save(commit)
|
|
441
548
|
|
|
442
549
|
|
|
550
|
+
|
|
443
551
|
class AlarmClockEventForm(forms.Form):
|
|
444
552
|
uid = HiddenField(required=False)
|
|
445
553
|
enabled = forms.BooleanField(initial=True)
|
simo/generic/gateways.py
CHANGED
|
@@ -59,6 +59,82 @@ class CameraWatcher(threading.Thread):
|
|
|
59
59
|
|
|
60
60
|
|
|
61
61
|
|
|
62
|
+
class GroupButtonsHandler:
|
|
63
|
+
|
|
64
|
+
def __init__(self, *args, **kwargs):
|
|
65
|
+
super().__init__(*args, **kwargs)
|
|
66
|
+
self.group_buttons = {}
|
|
67
|
+
self.fade_directions = {}
|
|
68
|
+
|
|
69
|
+
def watch_groups(self):
|
|
70
|
+
from .controllers import DimmableLightsGroup, SwitchGroup
|
|
71
|
+
current_group_buttons = {}
|
|
72
|
+
for group_comp in Component.objects.filter(
|
|
73
|
+
controller_uid__in=(DimmableLightsGroup.uid, SwitchGroup.uid)
|
|
74
|
+
):
|
|
75
|
+
for ctrl in group_comp.config.get('controls', []):
|
|
76
|
+
if ctrl['button'] not in current_group_buttons:
|
|
77
|
+
current_group_buttons[ctrl['button']] = set(group_comp.id)
|
|
78
|
+
else:
|
|
79
|
+
current_group_buttons[ctrl['button']].add(group_comp.id)
|
|
80
|
+
|
|
81
|
+
if ctrl['button'] not in self.group_buttons:
|
|
82
|
+
self.group_buttons[ctrl['button']] = set()
|
|
83
|
+
if group_comp.id not in self.group_buttons[ctrl['button']]:
|
|
84
|
+
self.group_buttons[ctrl['button']].add(group_comp.id)
|
|
85
|
+
btn = Component.objects.filter(id=ctrl['button']).first()
|
|
86
|
+
if btn:
|
|
87
|
+
btn.on_change(self.watch_group_button)
|
|
88
|
+
|
|
89
|
+
# remove groups and buttons that are no longer in use
|
|
90
|
+
for btn_id, groups in self.group_buttons.items():
|
|
91
|
+
if btn_id not in current_group_buttons:
|
|
92
|
+
self.group_buttons[btn_id] = set()
|
|
93
|
+
continue
|
|
94
|
+
self.group_buttons[btn_id] = current_group_buttons[btn_id]
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def watch_group_button(self, button):
|
|
98
|
+
group_ids = self.group_buttons.get(button.id)
|
|
99
|
+
if not group_ids:
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
btn_type = button.config.get('btn_type', 'momentary')
|
|
103
|
+
|
|
104
|
+
if btn_type == 'momentary':
|
|
105
|
+
if button.value not in ('click', 'hold', 'up', 'double-click'):
|
|
106
|
+
return
|
|
107
|
+
for g_id in group_ids:
|
|
108
|
+
group = Component.objects.filter(id=g_id).first()
|
|
109
|
+
if not group:
|
|
110
|
+
continue
|
|
111
|
+
if button.value == 'click':
|
|
112
|
+
group.toggle()
|
|
113
|
+
elif button.value == 'double-click':
|
|
114
|
+
group.send(group.config.get('max', 100))
|
|
115
|
+
elif button.value == 'down':
|
|
116
|
+
if self.fade_directions.get(group.id, 0) < 0:
|
|
117
|
+
self.fade_directions[group.id] = 1
|
|
118
|
+
group.fade_up()
|
|
119
|
+
else:
|
|
120
|
+
self.fade_directions[group.id] = -1
|
|
121
|
+
group.fade_down()
|
|
122
|
+
elif button.value == 'up':
|
|
123
|
+
if self.fade_directions.get(group.id):
|
|
124
|
+
self.fade_directions[group.id] = 0
|
|
125
|
+
group.fade_stop()
|
|
126
|
+
|
|
127
|
+
else: # toggle
|
|
128
|
+
if button.value not in ('down', 'up'):
|
|
129
|
+
return
|
|
130
|
+
for g_id in group_ids:
|
|
131
|
+
group = Component.objects.filter(id=g_id).first()
|
|
132
|
+
if not group:
|
|
133
|
+
continue
|
|
134
|
+
group.toggle()
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
|
|
62
138
|
class GenericGatewayHandler(BaseObjectCommandsGatewayHandler):
|
|
63
139
|
name = "Generic"
|
|
64
140
|
config_form = BaseGatewayForm
|
|
@@ -72,10 +148,17 @@ class GenericGatewayHandler(BaseObjectCommandsGatewayHandler):
|
|
|
72
148
|
('watch_alarm_clocks', 30),
|
|
73
149
|
('watch_watering', 60),
|
|
74
150
|
('watch_alarm_events', 1),
|
|
75
|
-
('watch_timers', 1)
|
|
151
|
+
('watch_timers', 1),
|
|
152
|
+
('watch_main_states', 60)
|
|
76
153
|
)
|
|
77
154
|
|
|
78
|
-
|
|
155
|
+
def __init__(self, *args, **kwargs):
|
|
156
|
+
super().__init__(*args, **kwargs)
|
|
157
|
+
self.last_sensor_actions = {}
|
|
158
|
+
self.sensors_on_watch = {}
|
|
159
|
+
self.sleep_is_on = {}
|
|
160
|
+
|
|
161
|
+
|
|
79
162
|
|
|
80
163
|
def watch_thermostats(self):
|
|
81
164
|
from .controllers import Thermostat
|
|
@@ -242,6 +325,78 @@ class GenericGatewayHandler(BaseObjectCommandsGatewayHandler):
|
|
|
242
325
|
print(traceback.format_exc(), file=sys.stderr)
|
|
243
326
|
|
|
244
327
|
|
|
328
|
+
def watch_main_state(self, state):
|
|
329
|
+
i_id = state.zone.instance.id
|
|
330
|
+
if state.value in ('day', 'night', 'evening', 'morning'):
|
|
331
|
+
new_state = state.get_day_evening_night_morning()
|
|
332
|
+
if new_state != state.value:
|
|
333
|
+
print(f"New main state of {state.zone.instance} - {new_state}")
|
|
334
|
+
state.send(new_state)
|
|
335
|
+
|
|
336
|
+
if state.config.get('away_on_no_action'):
|
|
337
|
+
for sensor in Component.objects.filter(
|
|
338
|
+
zone__instance=state.zone.instance,
|
|
339
|
+
base_type='binary-sensor', alarm_category='security'
|
|
340
|
+
):
|
|
341
|
+
if state.id not in self.sensors_on_watch:
|
|
342
|
+
self.sensors_on_watch[state.id] = {}
|
|
343
|
+
|
|
344
|
+
if sensor.id not in self.sensors_on_watch[state.id]:
|
|
345
|
+
self.sensors_on_watch[state.id][sensor.id] = i_id
|
|
346
|
+
self.last_sensor_actions[i_id] = time.time()
|
|
347
|
+
sensor.on_change(self.security_sensor_change)
|
|
348
|
+
|
|
349
|
+
last_action = self.last_sensor_actions.get(i_id, time.time())
|
|
350
|
+
if state.check_is_away(last_action):
|
|
351
|
+
if state.value != 'away':
|
|
352
|
+
print(f"New main state of "
|
|
353
|
+
f"{state.zone.instance} - away")
|
|
354
|
+
state.send('away')
|
|
355
|
+
else:
|
|
356
|
+
if state.value == 'away':
|
|
357
|
+
try:
|
|
358
|
+
new_state = state.get_day_evening_night_morning()
|
|
359
|
+
except:
|
|
360
|
+
new_state = 'day'
|
|
361
|
+
print(f"New main state of "
|
|
362
|
+
f"{state.zone.instance} - {new_state}")
|
|
363
|
+
state.send(new_state)
|
|
364
|
+
|
|
365
|
+
if state.config.get('sleeping_phones_hour') is not None:
|
|
366
|
+
sleep_time = state.owner_phones_on_sleep()
|
|
367
|
+
if sleep_time and state.value != 'sleep':
|
|
368
|
+
print(f"New main state of {state.zone.instance} - sleep")
|
|
369
|
+
state.send('sleep')
|
|
370
|
+
elif state.value == 'sleep':
|
|
371
|
+
try:
|
|
372
|
+
new_state = state.get_day_evening_night_morning()
|
|
373
|
+
except:
|
|
374
|
+
new_state = 'day'
|
|
375
|
+
print(f"New main state of "
|
|
376
|
+
f"{state.zone.instance} - {new_state}")
|
|
377
|
+
state.send(new_state)
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def watch_main_states(self):
|
|
381
|
+
drop_current_instance()
|
|
382
|
+
from .controllers import MainState
|
|
383
|
+
for state in Component.objects.filter(
|
|
384
|
+
controller_uid=MainState.uid
|
|
385
|
+
).select_related('zone', 'zone__instance'):
|
|
386
|
+
try:
|
|
387
|
+
self.watch_main_state(state)
|
|
388
|
+
except Exception as e:
|
|
389
|
+
print(traceback.format_exc(), file=sys.stderr)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def security_sensor_change(self, sensor):
|
|
394
|
+
self.last_sensor_actions[
|
|
395
|
+
self.sensors_on_watch[sensor.id]
|
|
396
|
+
] = time.time()
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
|
|
245
400
|
class DummyGatewayHandler(BaseObjectCommandsGatewayHandler):
|
|
246
401
|
name = "Dummy"
|
|
247
402
|
config_form = BaseGatewayForm
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
simo/users/admin.py
CHANGED
|
@@ -32,6 +32,15 @@ class PermissionsRoleAdmin(admin.ModelAdmin):
|
|
|
32
32
|
inlines = ComponentPermissionInline,
|
|
33
33
|
list_filter = 'is_superuser', 'is_owner', 'can_manage_users', 'is_default'
|
|
34
34
|
|
|
35
|
+
|
|
36
|
+
def get_queryset(self, request):
|
|
37
|
+
qs = super().get_queryset(request)
|
|
38
|
+
instance = get_current_instance()
|
|
39
|
+
if instance:
|
|
40
|
+
return qs.filter(instance=instance)
|
|
41
|
+
return qs
|
|
42
|
+
|
|
43
|
+
|
|
35
44
|
def save_model(self, request, obj, form, change):
|
|
36
45
|
if not obj.id:
|
|
37
46
|
obj.instance = request.instance
|
simo/users/api.py
CHANGED
|
@@ -11,6 +11,7 @@ from django.utils import timezone
|
|
|
11
11
|
from django_filters.rest_framework import DjangoFilterBackend
|
|
12
12
|
from simo.conf import dynamic_settings
|
|
13
13
|
from simo.core.api import InstanceMixin
|
|
14
|
+
from simo.core.middleware import drop_current_instance
|
|
14
15
|
from .models import (
|
|
15
16
|
User, UserDevice, UserDeviceReportLog, PermissionsRole, InstanceInvitation,
|
|
16
17
|
Fingerprint, ComponentPermission, InstanceUser
|
|
@@ -247,6 +248,7 @@ class UserDeviceReport(InstanceMixin, viewsets.GenericViewSet):
|
|
|
247
248
|
self.instance.location, location
|
|
248
249
|
) < dynamic_settings['users__at_home_radius']
|
|
249
250
|
|
|
251
|
+
drop_current_instance()
|
|
250
252
|
for iu in request.user.instance_roles.filter(is_active=True):
|
|
251
253
|
if not relay:
|
|
252
254
|
iu.at_home = True
|
simo/users/auto_urls.py
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
from django.urls import
|
|
2
|
-
from
|
|
3
|
-
from .views import accept_invitation
|
|
1
|
+
from django.urls import re_path, path
|
|
2
|
+
from .views import accept_invitation, RolesAutocomplete
|
|
4
3
|
|
|
5
4
|
urlpatterns = [
|
|
6
5
|
re_path(
|
|
7
6
|
r"^accept-invitation/(?P<token>[a-zA-Z0-9]+)/$",
|
|
8
7
|
accept_invitation, name='accept_invitation'
|
|
9
8
|
),
|
|
9
|
+
path(
|
|
10
|
+
'autocomplete-roles',
|
|
11
|
+
RolesAutocomplete.as_view(), name='autocomplete-user-roles'
|
|
12
|
+
),
|
|
10
13
|
]
|
simo/users/views.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import re
|
|
2
2
|
from django.contrib.auth.decorators import login_required
|
|
3
|
+
from dal import autocomplete
|
|
3
4
|
from django.contrib.auth import logout
|
|
4
5
|
from django.urls import re_path
|
|
5
6
|
from django.shortcuts import get_object_or_404, render
|
|
@@ -11,8 +12,10 @@ from django.template.loader import render_to_string
|
|
|
11
12
|
from django.http import (
|
|
12
13
|
JsonResponse, HttpResponseRedirect, HttpResponse, Http404
|
|
13
14
|
)
|
|
15
|
+
from simo.core.middleware import get_current_instance
|
|
16
|
+
from simo.core.utils.helpers import search_queryset
|
|
14
17
|
from simo.conf import dynamic_settings
|
|
15
|
-
from .models import InstanceInvitation
|
|
18
|
+
from .models import InstanceInvitation, PermissionsRole, InstanceUser
|
|
16
19
|
|
|
17
20
|
|
|
18
21
|
@atomic
|
|
@@ -104,3 +107,19 @@ def protected_static(prefix, **kwargs):
|
|
|
104
107
|
r'^%s(?P<path>.*)$' % re.escape(prefix.lstrip('/')),
|
|
105
108
|
serve_protected, kwargs={'prefix': prefix}
|
|
106
109
|
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class RolesAutocomplete(autocomplete.Select2QuerySetView):
|
|
113
|
+
|
|
114
|
+
def get_queryset(self):
|
|
115
|
+
if not self.request.user.is_authenticated:
|
|
116
|
+
raise Http404()
|
|
117
|
+
|
|
118
|
+
qs = PermissionsRole.objects.filter(instance=get_current_instance(self.request))
|
|
119
|
+
|
|
120
|
+
if self.request.GET.get('value'):
|
|
121
|
+
qs = qs.filter(pk__in=self.request.GET['value'].split(','))
|
|
122
|
+
elif self.q:
|
|
123
|
+
qs = search_queryset(qs, self.q, ('name',))
|
|
124
|
+
|
|
125
|
+
return qs.distinct()
|