simo 2.3.2__py3-none-any.whl → 2.3.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/__pycache__/asgi.cpython-38.pyc +0 -0
- simo/__pycache__/settings.cpython-38.pyc +0 -0
- simo/backups/__init__.py +0 -0
- simo/backups/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/backups/__pycache__/admin.cpython-38.pyc +0 -0
- simo/backups/__pycache__/dynamic_settings.cpython-38.pyc +0 -0
- simo/backups/__pycache__/models.cpython-38.pyc +0 -0
- simo/backups/admin.py +19 -0
- simo/backups/dynamic_settings.py +21 -0
- simo/backups/migrations/0001_initial.py +27 -0
- simo/backups/migrations/__init__.py +0 -0
- simo/backups/migrations/__pycache__/0001_initial.cpython-38.pyc +0 -0
- simo/backups/migrations/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/backups/models.py +18 -0
- simo/backups/tasks.py +272 -0
- simo/core/__pycache__/context.cpython-38.pyc +0 -0
- simo/core/__pycache__/dynamic_settings.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__/tasks.cpython-38.pyc +0 -0
- simo/core/context.py +2 -2
- simo/core/management/commands/on_http_start.py +1 -2
- simo/core/management/update.py +0 -19
- simo/core/middleware.py +16 -0
- simo/core/serializers.py +3 -1
- simo/core/tasks.py +0 -2
- simo/fleet/__pycache__/controllers.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/forms.cpython-38.pyc +0 -0
- simo/fleet/controllers.py +1 -2
- simo/fleet/forms.py +6 -0
- simo/generic/__pycache__/forms.cpython-38.pyc +0 -0
- simo/generic/__pycache__/socket_consumers.cpython-38.pyc +0 -0
- simo/generic/forms.py +4 -2
- simo/settings.py +1 -0
- simo/users/__pycache__/admin.cpython-38.pyc +0 -0
- simo/users/__pycache__/models.cpython-38.pyc +0 -0
- simo/users/admin.py +14 -15
- simo/users/models.py +22 -20
- {simo-2.3.2.dist-info → simo-2.3.4.dist-info}/METADATA +1 -1
- {simo-2.3.2.dist-info → simo-2.3.4.dist-info}/RECORD +44 -34
- simo/core/templates/admin/formset_widget_old.html +0 -122
- simo/core/templates/setup_wizard/clearable_easy_thumbnails_widget.html +0 -15
- simo/core/templates/setup_wizard/form.html +0 -225
- {simo-2.3.2.dist-info → simo-2.3.4.dist-info}/LICENSE.md +0 -0
- {simo-2.3.2.dist-info → simo-2.3.4.dist-info}/WHEEL +0 -0
- {simo-2.3.2.dist-info → simo-2.3.4.dist-info}/entry_points.txt +0 -0
- {simo-2.3.2.dist-info → simo-2.3.4.dist-info}/top_level.txt +0 -0
|
Binary file
|
|
Binary file
|
simo/backups/__init__.py
ADDED
|
File without changes
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
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
|
|
Binary file
|
|
Binary file
|
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
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
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':
|
|
18
|
+
'instances': request.user.instances,
|
|
19
19
|
'current_instance': get_current_instance()
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
if request.path
|
|
22
|
+
if request.path == '/admin/':
|
|
23
23
|
ctx['todos'] = []
|
|
24
24
|
for app_name, app in apps.app_configs.items():
|
|
25
25
|
try:
|
|
@@ -74,11 +74,10 @@ def update_auto_update():
|
|
|
74
74
|
cron.write()
|
|
75
75
|
|
|
76
76
|
|
|
77
|
-
|
|
78
|
-
|
|
79
77
|
class Command(BaseCommand):
|
|
80
78
|
|
|
81
79
|
def handle(self, *args, **options):
|
|
82
80
|
prepare_mosquitto()
|
|
83
81
|
from simo.core.tasks import maybe_update_to_latest
|
|
84
82
|
maybe_update_to_latest.delay()
|
|
83
|
+
update_auto_update()
|
simo/core/management/update.py
CHANGED
|
@@ -8,23 +8,6 @@ import pkg_resources
|
|
|
8
8
|
HUB_DIR = '/etc/SIMO/hub'
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
def install_dependencies():
|
|
12
|
-
|
|
13
|
-
status = subprocess.call(
|
|
14
|
-
'apt install postgresql libpq-dev postgresql-client '
|
|
15
|
-
'postgresql-client-common python3-pip redis-server supervisor '
|
|
16
|
-
'mosquitto libopenjp2-7 libtiff5-dev pkg-config libcairo2-dev '
|
|
17
|
-
'libgirepository1.0-dev libcairo2 libudev-dev gdal-bin net-tools '
|
|
18
|
-
'nginx postgis openvpn ffmpeg libsm6 libxext6 ssh keychain -y',
|
|
19
|
-
shell=True
|
|
20
|
-
)
|
|
21
|
-
if status != 0:
|
|
22
|
-
print("Unable install required packages.")
|
|
23
|
-
return
|
|
24
|
-
|
|
25
|
-
return True
|
|
26
|
-
|
|
27
|
-
|
|
28
11
|
def perform_update():
|
|
29
12
|
|
|
30
13
|
proc = subprocess.Popen(
|
|
@@ -35,8 +18,6 @@ def perform_update():
|
|
|
35
18
|
if proc.returncode:
|
|
36
19
|
raise Exception(err.decode())
|
|
37
20
|
|
|
38
|
-
install_dependencies()
|
|
39
|
-
|
|
40
21
|
proc = subprocess.Popen(
|
|
41
22
|
[os.path.join(HUB_DIR, 'manage.py'), 'migrate'],
|
|
42
23
|
cwd=HUB_DIR,
|
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.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
|
|
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
|
Binary file
|
|
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,
|
|
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')
|
|
Binary file
|
|
Binary file
|
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(
|
|
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
|
Binary file
|
|
Binary file
|