simo 2.0.41__py3-none-any.whl → 2.1.0__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__/on_http_start.cpython-38.pyc +0 -0
- simo/__pycache__/settings.cpython-38.pyc +0 -0
- simo/__pycache__/wsgi.cpython-38.pyc +0 -0
- 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__/base_types.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__/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/admin.py +28 -10
- simo/core/api.py +24 -5
- simo/core/api_meta.py +23 -13
- simo/core/app_widgets.py +6 -0
- simo/core/apps.py +10 -0
- simo/core/base_types.py +1 -0
- simo/core/controllers.py +57 -0
- simo/core/form_fields.py +93 -0
- simo/core/forms.py +15 -3
- 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/__pycache__/0037_auto_20240606_1057.cpython-38.pyc +0 -0
- simo/core/models.py +28 -9
- 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/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/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/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 +193 -30
- 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/__pycache__/0036_auto_20240605_0702.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 +59 -14
- simo/generic/forms.py +4 -3
- simo/generic/gateways.py +2 -0
- simo/generic/templates/admin/controller_widgets/blinds.html +2 -1
- simo/generic/templates/generic/controllers_info/dummy.md +3 -0
- simo/generic/templates/generic/controllers_info/stateselect.md +2 -0
- 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.41.dist-info → simo-2.1.0.dist-info}/METADATA +5 -3
- {simo-2.0.41.dist-info → simo-2.1.0.dist-info}/RECORD +93 -74
- simo/wsgi.py +0 -7
- {simo-2.0.41.dist-info → simo-2.1.0.dist-info}/LICENSE.md +0 -0
- {simo-2.0.41.dist-info → simo-2.1.0.dist-info}/WHEEL +0 -0
- {simo-2.0.41.dist-info → simo-2.1.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Generated by Django 4.2.10 on 2024-06-06 10:57
|
|
2
|
+
|
|
3
|
+
from django.db import migrations
|
|
4
|
+
|
|
5
|
+
def forwards_func(apps, schema_editor):
|
|
6
|
+
|
|
7
|
+
Component = apps.get_model("core", "Component")
|
|
8
|
+
Gateway = apps.get_model('core', "Gateway")
|
|
9
|
+
|
|
10
|
+
generic_gateway = Gateway.objects.filter(
|
|
11
|
+
type='simo.generic.gateways.GenericGatewayHandler'
|
|
12
|
+
).first()
|
|
13
|
+
if not generic_gateway:
|
|
14
|
+
return
|
|
15
|
+
|
|
16
|
+
Component.objects.filter(
|
|
17
|
+
controller_uid='simo.generic.controllers.StateSelect'
|
|
18
|
+
).update(gateway=generic_gateway)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def reverse_func(apps, schema_editor):
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Migration(migrations.Migration):
|
|
26
|
+
|
|
27
|
+
dependencies = [
|
|
28
|
+
('core', '0036_auto_20240521_0823'),
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
operations = [
|
|
32
|
+
migrations.RunPython(forwards_func, reverse_func, elidable=True),
|
|
33
|
+
]
|
|
Binary file
|
simo/core/models.py
CHANGED
|
@@ -12,6 +12,7 @@ from timezone_utils.choices import ALL_TIMEZONES_CHOICES
|
|
|
12
12
|
from location_field.models.plain import PlainLocationField
|
|
13
13
|
from model_utils import FieldTracker
|
|
14
14
|
from dirtyfields import DirtyFieldsMixin
|
|
15
|
+
from actstream import action
|
|
15
16
|
from simo.core.utils.mixins import SimoAdminMixin
|
|
16
17
|
from simo.core.storage import OverwriteStorage
|
|
17
18
|
from simo.core.utils.validators import validate_svg
|
|
@@ -399,6 +400,8 @@ def is_in_alarm(self):
|
|
|
399
400
|
|
|
400
401
|
controller_cls = None
|
|
401
402
|
|
|
403
|
+
_controller_initiated = False
|
|
404
|
+
|
|
402
405
|
_mqtt_client = None
|
|
403
406
|
_on_change_function = None
|
|
404
407
|
_obj_ct_id = 0
|
|
@@ -408,15 +411,21 @@ def is_in_alarm(self):
|
|
|
408
411
|
verbose_name_plural = _("Components")
|
|
409
412
|
ordering = 'zone', 'base_type', 'name'
|
|
410
413
|
|
|
411
|
-
def __init__(self, *args, **kwargs):
|
|
412
|
-
super().__init__(*args, **kwargs)
|
|
413
|
-
self.prepare_controller()
|
|
414
|
-
|
|
415
414
|
def __str__(self):
|
|
416
415
|
if self.zone:
|
|
417
416
|
return '%s | %s' % (self.zone.name, self.name)
|
|
418
417
|
return self.name
|
|
419
418
|
|
|
419
|
+
def __getattribute__(self, attr):
|
|
420
|
+
try:
|
|
421
|
+
return super().__getattribute__(attr)
|
|
422
|
+
except Exception as e:
|
|
423
|
+
if not attr.startswith('_') and not self._controller_initiated:
|
|
424
|
+
self._controller_initiated = True
|
|
425
|
+
self.prepare_controller()
|
|
426
|
+
return super().__getattribute__(attr)
|
|
427
|
+
raise e
|
|
428
|
+
|
|
420
429
|
@cached_property
|
|
421
430
|
def controller(self):
|
|
422
431
|
from .utils.type_constants import (
|
|
@@ -425,13 +434,13 @@ def is_in_alarm(self):
|
|
|
425
434
|
)
|
|
426
435
|
self._meta.get_field('controller_uid').choices = CONTROLLER_TYPES_CHOICES
|
|
427
436
|
if self.controller_uid:
|
|
428
|
-
controller_cls = None
|
|
429
|
-
if not controller_cls:
|
|
430
|
-
controller_cls = CONTROLLERS_BY_GATEWAY.get(
|
|
437
|
+
self.controller_cls = None
|
|
438
|
+
if not self.controller_cls:
|
|
439
|
+
self.controller_cls = CONTROLLERS_BY_GATEWAY.get(
|
|
431
440
|
self.gateway.type, {}
|
|
432
441
|
).get(self.controller_uid)
|
|
433
|
-
if controller_cls:
|
|
434
|
-
return controller_cls(self)
|
|
442
|
+
if self.controller_cls:
|
|
443
|
+
return self.controller_cls(self)
|
|
435
444
|
|
|
436
445
|
def prepare_controller(self):
|
|
437
446
|
if self.controller:
|
|
@@ -483,6 +492,11 @@ def is_in_alarm(self):
|
|
|
483
492
|
component=self, type='value', value=self.value,
|
|
484
493
|
user=actor
|
|
485
494
|
)
|
|
495
|
+
action.send(
|
|
496
|
+
actor, target=self, verb="value change",
|
|
497
|
+
instance_id=self.zone.instance.id,
|
|
498
|
+
action_type='comp_value', value=self.value
|
|
499
|
+
)
|
|
486
500
|
action_performed = True
|
|
487
501
|
self.last_change = timezone.now()
|
|
488
502
|
if 'arm_status' in dirty_fields:
|
|
@@ -490,6 +504,11 @@ def is_in_alarm(self):
|
|
|
490
504
|
component=self, type='security',
|
|
491
505
|
value=self.arm_status, user=actor
|
|
492
506
|
)
|
|
507
|
+
action.send(
|
|
508
|
+
actor, target=self, verb="security event",
|
|
509
|
+
instance_id=self.zone.instance.id,
|
|
510
|
+
action_type='security', value=self.value
|
|
511
|
+
)
|
|
493
512
|
action_performed = True
|
|
494
513
|
self.last_change = timezone.now()
|
|
495
514
|
if action_performed:
|
simo/core/permissions.py
CHANGED
|
@@ -10,9 +10,12 @@ class InstancePermission(BasePermission):
|
|
|
10
10
|
if not request.user.is_active:
|
|
11
11
|
return False
|
|
12
12
|
|
|
13
|
-
instance =
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
instance = getattr(view, 'instance', None)
|
|
14
|
+
if not instance:
|
|
15
|
+
instance = Instance.objects.filter(
|
|
16
|
+
slug=request.resolver_match.kwargs.get('instance_slug')
|
|
17
|
+
).first()
|
|
18
|
+
|
|
16
19
|
if not instance:
|
|
17
20
|
raise Http404()
|
|
18
21
|
|
simo/core/serializers.py
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import inspect
|
|
2
2
|
import datetime
|
|
3
|
-
import
|
|
3
|
+
import re
|
|
4
4
|
from django import forms
|
|
5
5
|
from collections import OrderedDict
|
|
6
|
-
from django.forms.utils import ErrorDict
|
|
7
6
|
from django.conf import settings
|
|
8
7
|
from collections.abc import Iterable
|
|
9
8
|
from easy_thumbnails.files import get_thumbnailer
|
|
@@ -11,7 +10,13 @@ from simo.core.middleware import get_current_request
|
|
|
11
10
|
from rest_framework import serializers
|
|
12
11
|
from rest_framework.fields import SkipField
|
|
13
12
|
from rest_framework.relations import Hyperlink, PKOnlyObject
|
|
13
|
+
from actstream.models import Action
|
|
14
14
|
from simo.core.forms import HiddenField, FormsetField
|
|
15
|
+
from simo.core.form_fields import (
|
|
16
|
+
Select2ListChoiceField, Select2ListChoiceField,
|
|
17
|
+
Select2ModelChoiceField, Select2ListMultipleChoiceField
|
|
18
|
+
)
|
|
19
|
+
from simo.core.models import Component
|
|
15
20
|
from rest_framework.relations import PrimaryKeyRelatedField, ManyRelatedField
|
|
16
21
|
from .drf_braces.serializers.form_serializer import (
|
|
17
22
|
FormSerializer, FormSerializerBase, reduce_attr_dict_from_instance,
|
|
@@ -106,6 +111,7 @@ class ComponentFormsetField(FormSerializer):
|
|
|
106
111
|
form = forms.Form
|
|
107
112
|
field_mapping = {
|
|
108
113
|
HiddenField: HiddenSerializerField,
|
|
114
|
+
Select2ListChoiceField: serializers.ChoiceField,
|
|
109
115
|
forms.ModelChoiceField: FormsetPrimaryKeyRelatedField,
|
|
110
116
|
forms.TypedChoiceField: serializers.ChoiceField,
|
|
111
117
|
forms.FloatField: serializers.FloatField,
|
|
@@ -249,6 +255,9 @@ class ComponentSerializer(FormSerializer):
|
|
|
249
255
|
arm_status = ObjectSerializerMethodField()
|
|
250
256
|
battery_level = ObjectSerializerMethodField()
|
|
251
257
|
controller_methods = serializers.SerializerMethodField()
|
|
258
|
+
info = serializers.SerializerMethodField()
|
|
259
|
+
|
|
260
|
+
_forms = {}
|
|
252
261
|
|
|
253
262
|
class Meta:
|
|
254
263
|
form = ComponentAdminForm
|
|
@@ -258,11 +267,27 @@ class ComponentSerializer(FormSerializer):
|
|
|
258
267
|
forms.FloatField: serializers.FloatField,
|
|
259
268
|
forms.SlugField: serializers.CharField,
|
|
260
269
|
forms.ModelChoiceField: ComponentPrimaryKeyRelatedField,
|
|
270
|
+
Select2ModelChoiceField: ComponentPrimaryKeyRelatedField,
|
|
261
271
|
forms.ModelMultipleChoiceField: ComponentManyToManyRelatedField,
|
|
272
|
+
Select2ListMultipleChoiceField: ComponentManyToManyRelatedField,
|
|
262
273
|
FormsetField: ComponentFormsetField,
|
|
263
274
|
}
|
|
264
275
|
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def __init__(self, *args, **kwargs):
|
|
279
|
+
super().__init__(*args, **kwargs)
|
|
280
|
+
# Set proper instance for OPTIONS request
|
|
281
|
+
if not self.instance:
|
|
282
|
+
res = re.findall(
|
|
283
|
+
r'.*\/core\/components\/(?P<component_id>[0-9]+)\/',
|
|
284
|
+
self.context['request'].path
|
|
285
|
+
)
|
|
286
|
+
if res:
|
|
287
|
+
self.instance = Component.objects.filter(id=res[0]).first()
|
|
288
|
+
|
|
265
289
|
def get_fields(self):
|
|
290
|
+
|
|
266
291
|
self.set_form_cls()
|
|
267
292
|
|
|
268
293
|
ret = OrderedDict()
|
|
@@ -322,6 +347,7 @@ class ComponentSerializer(FormSerializer):
|
|
|
322
347
|
if name in ret:
|
|
323
348
|
continue
|
|
324
349
|
ret[name] = field
|
|
350
|
+
|
|
325
351
|
return ret
|
|
326
352
|
|
|
327
353
|
def _get_field_kwargs(self, form_field, serializer_field_class):
|
|
@@ -359,7 +385,15 @@ class ComponentSerializer(FormSerializer):
|
|
|
359
385
|
if controller:
|
|
360
386
|
self.Meta.form = controller.config_form
|
|
361
387
|
|
|
362
|
-
def get_form(self, data=None, **kwargs):
|
|
388
|
+
def get_form(self, data=None, instance=None, **kwargs):
|
|
389
|
+
form_key = None
|
|
390
|
+
if not data:
|
|
391
|
+
form_key = 0
|
|
392
|
+
if instance:
|
|
393
|
+
form_key = instance.id
|
|
394
|
+
if form_key in self._forms:
|
|
395
|
+
return self._forms[form_key]
|
|
396
|
+
|
|
363
397
|
self.set_form_cls()
|
|
364
398
|
if not self.instance or isinstance(self.instance, Iterable):
|
|
365
399
|
#controller_uid = 'simo.generic.controllers.AlarmClock'
|
|
@@ -368,7 +402,7 @@ class ComponentSerializer(FormSerializer):
|
|
|
368
402
|
controller_uid = self.instance.controller_uid
|
|
369
403
|
form = self.Meta.form(
|
|
370
404
|
data=data, request=self.context['request'],
|
|
371
|
-
controller_uid=controller_uid,
|
|
405
|
+
controller_uid=controller_uid, instance=instance,
|
|
372
406
|
**kwargs
|
|
373
407
|
)
|
|
374
408
|
if not self.context['request'].user.is_master:
|
|
@@ -380,6 +414,10 @@ class ComponentSerializer(FormSerializer):
|
|
|
380
414
|
if field_name not in form.basic_fields:
|
|
381
415
|
print("DELETE FIELD: ", field_name)
|
|
382
416
|
del form.fields[field_name]
|
|
417
|
+
|
|
418
|
+
if form_key is not None:
|
|
419
|
+
self._forms[form_key] = form
|
|
420
|
+
|
|
383
421
|
return form
|
|
384
422
|
|
|
385
423
|
def accomodate_formsets(self, form, data):
|
|
@@ -426,7 +464,6 @@ class ComponentSerializer(FormSerializer):
|
|
|
426
464
|
a_data = self.accomodate_formsets(form, validated_data)
|
|
427
465
|
form = self.get_form(instance=instance, data=a_data)
|
|
428
466
|
if form.is_valid():
|
|
429
|
-
print("FORM FIELDS", form.fields)
|
|
430
467
|
instance = form.save(commit=True)
|
|
431
468
|
return instance
|
|
432
469
|
raise serializers.ValidationError(form.errors)
|
|
@@ -450,6 +487,10 @@ class ComponentSerializer(FormSerializer):
|
|
|
450
487
|
c_methods.extend(['arm', 'disarm'])
|
|
451
488
|
return c_methods
|
|
452
489
|
|
|
490
|
+
def get_info(self, obj):
|
|
491
|
+
if obj.controller:
|
|
492
|
+
return obj.controller.info()
|
|
493
|
+
|
|
453
494
|
def get_read_only(self, obj):
|
|
454
495
|
user = self.context.get('user')
|
|
455
496
|
if not user:
|
|
@@ -507,3 +548,34 @@ class ComponentHistorySerializer(serializers.ModelSerializer):
|
|
|
507
548
|
class Meta:
|
|
508
549
|
model = ComponentHistory
|
|
509
550
|
fields = '__all__'
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
class ActionSerializer(serializers.ModelSerializer):
|
|
554
|
+
timestamp = TimestampField()
|
|
555
|
+
actor = serializers.SerializerMethodField()
|
|
556
|
+
target = serializers.SerializerMethodField()
|
|
557
|
+
action_type = serializers.SerializerMethodField()
|
|
558
|
+
value = serializers.SerializerMethodField()
|
|
559
|
+
|
|
560
|
+
class Meta:
|
|
561
|
+
model = Action
|
|
562
|
+
fields = (
|
|
563
|
+
'id', 'timestamp', 'actor', 'target', 'verb',
|
|
564
|
+
'action_type', 'value'
|
|
565
|
+
)
|
|
566
|
+
|
|
567
|
+
def get_actor(self, obj):
|
|
568
|
+
if obj.actor:
|
|
569
|
+
return str(obj.actor)
|
|
570
|
+
|
|
571
|
+
def get_target(self, obj):
|
|
572
|
+
if obj.target:
|
|
573
|
+
return str(obj.target)
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
def get_action_type(self, obj):
|
|
577
|
+
return obj.data.get('action_type')
|
|
578
|
+
|
|
579
|
+
def get_value(self, obj):
|
|
580
|
+
return obj.data.get('value')
|
|
581
|
+
|
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
|
+
}
|
|
@@ -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 %}
|