simo 2.0.42__py3-none-any.whl → 2.1.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__/asgi.cpython-38.pyc +0 -0
- simo/__pycache__/settings.cpython-38.pyc +0 -0
- simo/__pycache__/wsgi.cpython-38.pyc +0 -0
- simo/asgi.py +1 -1
- simo/core/__init__.py +1 -0
- simo/core/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/core/__pycache__/admin.cpython-38.pyc +0 -0
- simo/core/__pycache__/api.cpython-38.pyc +0 -0
- simo/core/__pycache__/api_meta.cpython-38.pyc +0 -0
- simo/core/__pycache__/app_widgets.cpython-38.pyc +0 -0
- simo/core/__pycache__/apps.cpython-38.pyc +0 -0
- simo/core/__pycache__/auto_urls.cpython-38.pyc +0 -0
- simo/core/__pycache__/base_types.cpython-38.pyc +0 -0
- simo/core/__pycache__/controllers.cpython-38.pyc +0 -0
- simo/core/__pycache__/dynamic_settings.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__/managers.cpython-38.pyc +0 -0
- simo/core/__pycache__/models.cpython-38.pyc +0 -0
- simo/core/__pycache__/permissions.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/__pycache__/tasks.cpython-38.pyc +0 -0
- simo/core/__pycache__/views.cpython-38.pyc +0 -0
- simo/core/admin.py +26 -26
- simo/core/api.py +22 -2
- simo/core/api_meta.py +23 -13
- simo/core/app_widgets.py +6 -0
- simo/core/apps.py +13 -0
- simo/core/auto_urls.py +2 -3
- simo/core/base_types.py +1 -0
- simo/core/controllers.py +57 -0
- simo/core/dynamic_settings.py +0 -8
- simo/core/form_fields.py +93 -0
- simo/core/forms.py +16 -101
- simo/core/gateways.py +1 -1
- simo/core/managers.py +14 -1
- simo/core/migrations/0037_auto_20240606_1057.py +33 -0
- simo/core/migrations/0038_remove_instance_cover_image_and_more.py +30 -0
- simo/core/migrations/__pycache__/0037_auto_20240606_1057.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0038_remove_instance_cover_image_and_more.cpython-38.pyc +0 -0
- simo/core/models.py +30 -16
- simo/core/permissions.py +6 -3
- simo/core/serializers.py +77 -5
- simo/core/signal_receivers.py +25 -0
- simo/core/static/admin/css/simo.css +14 -0
- simo/core/tasks.py +82 -49
- simo/core/templates/admin/controller_widgets/button.html +8 -0
- simo/core/templates/admin/core/component_change_form.html +97 -0
- simo/core/templates/admin/formset_widget.html +88 -118
- simo/core/templates/admin/formset_widget_old.html +122 -0
- simo/core/templates/admin/user_tools.html +0 -3
- simo/core/templates/admin/wizard/wizard_add.html +16 -9
- simo/core/utils/__pycache__/admin.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/cache.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/formsets.cpython-38.pyc +0 -0
- simo/core/utils/admin.py +11 -0
- simo/core/utils/cache.py +15 -0
- simo/core/utils/formsets.py +11 -18
- simo/core/views.py +2 -85
- 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__/gateways.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/models.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/socket_consumers.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/utils.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/views.cpython-38.pyc +0 -0
- simo/fleet/auto_urls.py +7 -1
- simo/fleet/controllers.py +194 -31
- simo/fleet/forms.py +223 -87
- simo/fleet/gateways.py +53 -2
- simo/fleet/migrations/0036_auto_20240605_0702.py +68 -0
- simo/fleet/migrations/0037_alter_colonelpin_options_alter_colonelpin_no_and_more.py +27 -0
- simo/fleet/migrations/__pycache__/0036_auto_20240605_0702.cpython-38.pyc +0 -0
- simo/fleet/migrations/__pycache__/0037_alter_colonelpin_options_alter_colonelpin_no_and_more.cpython-38.pyc +0 -0
- simo/fleet/models.py +35 -6
- simo/fleet/socket_consumers.py +1 -1
- simo/fleet/templates/fleet/controllers_info/button.md +16 -0
- simo/fleet/utils.py +31 -1
- simo/fleet/views.py +45 -0
- 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 +61 -16
- simo/generic/forms.py +0 -3
- simo/generic/gateways.py +2 -0
- simo/generic/templates/admin/controller_widgets/blinds.html +2 -1
- simo/generic/templates/admin/controller_widgets/weather_forecast.html +1 -1
- simo/generic/templates/generic/controllers_info/dummy.md +3 -0
- simo/generic/templates/generic/controllers_info/stateselect.md +2 -0
- simo/management/__init__.py +0 -0
- simo/management/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/management/__pycache__/on_http_start.cpython-38.pyc +0 -0
- simo/{_hub_template → management/_hub_template}/hub/nginx.conf +2 -2
- simo/{auto_update.py → management/auto_update.py} +3 -0
- simo/{cli.py → management/copy_template.py} +3 -16
- simo/management/install.py +258 -0
- simo/{on_http_start.py → management/on_http_start.py} +22 -2
- simo/settings.py +20 -4
- simo/users/__init__.py +1 -0
- simo/users/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/users/__pycache__/admin.cpython-38.pyc +0 -0
- simo/users/__pycache__/apps.cpython-38.pyc +0 -0
- simo/users/__pycache__/models.cpython-38.pyc +0 -0
- simo/users/apps.py +9 -0
- simo/users/migrations/__pycache__/0029_alter_instanceuser_options_instanceuser_order.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0030_alter_instanceuser_options_remove_instanceuser_order.cpython-38.pyc +0 -0
- simo/users/models.py +16 -3
- {simo-2.0.42.dist-info → simo-2.1.2.dist-info}/METADATA +5 -3
- {simo-2.0.42.dist-info → simo-2.1.2.dist-info}/RECORD +122 -95
- simo-2.1.2.dist-info/entry_points.txt +2 -0
- simo/__pycache__/on_http_start.cpython-38.pyc +0 -0
- simo/wsgi.py +0 -7
- /simo/{_hub_template → management/_hub_template}/hub/asgi.py +0 -0
- /simo/{_hub_template → management/_hub_template}/hub/celeryc.py +0 -0
- /simo/{_hub_template → management/_hub_template}/hub/manage.py +0 -0
- /simo/{_hub_template → management/_hub_template}/hub/settings.py +0 -0
- /simo/{_hub_template → management/_hub_template}/hub/supervisor.conf +0 -0
- /simo/{_hub_template → management/_hub_template}/hub/urls.py +0 -0
- {simo-2.0.42.dist-info → simo-2.1.2.dist-info}/LICENSE.md +0 -0
- {simo-2.0.42.dist-info → simo-2.1.2.dist-info}/WHEEL +0 -0
- {simo-2.0.42.dist-info → simo-2.1.2.dist-info}/top_level.txt +0 -0
simo/core/signal_receivers.py
CHANGED
|
@@ -5,6 +5,7 @@ from django.db.models.signals import post_save, post_delete
|
|
|
5
5
|
from django.dispatch import receiver
|
|
6
6
|
from django.utils import timezone
|
|
7
7
|
from django.conf import settings
|
|
8
|
+
from actstream import action
|
|
8
9
|
from simo.users.models import PermissionsRole
|
|
9
10
|
from .models import Instance, Gateway, Component, Icon, Zone, Category
|
|
10
11
|
|
|
@@ -14,6 +15,14 @@ def create_instance_defaults(sender, instance, created, **kwargs):
|
|
|
14
15
|
if not created:
|
|
15
16
|
return
|
|
16
17
|
|
|
18
|
+
from simo.users.middleware import get_current_user
|
|
19
|
+
actor = get_current_user()
|
|
20
|
+
action.send(
|
|
21
|
+
actor, target=instance, verb="instance created",
|
|
22
|
+
instance_id=instance.id,
|
|
23
|
+
action_type='management_event'
|
|
24
|
+
)
|
|
25
|
+
|
|
17
26
|
# Create default zones
|
|
18
27
|
|
|
19
28
|
for zone_name in (
|
|
@@ -86,6 +95,22 @@ def create_instance_defaults(sender, instance, created, **kwargs):
|
|
|
86
95
|
)
|
|
87
96
|
|
|
88
97
|
|
|
98
|
+
@receiver(post_save, sender=Zone)
|
|
99
|
+
@receiver(post_save, sender=Category)
|
|
100
|
+
def post_save_actions_dispatcher(sender, instance, created, **kwargs):
|
|
101
|
+
from simo.users.middleware import get_current_user
|
|
102
|
+
actor = get_current_user()
|
|
103
|
+
if created:
|
|
104
|
+
verb = 'created'
|
|
105
|
+
else:
|
|
106
|
+
verb = 'modified'
|
|
107
|
+
action.send(
|
|
108
|
+
actor, target=instance, verb=verb,
|
|
109
|
+
instance_id=instance.instance.id,
|
|
110
|
+
action_type='management_event'
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
89
114
|
@receiver(post_save, sender=Component)
|
|
90
115
|
@receiver(post_save, sender=Gateway)
|
|
91
116
|
def post_save_change_events(sender, instance, created, **kwargs):
|
|
@@ -363,3 +363,17 @@ body .submit-row a.deletelink{
|
|
|
363
363
|
}
|
|
364
364
|
}
|
|
365
365
|
|
|
366
|
+
.markdownified-info{
|
|
367
|
+
padding: 15px;
|
|
368
|
+
background-color: #cfefff;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
form .aligned .markdownified-info ul{
|
|
372
|
+
margin-left: 30px;
|
|
373
|
+
}
|
|
374
|
+
form .aligned .markdownified-info ul li {
|
|
375
|
+
list-style-type: square;
|
|
376
|
+
}
|
|
377
|
+
.markdownified-info hr {
|
|
378
|
+
background-color: #9f9f9f;
|
|
379
|
+
}
|
simo/core/tasks.py
CHANGED
|
@@ -2,20 +2,23 @@ import time
|
|
|
2
2
|
import os
|
|
3
3
|
import io
|
|
4
4
|
import json
|
|
5
|
-
import base64
|
|
6
5
|
import datetime
|
|
7
6
|
import requests
|
|
8
7
|
import subprocess
|
|
9
8
|
import threading
|
|
10
9
|
import pkg_resources
|
|
10
|
+
import sys
|
|
11
|
+
import traceback
|
|
11
12
|
from django.db.models import Q
|
|
12
|
-
from django.db import connection
|
|
13
|
+
from django.db import connection, transaction
|
|
13
14
|
from django.template.loader import render_to_string
|
|
14
15
|
from celeryc import celery_app
|
|
15
16
|
from django.utils import timezone
|
|
17
|
+
from actstream.models import Action
|
|
16
18
|
from easy_thumbnails.files import get_thumbnailer
|
|
17
19
|
from simo.conf import dynamic_settings
|
|
18
20
|
from simo.core.utils.helpers import get_self_ip
|
|
21
|
+
from simo.users.models import PermissionsRole, InstanceUser
|
|
19
22
|
from .models import Instance, Component, ComponentHistory, HistoryAggregate
|
|
20
23
|
|
|
21
24
|
|
|
@@ -99,19 +102,15 @@ def save_config(data):
|
|
|
99
102
|
def sync_with_remote():
|
|
100
103
|
from simo.users.models import User
|
|
101
104
|
|
|
102
|
-
instances = Instance.objects.all()
|
|
103
|
-
if not instances:
|
|
104
|
-
# No initial configuration yet
|
|
105
|
-
return
|
|
106
|
-
|
|
107
105
|
report_data = {
|
|
108
106
|
'simo_version': pkg_resources.get_distribution('simo').version,
|
|
109
107
|
'local_http': 'https://%s' % get_self_ip(),
|
|
110
108
|
'hub_uid': dynamic_settings['core__hub_uid'],
|
|
111
109
|
'hub_secret': dynamic_settings['core__hub_secret'],
|
|
110
|
+
'remote_conn_version': dynamic_settings['core__remote_conn_version'],
|
|
112
111
|
'instances': []
|
|
113
112
|
}
|
|
114
|
-
for instance in
|
|
113
|
+
for instance in Instance.objects.all():
|
|
115
114
|
instance_data = {
|
|
116
115
|
'uid': instance.uid,
|
|
117
116
|
'name': instance.name,
|
|
@@ -131,10 +130,14 @@ def sync_with_remote():
|
|
|
131
130
|
user_role = user.get_role(instance)
|
|
132
131
|
if user_role and user_role.is_superuser:
|
|
133
132
|
is_superuser = True
|
|
133
|
+
is_owner = False
|
|
134
|
+
if user_role and user_role.is_owner:
|
|
135
|
+
is_owner = True
|
|
134
136
|
instance_data['users'].append({
|
|
135
137
|
'email': user.email,
|
|
136
138
|
'is_hub_master': user.is_master,
|
|
137
139
|
'is_superuser': is_superuser,
|
|
140
|
+
'is_owner': is_owner,
|
|
138
141
|
'device_token': user.primary_device_token
|
|
139
142
|
})
|
|
140
143
|
|
|
@@ -144,15 +147,6 @@ def sync_with_remote():
|
|
|
144
147
|
if last_event:
|
|
145
148
|
instance_data['last_event'] = last_event.date.timestamp()
|
|
146
149
|
|
|
147
|
-
if instance.cover_image and not instance.cover_image_synced:
|
|
148
|
-
thumbnailer = get_thumbnailer(instance.cover_image.path)
|
|
149
|
-
cover_imb_path = thumbnailer.get_thumbnail(
|
|
150
|
-
{'size': (880, 490), 'crop': True}
|
|
151
|
-
).path
|
|
152
|
-
with open(cover_imb_path, 'rb') as img:
|
|
153
|
-
instance_data['cover_image'] = base64.b64encode(
|
|
154
|
-
img.read()
|
|
155
|
-
).decode()
|
|
156
150
|
report_data['instances'].append(instance_data)
|
|
157
151
|
|
|
158
152
|
print("Sync UP with remote: ", json.dumps(report_data))
|
|
@@ -169,11 +163,7 @@ def sync_with_remote():
|
|
|
169
163
|
if 'hub_uid' in r_json:
|
|
170
164
|
dynamic_settings['core__hub_uid'] = r_json['hub_uid']
|
|
171
165
|
|
|
172
|
-
|
|
173
|
-
instance.cover_image_synced = True
|
|
174
|
-
instance.save()
|
|
175
|
-
|
|
176
|
-
dynamic_settings['core__remote_http'] = r_json.get('hub_remote_http')
|
|
166
|
+
dynamic_settings['core__remote_http'] = r_json.get('hub_remote_http', '')
|
|
177
167
|
if 'new_secret' in r_json:
|
|
178
168
|
dynamic_settings['core__hub_secret'] = r_json['new_secret']
|
|
179
169
|
|
|
@@ -182,9 +172,14 @@ def sync_with_remote():
|
|
|
182
172
|
dynamic_settings['core__remote_conn_version'] = r_json['remote_conn_version']
|
|
183
173
|
|
|
184
174
|
for data in r_json['instances']:
|
|
185
|
-
|
|
175
|
+
users_data = data.pop('users', {})
|
|
176
|
+
instance_uid = data.pop('uid')
|
|
177
|
+
weather_forecast = data.pop('weather_forecast', None)
|
|
178
|
+
instance, new_instance = Instance.objects.update_or_create(
|
|
179
|
+
uid=instance_uid, defaults=data
|
|
180
|
+
)
|
|
186
181
|
|
|
187
|
-
if
|
|
182
|
+
if weather_forecast:
|
|
188
183
|
from simo.generic.controllers import WeatherForecast
|
|
189
184
|
weather_component = Component.objects.filter(
|
|
190
185
|
zone__instance=instance,
|
|
@@ -192,27 +187,55 @@ def sync_with_remote():
|
|
|
192
187
|
).first()
|
|
193
188
|
if weather_component:
|
|
194
189
|
weather_component.track_history = False
|
|
195
|
-
weather_component.controller.set(
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
190
|
+
weather_component.controller.set(weather_forecast)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
for email, options in users_data.items():
|
|
194
|
+
with transaction.atomic():
|
|
195
|
+
if new_instance:
|
|
196
|
+
# Create users for new instance!
|
|
197
|
+
user, new_user = User.objects.update_or_create(
|
|
198
|
+
email=email, defaults={
|
|
199
|
+
'name': options.get('name'),
|
|
200
|
+
'is_master': options.get('is_hub_master', False),
|
|
201
|
+
'ssh_key': options.get('ssh_key')
|
|
202
|
+
})
|
|
203
|
+
role = None
|
|
204
|
+
if options.get('is_superuser'):
|
|
205
|
+
role = PermissionsRole.objects.filter(
|
|
206
|
+
instance=new_instance, is_superuser=True
|
|
207
|
+
).first()
|
|
208
|
+
elif options.get('is_owner'):
|
|
209
|
+
role = PermissionsRole.objects.filter(
|
|
210
|
+
instance=new_instance, is_owner=True
|
|
211
|
+
).first()
|
|
212
|
+
InstanceUser.objects.update_or_create(
|
|
213
|
+
user=user, instance=new_instance, defaults={
|
|
214
|
+
'is_active': True, 'role': role
|
|
215
|
+
}
|
|
216
|
+
)
|
|
217
|
+
else:
|
|
218
|
+
user = User.objects.filter(email=email).first()
|
|
219
|
+
|
|
220
|
+
if not user:
|
|
221
|
+
continue
|
|
222
|
+
|
|
223
|
+
if user.name != options.get('name'):
|
|
224
|
+
user.name = options['name']
|
|
225
|
+
user.save()
|
|
226
|
+
if user.ssh_key != options.get('ssh_key'):
|
|
227
|
+
user.ssh_key = options['ssh_key']
|
|
228
|
+
user.save()
|
|
229
|
+
|
|
230
|
+
avatar_url = options.get('avatar_url')
|
|
231
|
+
if avatar_url and user.avatar_url != avatar_url:
|
|
232
|
+
resp = requests.get(avatar_url)
|
|
233
|
+
user.avatar.save(
|
|
234
|
+
os.path.basename(avatar_url), io.BytesIO(resp.content)
|
|
235
|
+
)
|
|
236
|
+
user.avatar_url = avatar_url
|
|
237
|
+
user.avatar_last_change = timezone.now()
|
|
238
|
+
user.save()
|
|
216
239
|
|
|
217
240
|
|
|
218
241
|
@celery_app.task
|
|
@@ -223,7 +246,10 @@ def watch_timers():
|
|
|
223
246
|
component.meta['timer_to'] = 0
|
|
224
247
|
component.meta['timer_start'] = 0
|
|
225
248
|
component.save()
|
|
226
|
-
|
|
249
|
+
try:
|
|
250
|
+
component.controller._on_timer_end()
|
|
251
|
+
except Exception as e:
|
|
252
|
+
print(traceback.format_exc(), file=sys.stderr)
|
|
227
253
|
|
|
228
254
|
|
|
229
255
|
@celery_app.task
|
|
@@ -232,8 +258,15 @@ def clear_history():
|
|
|
232
258
|
old_times = timezone.now() - datetime.timedelta(
|
|
233
259
|
days=instance.history_days
|
|
234
260
|
)
|
|
235
|
-
ComponentHistory.objects.filter(
|
|
236
|
-
|
|
261
|
+
ComponentHistory.objects.filter(
|
|
262
|
+
component__zone__instance=instance, date__lt=old_times
|
|
263
|
+
).delete()
|
|
264
|
+
HistoryAggregate.objects.filter(
|
|
265
|
+
component__zone__instance=instance, start__lt=old_times
|
|
266
|
+
).delete()
|
|
267
|
+
Action.objects.filter(
|
|
268
|
+
data__instance_id=instance.id, timestamp__lt=old_times
|
|
269
|
+
)
|
|
237
270
|
|
|
238
271
|
|
|
239
272
|
@celery_app.task
|
|
@@ -300,6 +333,7 @@ def update_latest_version_available():
|
|
|
300
333
|
return
|
|
301
334
|
latest = list(resp.json()['releases'].keys())[-1]
|
|
302
335
|
dynamic_settings['core__latest_version_available'] = latest
|
|
336
|
+
print("Got the latest version available!")
|
|
303
337
|
|
|
304
338
|
|
|
305
339
|
@celery_app.task
|
|
@@ -358,7 +392,6 @@ def low_battery_notifications():
|
|
|
358
392
|
)
|
|
359
393
|
|
|
360
394
|
|
|
361
|
-
|
|
362
395
|
@celery_app.on_after_finalize.connect
|
|
363
396
|
def setup_periodic_tasks(sender, **kwargs):
|
|
364
397
|
sender.add_periodic_task(1, watch_timers.s())
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<div class="component-controller"
|
|
2
|
+
data-ws_url="{{ obj.get_socket_url|default_if_none:"" }}">
|
|
3
|
+
{% if obj.is_down %}
|
|
4
|
+
<i class="fas fa-rectangle-landscape" style="color: #1b6082;"></i>
|
|
5
|
+
{% else %}
|
|
6
|
+
<i class="far fa-rectangle-landscape" style="color: #a7a7a7;"></i>
|
|
7
|
+
{% endif %}
|
|
8
|
+
</div>
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
{% extends "admin/base_site.html" %}
|
|
2
|
+
{% load i18n admin_urls static admin_modify markdownify %}
|
|
3
|
+
|
|
4
|
+
{% block extrahead %}{{ block.super }}
|
|
5
|
+
<script src="{% url 'admin:jsi18n' %}"></script>
|
|
6
|
+
{{ media }}
|
|
7
|
+
{% endblock %}
|
|
8
|
+
|
|
9
|
+
{% block extrastyle %}{{ block.super }}<link rel="stylesheet" href="{% static "admin/css/forms.css" %}">{% endblock %}
|
|
10
|
+
|
|
11
|
+
{% block coltype %}colM{% endblock %}
|
|
12
|
+
|
|
13
|
+
{% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} change-form{% endblock %}
|
|
14
|
+
|
|
15
|
+
{% if not is_popup %}
|
|
16
|
+
{% block breadcrumbs %}
|
|
17
|
+
<div class="breadcrumbs">
|
|
18
|
+
<a href="{% url 'admin:index' %}">{% translate 'Home' %}</a>
|
|
19
|
+
› <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ opts.app_config.verbose_name }}</a>
|
|
20
|
+
› {% if has_view_permission %}<a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>{% else %}{{ opts.verbose_name_plural|capfirst }}{% endif %}
|
|
21
|
+
› {% if add %}{% blocktranslate with name=opts.verbose_name %}Add {{ name }}{% endblocktranslate %}{% else %}{{ original|truncatewords:"18" }}{% endif %}
|
|
22
|
+
</div>
|
|
23
|
+
{% endblock %}
|
|
24
|
+
{% endif %}
|
|
25
|
+
|
|
26
|
+
{% block content %}<div id="content-main">
|
|
27
|
+
{% block object-tools %}
|
|
28
|
+
{% if change and not is_popup %}
|
|
29
|
+
<ul class="object-tools">
|
|
30
|
+
{% block object-tools-items %}
|
|
31
|
+
{% change_form_object_tools %}
|
|
32
|
+
{% endblock %}
|
|
33
|
+
</ul>
|
|
34
|
+
{% endif %}
|
|
35
|
+
{% endblock %}
|
|
36
|
+
<form {% if has_file_field %}enctype="multipart/form-data" {% endif %}{% if form_url %}action="{{ form_url }}" {% endif %}method="post" id="{{ opts.model_name }}_form" novalidate>{% csrf_token %}{% block form_top %}{% endblock %}
|
|
37
|
+
<div>
|
|
38
|
+
{% if is_popup %}<input type="hidden" name="{{ is_popup_var }}" value="1">{% endif %}
|
|
39
|
+
{% if to_field %}<input type="hidden" name="{{ to_field_var }}" value="{{ to_field }}">{% endif %}
|
|
40
|
+
{% if save_on_top %}{% block submit_buttons_top %}{% submit_row %}{% endblock %}{% endif %}
|
|
41
|
+
{% if errors %}
|
|
42
|
+
<p class="errornote">
|
|
43
|
+
{% blocktranslate count counter=errors|length %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktranslate %}
|
|
44
|
+
</p>
|
|
45
|
+
{{ adminform.form.non_field_errors }}
|
|
46
|
+
{% endif %}
|
|
47
|
+
|
|
48
|
+
{% block field_sets %}
|
|
49
|
+
{% for fieldset in adminform %}
|
|
50
|
+
{% if forloop.first %}
|
|
51
|
+
<div style="display: flex">
|
|
52
|
+
<div style="flex-basis: 70%; margin-right: 15px;">
|
|
53
|
+
{% include "admin/includes/fieldset.html" %}
|
|
54
|
+
</div>
|
|
55
|
+
<div class="module" style="flex-basis: 30%; margin-left: 15px;">
|
|
56
|
+
<h2 style="background: linear-gradient(0.3turn, #5c7ca5, #5a95df);">Info</h2>
|
|
57
|
+
<div class="form-row">
|
|
58
|
+
{% if original.controller %}
|
|
59
|
+
{{ original.info|markdownify }}
|
|
60
|
+
{% endif %}
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
{% else %}
|
|
65
|
+
|
|
66
|
+
{% endif %}
|
|
67
|
+
{% endfor %}
|
|
68
|
+
{% endblock %}
|
|
69
|
+
|
|
70
|
+
{% block after_field_sets %}{% endblock %}
|
|
71
|
+
|
|
72
|
+
{% block inline_field_sets %}
|
|
73
|
+
{% for inline_admin_formset in inline_admin_formsets %}
|
|
74
|
+
{% include inline_admin_formset.opts.template %}
|
|
75
|
+
{% endfor %}
|
|
76
|
+
{% endblock %}
|
|
77
|
+
|
|
78
|
+
{% block after_related_objects %}{% endblock %}
|
|
79
|
+
|
|
80
|
+
{% block submit_buttons_bottom %}{% submit_row %}{% endblock %}
|
|
81
|
+
|
|
82
|
+
{% block admin_change_form_document_ready %}
|
|
83
|
+
<script id="django-admin-form-add-constants"
|
|
84
|
+
src="{% static 'admin/js/change_form.js' %}"
|
|
85
|
+
{% if adminform and add %}
|
|
86
|
+
data-model-name="{{ opts.model_name }}"
|
|
87
|
+
{% endif %}
|
|
88
|
+
async>
|
|
89
|
+
</script>
|
|
90
|
+
{% endblock %}
|
|
91
|
+
|
|
92
|
+
{# JavaScript for prepopulated fields #}
|
|
93
|
+
{% prepopulated_fields_js %}
|
|
94
|
+
|
|
95
|
+
</div>
|
|
96
|
+
</form></div>
|
|
97
|
+
{% endblock %}
|
|
@@ -1,122 +1,92 @@
|
|
|
1
1
|
{% load i18n admin_urls static admin_modify %}
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
2
|
+
<div class="js-inline-admin-formset inline-group" id="{{ formset.prefix }}-group"
|
|
3
|
+
data-inline-type="tabular"
|
|
4
|
+
data-inline-formset="{{ inline_formset_data }}"
|
|
5
|
+
style="width: 100%">
|
|
6
|
+
<div class="tabular inline-related {% if forloop.last %}last-related{% endif %}">
|
|
7
|
+
{{ formset.management_form }}
|
|
8
|
+
<fieldset class="module sortable">
|
|
9
|
+
{{ formset.non_form_errors }}
|
|
10
|
+
<table>
|
|
11
|
+
<thead><tr>
|
|
12
|
+
<th class="original"></th>
|
|
13
|
+
{% for field in empty_form %}
|
|
14
|
+
{% if not field.is_hidden and not field.name == 'DELETE' %}
|
|
15
|
+
<th class="column-{{ field.name }}{% if field.required %} required{% endif %}{% if field.widget.is_hidden %} hidden{% endif %}">
|
|
16
|
+
{{ field.label|capfirst }}
|
|
17
|
+
{% if field.help_text %}
|
|
18
|
+
<img src="{% static "admin/img/icon-unknown.svg" %}" class="help help-tooltip" width="10" height="10"
|
|
19
|
+
alt="({{ field.help_text|striptags }})" title="{{ field.help_text|striptags }}">
|
|
20
|
+
{% endif %}
|
|
21
|
+
</th>
|
|
22
|
+
{% endif %}
|
|
23
|
+
{% endfor %}
|
|
24
|
+
{% if formset.can_delete %}<th>{% translate "Delete?" %}</th>{% endif %}
|
|
25
|
+
</tr></thead>
|
|
26
|
+
|
|
27
|
+
<tbody>
|
|
28
|
+
{% for form in formset %}
|
|
29
|
+
{% if form.non_field_errors %}
|
|
30
|
+
<tr class="row-form-errors"><td colspan="{{ inline_admin_form|cell_count }}">{{ form.non_field_errors }}</td></tr>
|
|
31
|
+
{% endif %}
|
|
32
|
+
<tr class="form-row has_original" id="{{ formset.prefix }}-{{ forloop.counter0 }}">
|
|
33
|
+
|
|
34
|
+
<td class="original">
|
|
35
|
+
<div style="display:none">
|
|
36
|
+
{% for field in form %}
|
|
37
|
+
{% if field.is_hidden %} {{ field }} {% endif %}
|
|
38
|
+
{% endfor %}
|
|
39
|
+
</div>
|
|
40
|
+
<p>
|
|
41
|
+
<span class="sort"><i class="move-begin" role="button" {% translate "Move to first position" as move_begin %}aria-label="{{ move_begin }}" title="{{ move_begin }}"></i><i class="move-end" role="button" {% translate "Move to last position" as move_end %}aria-label="{{ move_end }}" title="{{ move_end }}"></i></span>
|
|
42
|
+
</p>
|
|
43
|
+
</td>
|
|
44
|
+
|
|
45
|
+
{% for field in form %}
|
|
46
|
+
{% if not field.is_hidden and not field.name == 'DELETE' %}
|
|
47
|
+
<td{% if field.name %} class="field-{{ field.name }}"{% endif %}>
|
|
48
|
+
{{ field }}
|
|
49
|
+
{{ field.errors.as_ul }}
|
|
50
|
+
</td>
|
|
51
|
+
{% endif %}
|
|
52
|
+
{% endfor %}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
{% if formset.can_delete %}
|
|
56
|
+
<td class="delete">{% if total_org_forms >= forloop.counter0 %}{{ form.DELETE }}{% endif %}</td>
|
|
57
|
+
{% endif %}
|
|
58
|
+
</tr>
|
|
59
|
+
{% endfor %}
|
|
60
|
+
|
|
61
|
+
<tr class="form-row has_original empty-form" id="{{ formset.prefix }}-empty">
|
|
37
62
|
|
|
38
|
-
|
|
39
|
-
<div
|
|
40
|
-
|
|
41
|
-
{
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
{% for field in form %}
|
|
67
|
-
{% if field.is_hidden %} {{ field }} {% endif %}
|
|
68
|
-
{% endfor %}
|
|
69
|
-
</td>
|
|
70
|
-
|
|
71
|
-
{% for field in form %}
|
|
72
|
-
{% if not field.is_hidden and not field.name == 'DELETE' %}
|
|
73
|
-
<td{% if field.name %} class="field-{{ field.name }}"{% endif %}>
|
|
74
|
-
{{ field }}
|
|
75
|
-
{{ field.errors.as_ul }}
|
|
76
|
-
</td>
|
|
77
|
-
{% endif %}
|
|
78
|
-
{% endfor %}
|
|
79
|
-
|
|
80
|
-
{% if formset.can_delete %}
|
|
81
|
-
<td class="delete">{% if total_org_forms >= forloop.counter0 %}{{ form.DELETE }}{% endif %}</td>
|
|
82
|
-
{% endif %}
|
|
83
|
-
</tr>
|
|
84
|
-
|
|
85
|
-
{% endfor %}
|
|
86
|
-
|
|
87
|
-
<tr class="form-row empty-form" id="{{ formset.prefix }}-empty">
|
|
88
|
-
<td class="drag"> </td>
|
|
89
|
-
<td class="original hidden">
|
|
90
|
-
{% for field in empty_form %}
|
|
91
|
-
{% if field.is_hidden %} {{ field }} {% endif %}
|
|
92
|
-
{% endfor %}
|
|
93
|
-
</td>
|
|
94
|
-
|
|
95
|
-
{% for field in empty_form %}
|
|
96
|
-
{% if not field.is_hidden and not field.name == 'DELETE' %}
|
|
97
|
-
<td{% if field.name %} class="field-{{ field.name }}"{% endif %}>
|
|
98
|
-
{{ field }}
|
|
99
|
-
{{ field.errors.as_ul }}
|
|
100
|
-
</td>
|
|
101
|
-
{% endif %}
|
|
102
|
-
{% endfor %}
|
|
103
|
-
|
|
104
|
-
{% if formset.can_delete %}
|
|
105
|
-
<td class="delete"></td>
|
|
106
|
-
{% endif %}
|
|
107
|
-
</tr>
|
|
108
|
-
|
|
109
|
-
</tbody>
|
|
110
|
-
</table>
|
|
111
|
-
</fieldset>
|
|
63
|
+
<td class="original">
|
|
64
|
+
<div style="display:none">
|
|
65
|
+
{% for field in empty_form %}
|
|
66
|
+
{% if field.is_hidden %} {{ field }} {% endif %}
|
|
67
|
+
{% endfor %}
|
|
68
|
+
</div>
|
|
69
|
+
<p>
|
|
70
|
+
<span class="sort"><i class="move-begin" role="button" {% translate "Move to first position" as move_begin %}aria-label="{{ move_begin }}" title="{{ move_begin }}"></i><i class="move-end" role="button" {% translate "Move to last position" as move_end %}aria-label="{{ move_end }}" title="{{ move_end }}"></i></span>
|
|
71
|
+
</p>
|
|
72
|
+
</td>
|
|
73
|
+
|
|
74
|
+
{% for field in empty_form %}
|
|
75
|
+
{% if not field.is_hidden and not field.name == 'DELETE' %}
|
|
76
|
+
<td{% if field.name %} class="field-{{ field.name }}"{% endif %}>
|
|
77
|
+
{{ field }}
|
|
78
|
+
{{ field.errors.as_ul }}
|
|
79
|
+
</td>
|
|
80
|
+
{% endif %}
|
|
81
|
+
{% endfor %}
|
|
82
|
+
|
|
83
|
+
{% if formset.can_delete %}
|
|
84
|
+
<td class="delete"></td>
|
|
85
|
+
{% endif %}
|
|
86
|
+
</tr>
|
|
87
|
+
|
|
88
|
+
</tbody>
|
|
89
|
+
</table>
|
|
90
|
+
</fieldset>
|
|
112
91
|
</div>
|
|
113
92
|
</div>
|
|
114
|
-
|
|
115
|
-
<script type="application/json" class="inline-tabular-config">
|
|
116
|
-
{
|
|
117
|
-
"prefix": "{{ formset.prefix|escapejs }}",
|
|
118
|
-
"addText": "{% filter escapejs %}Add another{% endfilter %}",
|
|
119
|
-
"deleteText": "{% filter escapejs %}{% trans 'Remove' %}{% endfilter %}"
|
|
120
|
-
}
|
|
121
|
-
</script>
|
|
122
|
-
<div class="default_order_field" default_order_field="ORDER" default_order_direction=""></div>
|