simo 2.3.3__py3-none-any.whl → 2.3.5__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.

Files changed (45) hide show
  1. simo/__pycache__/asgi.cpython-38.pyc +0 -0
  2. simo/__pycache__/settings.cpython-38.pyc +0 -0
  3. simo/backups/__init__.py +0 -0
  4. simo/backups/__pycache__/__init__.cpython-38.pyc +0 -0
  5. simo/backups/__pycache__/admin.cpython-38.pyc +0 -0
  6. simo/backups/__pycache__/dynamic_settings.cpython-38.pyc +0 -0
  7. simo/backups/__pycache__/models.cpython-38.pyc +0 -0
  8. simo/backups/admin.py +19 -0
  9. simo/backups/dynamic_settings.py +21 -0
  10. simo/backups/migrations/0001_initial.py +27 -0
  11. simo/backups/migrations/__init__.py +0 -0
  12. simo/backups/migrations/__pycache__/0001_initial.cpython-38.pyc +0 -0
  13. simo/backups/migrations/__pycache__/__init__.cpython-38.pyc +0 -0
  14. simo/backups/models.py +18 -0
  15. simo/backups/tasks.py +272 -0
  16. simo/core/__pycache__/context.cpython-38.pyc +0 -0
  17. simo/core/__pycache__/dynamic_settings.cpython-38.pyc +0 -0
  18. simo/core/__pycache__/middleware.cpython-38.pyc +0 -0
  19. simo/core/__pycache__/serializers.cpython-38.pyc +0 -0
  20. simo/core/__pycache__/tasks.cpython-38.pyc +0 -0
  21. simo/core/context.py +2 -2
  22. simo/core/middleware.py +16 -0
  23. simo/core/serializers.py +3 -1
  24. simo/core/tasks.py +0 -2
  25. simo/fleet/__pycache__/controllers.cpython-38.pyc +0 -0
  26. simo/fleet/__pycache__/forms.cpython-38.pyc +0 -0
  27. simo/fleet/controllers.py +1 -2
  28. simo/fleet/forms.py +6 -0
  29. simo/generic/__pycache__/forms.cpython-38.pyc +0 -0
  30. simo/generic/__pycache__/socket_consumers.cpython-38.pyc +0 -0
  31. simo/generic/forms.py +4 -2
  32. simo/settings.py +1 -0
  33. simo/users/__pycache__/admin.cpython-38.pyc +0 -0
  34. simo/users/__pycache__/models.cpython-38.pyc +0 -0
  35. simo/users/admin.py +14 -15
  36. simo/users/models.py +22 -20
  37. {simo-2.3.3.dist-info → simo-2.3.5.dist-info}/METADATA +1 -1
  38. {simo-2.3.3.dist-info → simo-2.3.5.dist-info}/RECORD +42 -32
  39. simo/core/templates/admin/formset_widget_old.html +0 -122
  40. simo/core/templates/setup_wizard/clearable_easy_thumbnails_widget.html +0 -15
  41. simo/core/templates/setup_wizard/form.html +0 -225
  42. {simo-2.3.3.dist-info → simo-2.3.5.dist-info}/LICENSE.md +0 -0
  43. {simo-2.3.3.dist-info → simo-2.3.5.dist-info}/WHEEL +0 -0
  44. {simo-2.3.3.dist-info → simo-2.3.5.dist-info}/entry_points.txt +0 -0
  45. {simo-2.3.3.dist-info → simo-2.3.5.dist-info}/top_level.txt +0 -0
Binary file
Binary file
File without changes
simo/backups/admin.py ADDED
@@ -0,0 +1,19 @@
1
+ from django.contrib import admin
2
+ from .models import Backup
3
+
4
+
5
+ @admin.register(Backup)
6
+ class BackupAdmin(admin.ModelAdmin):
7
+ list_display = 'datetime', 'device', 'filepath'
8
+ fields = 'datetime', 'device', 'filepath'
9
+ readonly_fields = 'datetime', 'device', 'filepath'
10
+ list_filter = 'datetime', 'mac',
11
+
12
+ def has_change_permission(self, request, obj=None):
13
+ return False
14
+
15
+ def has_add_permission(self, request):
16
+ return False
17
+
18
+ def has_delete_permission(self, request, obj=None):
19
+ return False
@@ -0,0 +1,21 @@
1
+ from dynamic_preferences.preferences import Section
2
+ from dynamic_preferences.types import (
3
+ BooleanPreference, StringPreference, ChoicePreference, IntegerPreference,
4
+ )
5
+ from dynamic_preferences.registries import global_preferences_registry
6
+
7
+ backups = Section('backups')
8
+
9
+
10
+ @global_preferences_registry.register
11
+ class LastBackupError(StringPreference):
12
+ section = backups
13
+ name = 'last_error'
14
+ default = ''
15
+
16
+
17
+ @global_preferences_registry.register
18
+ class LastBackupCheck(IntegerPreference):
19
+ section = backups
20
+ name = 'last_check'
21
+ default = 0
@@ -0,0 +1,27 @@
1
+ # Generated by Django 4.2.10 on 2024-10-01 13:01
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ initial = True
9
+
10
+ dependencies = [
11
+ ]
12
+
13
+ operations = [
14
+ migrations.CreateModel(
15
+ name='Backup',
16
+ fields=[
17
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18
+ ('datetime', models.DateTimeField(db_index=True)),
19
+ ('mac', models.CharField(db_index=True, max_length=100)),
20
+ ('filepath', models.CharField(max_length=200)),
21
+ ],
22
+ options={
23
+ 'ordering': ('datetime',),
24
+ 'unique_together': {('datetime', 'mac')},
25
+ },
26
+ ),
27
+ ]
File without changes
simo/backups/models.py ADDED
@@ -0,0 +1,18 @@
1
+ import uuid
2
+ from django.db import models
3
+
4
+
5
+ class Backup(models.Model):
6
+ datetime = models.DateTimeField(db_index=True)
7
+ mac = models.CharField(max_length=100, db_index=True)
8
+ filepath = models.CharField(max_length=200)
9
+
10
+ class Meta:
11
+ unique_together = 'datetime', 'mac'
12
+ ordering = 'datetime',
13
+
14
+ @property
15
+ def device(self):
16
+ if self.mac == str(hex(uuid.getnode())):
17
+ return "This machine"
18
+ return self.mac
simo/backups/tasks.py ADDED
@@ -0,0 +1,272 @@
1
+ import os, subprocess, json, uuid, datetime, shutil
2
+ from datetime import datetime, timezone
3
+ from celeryc import celery_app
4
+ from simo.conf import dynamic_settings
5
+
6
+
7
+ @celery_app.task
8
+ def check_backups():
9
+ '''
10
+ syncs up backups on external medium to the database
11
+ '''
12
+ from .models import Backup
13
+
14
+ try:
15
+ lv_group, lv_name, mountpoint = get_partitions()
16
+ except:
17
+ return Backup.objects.all().delete()
18
+
19
+
20
+ backups_dir = os.path.join(mountpoint, 'simo_backups')
21
+ if not os.path.exists(backups_dir):
22
+ return Backup.objects.all().delete()
23
+
24
+ backups_mentioned = []
25
+ for item in os.listdir(backups_dir):
26
+ if not item.startswith('hub-'):
27
+ continue
28
+ hub_mac = item.split('-')[1]
29
+ hub_dir = os.path.join(backups_dir, item)
30
+ for month_folder in os.listdir(hub_dir):
31
+ try:
32
+ year, month = month_folder.split('-')
33
+ year, month = int(year), int(month)
34
+ except:
35
+ continue
36
+ for filename in os.listdir(os.path.join(hub_dir, month_folder)):
37
+ try:
38
+ day, time, back = filename.split('.')
39
+ hour, minute, second = time.split('-')
40
+ day, hour, minute, second = \
41
+ int(day), int(hour), int(minute), int(second)
42
+ except:
43
+ continue
44
+
45
+ obj, new = Backup.objects.update_or_create(
46
+ datetime=datetime(
47
+ year, month, day, hour, minute, second,
48
+ tzinfo=timezone.utc
49
+ ), mac=hub_mac, defaults={
50
+ 'filepath': os.path.join(
51
+ hub_dir, month_folder, filename
52
+ )
53
+ }
54
+ )
55
+ backups_mentioned.append(obj.id)
56
+
57
+ Backup.objects.all().exclude(id__in=backups_mentioned).delete()
58
+
59
+ dynamic_settings['backups__last_check'] = datetime.now()
60
+
61
+
62
+ def create_snap(lv_group, lv_name):
63
+ try:
64
+ return subprocess.check_output(
65
+ f'lvcreate -s -n {lv_name}-snap {lv_group}/{lv_name} -L 3G',
66
+ shell=True
67
+ ).decode()
68
+ except:
69
+ return ''
70
+
71
+
72
+ def get_lvm_partition(lsblk_data):
73
+ for device in lsblk_data:
74
+ if device['type'] == 'lvm' and device['mountpoint'] == '/':
75
+ return device
76
+ if 'children' in device:
77
+ return get_lvm_partition(device['children'])
78
+
79
+
80
+ def get_backup_device(lsblk_data):
81
+ for device in lsblk_data:
82
+ if not device['hotplug']:
83
+ continue
84
+ target_device = None
85
+ if device.get('fstype') == 'exfat':
86
+ target_device = device
87
+ elif device.get('children'):
88
+ for child in device.get('children'):
89
+ if child.get('fstype') == 'exfat':
90
+ target_device = child
91
+ if target_device:
92
+ return target_device
93
+
94
+
95
+ def get_partitions():
96
+
97
+ lsblk_data = json.loads(subprocess.check_output(
98
+ 'lsblk --output NAME,HOTPLUG,MOUNTPOINT,FSTYPE,TYPE,LABEL,PARTLABEL --json',
99
+ shell=True
100
+ ).decode())['blockdevices']
101
+
102
+ # Figure out if we are running in an LVM group
103
+
104
+ lvm_partition = get_lvm_partition(lsblk_data)
105
+ if not lvm_partition:
106
+ print("No LVM partition!")
107
+ dynamic_settings['backups__last_error'] = 'No LVM partition!'
108
+ return
109
+
110
+ try:
111
+ name = lvm_partition.get('name')
112
+ split_at = name.find('-')
113
+ lv_group = name[:split_at]
114
+ lv_name = name[split_at + 1:].replace('--', '-')
115
+ except:
116
+ print("Failed to identify LVM partition")
117
+ dynamic_settings['backups__last_error'] = \
118
+ 'Failed to identify LVM partition'
119
+ return
120
+
121
+ if not lv_name:
122
+ print("LVM was not found on this system. Abort!")
123
+ dynamic_settings['backups__last_error'] = \
124
+ 'Failed to identify LVM partition name'
125
+ return
126
+
127
+
128
+ # check if we have any removable devices storage devices plugged in
129
+
130
+ backup_device = get_backup_device(lsblk_data)
131
+
132
+ if not backup_device:
133
+ dynamic_settings['backups__last_error'] = \
134
+ 'No external exFAT backup device on this machine'
135
+ return
136
+
137
+ if lvm_partition.get('partlabel'):
138
+ mountpoint = f"/media/{backup_device['partlabel']}"
139
+ elif lvm_partition.get('label'):
140
+ mountpoint = f"/media/{backup_device['label']}"
141
+ else:
142
+ mountpoint = f"/media/{backup_device['name']}"
143
+
144
+ if not os.path.exists(mountpoint):
145
+ os.makedirs(mountpoint)
146
+
147
+ if backup_device.get('mountpoint') != mountpoint:
148
+
149
+ if backup_device.get('mountpoint'):
150
+ subprocess.call(f"umount {backup_device['mountpoint']}", shell=True)
151
+
152
+ subprocess.call(
153
+ f'mount /dev/{backup_device["name"]} {mountpoint}', shell=True,
154
+ stdout=subprocess.PIPE
155
+ )
156
+
157
+ return lv_group, lv_name, mountpoint
158
+
159
+
160
+ @celery_app.task
161
+ def perform_backup():
162
+
163
+ try:
164
+ lv_group, lv_name, mountpoint = get_partitions()
165
+ except:
166
+ return
167
+
168
+ output = create_snap(lv_group, lv_name)
169
+ if not output:
170
+ subprocess.check_output(
171
+ f'lvremove -f {lv_group}/{lv_name}-snap',
172
+ shell=True
173
+ )
174
+ output = create_snap(lv_group, lv_name)
175
+
176
+ if f'Logical volume "{lv_name}-snap" created' not in output:
177
+ print(output)
178
+ print(f"Unable to create {lv_name}-snap.")
179
+ return
180
+
181
+ mac = str(hex(uuid.getnode()))
182
+ device_backups_path = f'{mountpoint}/simo_backups/hub-{mac}'
183
+ if not os.path.exists(device_backups_path):
184
+ os.makedirs(device_backups_path)
185
+
186
+ now = datetime.datetime.now()
187
+ level = now.day
188
+ month_folder = os.path.join(
189
+ device_backups_path, f'{now.year}-{now.month}'
190
+ )
191
+ if not os.path.exists(month_folder):
192
+ os.makedirs(month_folder)
193
+ level = 0
194
+
195
+ if level != 0:
196
+ # check if level 0 exists
197
+ level_0_exists = False
198
+ for filename in os.listdir(month_folder):
199
+ if '-' not in filename:
200
+ continue
201
+ try:
202
+ level, date = filename.split('-')
203
+ level = int(level)
204
+ if level == 0:
205
+ level_0_exists = True
206
+ break
207
+ except:
208
+ continue
209
+ if not level_0_exists:
210
+ print("Level 0 does not exist! Backups must be started from 0!")
211
+ shutil.rmtree(month_folder)
212
+ os.makedirs(month_folder)
213
+ level = 0
214
+
215
+ time_mark = now.strftime("%H-%M-%S")
216
+ backup_file = f"{month_folder}/{now.day}.{time_mark}.back"
217
+ snap_mapper = f"/dev/mapper/{lv_group}-{lv_name.replace('-', '--')}--snap"
218
+ label = f"simo {now.strftime('%Y-%m-%d')}"
219
+ dumpdates_file = os.path.join(month_folder, 'dumpdates')
220
+
221
+ estimated_size = int(subprocess.check_output(
222
+ f'dump -{level} -Squz9 -b 1024 {snap_mapper}',
223
+ shell=True
224
+ )) * 0.5
225
+
226
+ folders = []
227
+ for item in os.listdir(device_backups_path):
228
+ if not os.path.isdir(os.path.join(device_backups_path, item)):
229
+ continue
230
+ try:
231
+ year, month = item.split('-')
232
+ folders.append([
233
+ os.path.join(device_backups_path, item),
234
+ int(year) * 12 + int(month)
235
+ ])
236
+ except:
237
+ continue
238
+ folders.sort(key=lambda v: v[1])
239
+
240
+ # delete old backups to free up space required for this backup to take place
241
+ while shutil.disk_usage('/media/backup').free < estimated_size:
242
+ remove_folder = folders.pop()[0]
243
+ print(f"REMOVE: {remove_folder}")
244
+ shutil.rmtree(remove_folder)
245
+
246
+ success = False
247
+ try:
248
+ subprocess.check_call(
249
+ f'dump -{level} -quz9 -b 1024 -L "{label}" -D {dumpdates_file} -f {backup_file} {snap_mapper}',
250
+ shell=True,
251
+ )
252
+ success = True
253
+ except:
254
+ try:
255
+ os.remove(backup_file)
256
+ print("Dump failed!")
257
+ except:
258
+ pass
259
+
260
+ subprocess.call(
261
+ f"lvremove -f {lv_group}/{lv_name}-snap", shell=True,
262
+ stdout=subprocess.PIPE
263
+ )
264
+ if success:
265
+ print("DONE!")
266
+ dynamic_settings['backups__last_error'] = ''
267
+
268
+
269
+ @celery_app.on_after_finalize.connect
270
+ def setup_periodic_tasks(sender, **kwargs):
271
+ sender.add_periodic_task(60 * 60, check_backups.s())
272
+ sender.add_periodic_task(60 * 60 * 8, perform_backup.s()) # perform auto backup every 8 hours
Binary file
simo/core/context.py CHANGED
@@ -15,11 +15,11 @@ def additional_templates_context(request):
15
15
  'dynamic_settings': dynamic_settings,
16
16
  'current_version': pkg_resources.get_distribution('simo').version,
17
17
  'update_available': is_update_available(True),
18
- 'instances': Instance.objects.all(),
18
+ 'instances': request.user.instances,
19
19
  'current_instance': get_current_instance()
20
20
  }
21
21
 
22
- if request.path.endswith('/admin/'):
22
+ if request.path == '/admin/':
23
23
  ctx['todos'] = []
24
24
  for app_name, app in apps.app_configs.items():
25
25
  try:
simo/core/middleware.py CHANGED
@@ -2,6 +2,7 @@ import pytz
2
2
  import threading
3
3
  import re
4
4
  from django.utils import timezone
5
+ from django.shortcuts import render
5
6
 
6
7
 
7
8
  _thread_locals = threading.local()
@@ -15,9 +16,13 @@ def get_current_request():
15
16
 
16
17
 
17
18
  def introduce_instance(instance, request=None):
19
+ if request and request.user.is_authenticated \
20
+ and instance not in request.user.instances:
21
+ return
18
22
  _thread_locals.instance = instance
19
23
  if request:
20
24
  request.session['instance_id'] = instance.id
25
+ request.instance = instance
21
26
 
22
27
 
23
28
  def get_current_instance(request=None):
@@ -51,6 +56,17 @@ def simo_router_middleware(get_response):
51
56
  def instance_middleware(get_response):
52
57
 
53
58
  def middleware(request):
59
+
60
+ if request.path.startswith('/admin'):
61
+ if not (request.user.is_authenticated and request.user.is_master):
62
+ return render(request, 'admin/msg_page.html', {
63
+ 'page_title': "You are not allowed in here",
64
+ 'msg': "Page you are trying to access is only for hub masters.",
65
+ 'suggestion': "Try switching your user to the one who has proper "
66
+ "rights to come here or ask for somebody who already has "
67
+ "master rights enable these rights for you."
68
+ })
69
+
54
70
  from simo.core.models import Instance
55
71
 
56
72
  instance = None
simo/core/serializers.py CHANGED
@@ -416,11 +416,13 @@ class ComponentSerializer(FormSerializer):
416
416
  controller_uid=controller_uid, instance=instance,
417
417
  **kwargs
418
418
  )
419
+ # only masters and superusers can fully manage components via app
420
+ # others can only change basic fields
419
421
  if not self.context['request'].user.is_master:
420
422
  user_role = self.context['request'].user.get_role(
421
423
  self.context['instance']
422
424
  )
423
- if not user_role.is_superuser and user_role.is_owner:
425
+ if not user_role.is_superuser:
424
426
  for field_name in list(form.fields.keys()):
425
427
  if field_name not in form.basic_fields:
426
428
  del form.fields[field_name]
simo/core/tasks.py CHANGED
@@ -7,8 +7,6 @@ import requests
7
7
  import subprocess
8
8
  import threading
9
9
  import pkg_resources
10
- import sys
11
- import traceback
12
10
  import uuid
13
11
  from django.db.models import Q
14
12
  from django.db import connection, transaction
Binary file
simo/fleet/controllers.py CHANGED
@@ -424,12 +424,11 @@ class PWMOutput(FadeMixin, FleeDeviceMixin, BasicOutputMixin, BaseDimmer):
424
424
  return value
425
425
 
426
426
 
427
-
428
427
  class RGBLight(FleeDeviceMixin, BasicOutputMixin, BaseRGBWLight):
429
428
  config_form = ColonelRGBLightConfigForm
430
429
 
431
430
 
432
- class DualMotorValve(FleeDeviceMixin, BasicOutputMixin, BaseSwitch):
431
+ class DualMotorValve(FleeDeviceMixin, BasicOutputMixin, BaseDimmer):
433
432
  gateway_class = FleetGatewayHandler
434
433
  config_form = DualMotorValveForm
435
434
  name = "Dual Motor Valve"
simo/fleet/forms.py CHANGED
@@ -1109,6 +1109,12 @@ class BlindsConfigForm(ColonelComponentForm):
1109
1109
  help_text="Time in seconds it takes for your blinds to go "
1110
1110
  "from fully closed to fully open."
1111
1111
  )
1112
+ close_duration = forms.FloatField(
1113
+ label='Close duration', min_value=1, max_value=360000,
1114
+ initial=30,
1115
+ help_text="Time in seconds it takes for your blinds to go "
1116
+ "from fully open to fully closed."
1117
+ )
1112
1118
  control_type = forms.ChoiceField(
1113
1119
  initial=0, required=True, choices=(
1114
1120
  ('hold', "Hold"), ('click', 'Click')
simo/generic/forms.py CHANGED
@@ -7,7 +7,7 @@ from django.contrib.contenttypes.models import ContentType
7
7
  from simo.core.forms import HiddenField, BaseComponentForm
8
8
  from simo.core.models import Icon, Component
9
9
  from simo.core.controllers import (
10
- BEFORE_SET, BinarySensor, NumericSensor, MultiSensor, Switch
10
+ BEFORE_SET, BinarySensor, NumericSensor, MultiSensor, Switch, Dimmer
11
11
  )
12
12
  from simo.core.widgets import PythonCode, LogOutputWidget
13
13
  from dal import autocomplete, forward
@@ -615,7 +615,9 @@ class ContourForm(forms.Form):
615
615
 
616
616
  name = forms.CharField()
617
617
  switch = forms.ModelChoiceField(
618
- Component.objects.filter(base_type=Switch.base_type),
618
+ Component.objects.filter(
619
+ base_type__in=(Switch.base_type, Dimmer.base_type)
620
+ ),
619
621
  widget=autocomplete.ModelSelect2(
620
622
  url='autocomplete-component', attrs={'data-html': True},
621
623
  forward=(
simo/settings.py CHANGED
@@ -72,6 +72,7 @@ INSTALLED_APPS = [
72
72
  'simo.generic',
73
73
  'simo.multimedia',
74
74
  'simo.fleet',
75
+ 'simo.backups',
75
76
 
76
77
  'django.contrib.admin',
77
78
  'adminsortable2',
Binary file
simo/users/admin.py CHANGED
@@ -25,16 +25,20 @@ class ComponentPermissionInline(admin.TabularInline):
25
25
 
26
26
  @admin.register(PermissionsRole)
27
27
  class PermissionsRoleAdmin(admin.ModelAdmin):
28
- list_display = 'name', 'instance', 'is_superuser', 'is_default'
28
+ list_display = 'name', 'is_superuser', 'is_default'
29
29
  search_fields = 'name',
30
- list_filter = 'instance',
31
30
  inlines = ComponentPermissionInline,
32
31
 
33
32
  def get_queryset(self, request):
34
33
  qs = super().get_queryset(request)
35
34
  if request.user.is_master:
36
35
  return qs
37
- return qs.filter(instance__in=request.user.instances)
36
+ return qs.filter(instance=request.instance)
37
+
38
+ def save_model(self, request, obj, form, change):
39
+ if not obj.id:
40
+ obj.instance = request.instance
41
+ obj.save()
38
42
 
39
43
  def get_fields(self, request, obj=None):
40
44
  if request.user.is_master:
@@ -49,7 +53,7 @@ class PermissionsRoleAdmin(admin.ModelAdmin):
49
53
  class InstanceUserInline(admin.TabularInline):
50
54
  model = InstanceUser
51
55
  extra = 0
52
- readonly_fields = 'instance', 'at_home'
56
+ readonly_fields = 'at_home',
53
57
 
54
58
 
55
59
  @admin.register(User)
@@ -102,7 +106,7 @@ class UserAdmin(OrgUserAdmin):
102
106
  qs = super().get_queryset(request)
103
107
  if request.user.is_master:
104
108
  return qs
105
- return qs.filter(instance_roles__instance__in=request.user.instances)
109
+ return qs.filter(instance_roles__instance=request.instance)
106
110
 
107
111
 
108
112
  from django.contrib.auth.models import Group
@@ -127,11 +131,9 @@ class UserDeviceLogInline(admin.ModelAdmin):
127
131
 
128
132
  def get_queryset(self, request):
129
133
  qs = super().get_queryset(request)
130
- if request.user.is_master:
131
- return qs
132
134
  return qs.filter(
133
- user_device__user__role__instance__in=request.user.instances
134
- )
135
+ user_device__users__roles__instance=request.instance
136
+ ).distinct()
135
137
 
136
138
 
137
139
  @admin.register(UserDevice)
@@ -144,9 +146,7 @@ class UserDeviceAdmin(admin.ModelAdmin):
144
146
 
145
147
  def get_queryset(self, request):
146
148
  qs = super().get_queryset(request)
147
- if request.user.is_master:
148
- return qs
149
- return qs.filter(user__role__instance__in=request.user.instances)
149
+ return qs.filter(users__roles__instance=request.instance).distinct()
150
150
 
151
151
  def users_display(self, obj):
152
152
  return ', '.join([str(u) for u in obj.users.all()])
@@ -157,13 +157,12 @@ class UserDeviceAdmin(admin.ModelAdmin):
157
157
  @admin.register(InstanceInvitation)
158
158
  class InstanceInvitationAdmin(admin.ModelAdmin):
159
159
  list_display = (
160
- 'token', 'instance', 'from_user', 'to_email', 'role',
160
+ 'token', 'from_user', 'to_email', 'role',
161
161
  'issue_date', 'taken_by', 'taken_date'
162
162
  )
163
163
  readonly_fields = (
164
164
  'token', 'issue_date', 'from_user', 'taken_by', 'taken_date'
165
165
  )
166
- list_filter = 'instance',
167
166
 
168
167
  actions = ['send', ]
169
168
 
@@ -171,7 +170,7 @@ class InstanceInvitationAdmin(admin.ModelAdmin):
171
170
  qs = super().get_queryset(request)
172
171
  if request.user.is_master:
173
172
  return qs
174
- return qs.filter(instance__in=request.user.instances)
173
+ return qs.filter(instance=request.instance)
175
174
 
176
175
  def send(self, request, queryset):
177
176
  invitations_sent = 0