simo 1.7.20__py3-none-any.whl → 2.0.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__/settings.cpython-38.pyc +0 -0
- simo/__pycache__/urls.cpython-38.pyc +0 -0
- simo/__pycache__/wsgi.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__/auto_urls.cpython-38.pyc +0 -0
- simo/core/__pycache__/autocomplete_views.cpython-38.pyc +0 -0
- simo/core/__pycache__/base_types.cpython-38.pyc +0 -0
- simo/core/__pycache__/context.cpython-38.pyc +0 -0
- simo/core/__pycache__/controllers.cpython-38.pyc +0 -0
- simo/core/__pycache__/events.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__/middleware.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__/socket_consumers.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 +28 -18
- simo/core/api.py +157 -16
- simo/core/api_meta.py +87 -0
- simo/core/auto_urls.py +4 -1
- simo/core/autocomplete_views.py +8 -4
- simo/core/base_types.py +1 -0
- simo/core/context.py +3 -1
- simo/core/controllers.py +112 -32
- simo/core/db_backend/base.py +7 -22
- simo/core/drf_braces/README +3 -0
- simo/core/drf_braces/__init__.py +7 -0
- simo/core/drf_braces/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/core/drf_braces/__pycache__/utils.cpython-38.pyc +0 -0
- simo/core/drf_braces/fields/__init__.py +5 -0
- simo/core/drf_braces/fields/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/core/drf_braces/fields/__pycache__/_fields.cpython-38.pyc +0 -0
- simo/core/drf_braces/fields/__pycache__/custom.cpython-38.pyc +0 -0
- simo/core/drf_braces/fields/__pycache__/mixins.cpython-38.pyc +0 -0
- simo/core/drf_braces/fields/__pycache__/modified.cpython-38.pyc +0 -0
- simo/core/drf_braces/fields/_fields.py +48 -0
- simo/core/drf_braces/fields/custom.py +107 -0
- simo/core/drf_braces/fields/mixins.py +58 -0
- simo/core/drf_braces/fields/modified.py +41 -0
- simo/core/drf_braces/forms/__init__.py +0 -0
- simo/core/drf_braces/forms/fields.py +20 -0
- simo/core/drf_braces/forms/serializer_form.py +156 -0
- simo/core/drf_braces/mixins.py +52 -0
- simo/core/drf_braces/models.py +0 -0
- simo/core/drf_braces/parsers.py +72 -0
- simo/core/drf_braces/renderers.py +37 -0
- simo/core/drf_braces/serializers/__init__.py +0 -0
- simo/core/drf_braces/serializers/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/core/drf_braces/serializers/__pycache__/form_serializer.cpython-38.pyc +0 -0
- simo/core/drf_braces/serializers/enforce_validation_serializer.py +214 -0
- simo/core/drf_braces/serializers/form_serializer.py +391 -0
- simo/core/drf_braces/serializers/swapping.py +48 -0
- simo/core/drf_braces/tests/__init__.py +0 -0
- simo/core/drf_braces/tests/fields/__init__.py +0 -0
- simo/core/drf_braces/tests/fields/test_custom.py +94 -0
- simo/core/drf_braces/tests/fields/test_fields.py +13 -0
- simo/core/drf_braces/tests/fields/test_mixins.py +96 -0
- simo/core/drf_braces/tests/fields/test_modified.py +40 -0
- simo/core/drf_braces/tests/forms/__init__.py +0 -0
- simo/core/drf_braces/tests/forms/test_fields.py +46 -0
- simo/core/drf_braces/tests/forms/test_serializer_form.py +256 -0
- simo/core/drf_braces/tests/serializers/__init__.py +0 -0
- simo/core/drf_braces/tests/serializers/test_enforce_validation_serializer.py +169 -0
- simo/core/drf_braces/tests/serializers/test_form_serializer.py +387 -0
- simo/core/drf_braces/tests/serializers/test_swapping.py +40 -0
- simo/core/drf_braces/tests/test_mixins.py +111 -0
- simo/core/drf_braces/tests/test_parsers.py +73 -0
- simo/core/drf_braces/tests/test_renderers.py +23 -0
- simo/core/drf_braces/tests/test_utils.py +73 -0
- simo/core/drf_braces/utils.py +209 -0
- simo/core/events.py +3 -3
- simo/core/forms.py +79 -37
- simo/core/gateways.py +31 -14
- simo/core/management/commands/gateways_manager.py +0 -1
- simo/core/managers.py +81 -0
- simo/core/middleware.py +25 -0
- simo/core/migrations/0026_category_instance.py +20 -0
- simo/core/migrations/0027_remove_component_tags.py +17 -0
- simo/core/migrations/0028_rename_subcomponents_component_slaves.py +18 -0
- simo/core/migrations/0029_auto_20240229_1331.py +33 -0
- simo/core/migrations/__pycache__/0026_category_instance.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0027_remove_component_tags.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0028_rename_subcomponents_component_slaves.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0029_auto_20240229_1331.cpython-38.pyc +0 -0
- simo/core/models.py +103 -66
- simo/core/permissions.py +28 -2
- simo/core/serializers.py +330 -26
- simo/core/socket_consumers.py +5 -14
- simo/core/tasks.py +11 -1
- simo/core/templates/admin/base.html +37 -10
- simo/core/templates/admin/wizard/discovery.html +188 -0
- simo/core/templates/admin/wizard/wizard_add.html +5 -5
- simo/core/utils/__pycache__/serialization.cpython-38.pyc +0 -0
- simo/core/utils/admin.py +9 -2
- simo/core/utils/formsets.py +17 -16
- simo/core/utils/helpers.py +1 -0
- simo/core/utils/serialization.py +56 -0
- simo/core/utils/type_constants.py +1 -1
- simo/core/utils/validators.py +14 -1
- simo/core/views.py +13 -0
- simo/fleet/__pycache__/admin.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/api.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/auto_urls.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/controllers.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/forms.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/gateways.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/managers.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/models.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/serializers.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/admin.py +54 -25
- simo/fleet/api.py +59 -3
- simo/fleet/auto_urls.py +2 -3
- simo/fleet/controllers.py +199 -16
- simo/fleet/forms.py +325 -483
- simo/fleet/gateways.py +44 -2
- simo/fleet/managers.py +32 -0
- simo/fleet/migrations/0025_auto_20240130_1334.py +27 -0
- simo/fleet/migrations/0026_rename_i2cinterface_scl_pin_and_more.py +64 -0
- simo/fleet/migrations/0027_auto_20240306_0802.py +170 -0
- simo/fleet/migrations/0028_remove_i2cinterface_scl_pin_no_and_more.py +21 -0
- simo/fleet/migrations/0029_alter_i2cinterface_scl_pin_and_more.py +24 -0
- simo/fleet/migrations/0030_colonelpin_label_alter_colonel_type.py +24 -0
- simo/fleet/migrations/0031_alter_colonel_type.py +18 -0
- simo/fleet/migrations/__pycache__/0025_auto_20240130_1334.cpython-38.pyc +0 -0
- simo/fleet/migrations/__pycache__/0026_rename_i2cinterface_scl_pin_and_more.cpython-38.pyc +0 -0
- simo/fleet/migrations/__pycache__/0027_auto_20240306_0802.cpython-38.pyc +0 -0
- simo/fleet/migrations/__pycache__/0028_remove_i2cinterface_scl_pin_no_and_more.cpython-38.pyc +0 -0
- simo/fleet/migrations/__pycache__/0029_alter_i2cinterface_scl_pin_and_more.cpython-38.pyc +0 -0
- simo/fleet/migrations/__pycache__/0030_colonelpin_label_alter_colonel_type.cpython-38.pyc +0 -0
- simo/fleet/migrations/__pycache__/0031_alter_colonel_type.cpython-38.pyc +0 -0
- simo/fleet/models.py +134 -82
- simo/fleet/serializers.py +35 -1
- simo/fleet/socket_consumers.py +239 -76
- simo/fleet/utils.py +15 -53
- simo/fleet/views.py +28 -14
- simo/generic/controllers.py +13 -89
- simo/generic/forms.py +29 -18
- simo/generic/gateways.py +73 -2
- simo/generic/models.py +3 -3
- simo/multimedia/controllers.py +9 -8
- simo/settings.py +7 -4
- simo/urls.py +4 -8
- simo/users/__pycache__/admin.cpython-38.pyc +0 -0
- simo/users/__pycache__/api.cpython-38.pyc +0 -0
- simo/users/__pycache__/auto_urls.cpython-38.pyc +0 -0
- simo/users/__pycache__/models.cpython-38.pyc +0 -0
- simo/users/__pycache__/serializers.cpython-38.pyc +0 -0
- simo/users/__pycache__/sso_urls.cpython-38.pyc +0 -0
- simo/users/admin.py +8 -1
- simo/users/api.py +38 -2
- simo/users/auto_urls.py +2 -2
- simo/users/migrations/0025_rename_name_fingerprint_type_and_more.py +22 -0
- simo/users/migrations/__pycache__/0025_rename_name_fingerprint_type_and_more.cpython-38.pyc +0 -0
- simo/users/models.py +2 -3
- simo/users/serializers.py +15 -1
- simo/users/sso_urls.py +3 -3
- simo/wsgi.py +7 -0
- {simo-1.7.20.dist-info → simo-2.0.0.dist-info}/METADATA +8 -9
- {simo-1.7.20.dist-info → simo-2.0.0.dist-info}/RECORD +173 -189
- {simo-1.7.20.dist-info → simo-2.0.0.dist-info}/WHEEL +1 -1
- simo/core/db_backend/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/core/db_backend/__pycache__/base.cpython-38.pyc +0 -0
- simo/core/management/commands/__pycache__/gateways_manager.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0001_initial.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0002_load_icons.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0003_create_default_zones_and_categories.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0004_create_generic.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0005_component_subcomponents.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0006_alter_component_subcomponents.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0007_component_change_init_to.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0008_alter_component_change_init_to.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0009_auto_20220707_1404.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0010_historyaggregate.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0011_component_last_change.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0012_instance.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0013_auto_20231003_0754.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0014_zone_instance.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0015_auto_20231004_1113.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0016_auto_20231004_1113.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0017_auto_20231004_1313.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0018_auto_20231005_0622.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0019_alter_gateway_type.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0020_component_meta.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0021_auto_20231020_1041.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/core/templatetags/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/core/templatetags/__pycache__/components_list.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/admin.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/config_values.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/easing.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/form_fields.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/form_widgets.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/formsets.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/helpers.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/logs.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/mixins.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/model_helpers.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/relay.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/type_constants.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/validators.cpython-38.pyc +0 -0
- simo/fleet/tasks.py +0 -25
- simo/generic/__pycache__/__init__.cpython-37.pyc +0 -0
- simo/generic/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/generic/__pycache__/app_widgets.cpython-38.pyc +0 -0
- simo/generic/__pycache__/base_types.cpython-38.pyc +0 -0
- simo/generic/__pycache__/controllers.cpython-37.pyc +0 -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/__pycache__/models.cpython-38.pyc +0 -0
- simo/generic/__pycache__/routing.cpython-38.pyc +0 -0
- simo/generic/__pycache__/socket_consumers.cpython-38.pyc +0 -0
- simo/generic/__pycache__/widgets.cpython-37.pyc +0 -0
- simo/multimedia/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/multimedia/__pycache__/admin.cpython-38.pyc +0 -0
- simo/multimedia/__pycache__/api.cpython-38.pyc +0 -0
- simo/multimedia/__pycache__/app_widgets.cpython-38.pyc +0 -0
- simo/multimedia/__pycache__/base_types.cpython-38.pyc +0 -0
- simo/multimedia/__pycache__/controllers.cpython-38.pyc +0 -0
- simo/multimedia/__pycache__/forms.cpython-38.pyc +0 -0
- simo/multimedia/__pycache__/models.cpython-38.pyc +0 -0
- simo/multimedia/__pycache__/serializers.cpython-38.pyc +0 -0
- simo/multimedia/migrations/__pycache__/0001_initial.cpython-38.pyc +0 -0
- simo/multimedia/migrations/__pycache__/0002_sound_length.cpython-38.pyc +0 -0
- simo/multimedia/migrations/__pycache__/0003_alter_sound_length.cpython-38.pyc +0 -0
- simo/multimedia/migrations/__pycache__/0004_auto_20231023_1055.cpython-38.pyc +0 -0
- simo/multimedia/migrations/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/notifications/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/notifications/__pycache__/admin.cpython-38.pyc +0 -0
- simo/notifications/__pycache__/api.cpython-38.pyc +0 -0
- simo/notifications/__pycache__/models.cpython-38.pyc +0 -0
- simo/notifications/__pycache__/serializers.cpython-38.pyc +0 -0
- simo/notifications/__pycache__/utils.cpython-38.pyc +0 -0
- simo/notifications/migrations/__pycache__/0001_initial.cpython-38.pyc +0 -0
- simo/notifications/migrations/__pycache__/0002_notification_instance.cpython-38.pyc +0 -0
- simo/notifications/migrations/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0001_initial.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0002_componentpermission.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0003_create_roles_and_system_user.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0004_user_secret_key.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0005_permissionsrole_instance.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0006_auto_20231003_0850.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0007_auto_20231003_1228.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0008_auto_20231003_1229.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0009_remove_user_role.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0010_auto_20231004_1313.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0011_auto_20231004_1313.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0012_alter_userinstancerole_unique_together.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0013_remove_user_roles.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0014_user_roles.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0015_remove_user_at_home.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0016_auto_20231005_1050.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/__init__.cpython-38.pyc +0 -0
- {simo-1.7.20.dist-info → simo-2.0.0.dist-info}/LICENSE.md +0 -0
- {simo-1.7.20.dist-info → simo-2.0.0.dist-info}/top_level.txt +0 -0
simo/core/middleware.py
CHANGED
|
@@ -14,6 +14,26 @@ def get_current_request():
|
|
|
14
14
|
pass
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
def introduce_instance(instance, request=None):
|
|
18
|
+
_thread_locals.instance = instance
|
|
19
|
+
if request:
|
|
20
|
+
request.session['instance_id'] = instance.id
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_current_instance(request=None):
|
|
24
|
+
instance = getattr(_thread_locals, 'instance', None)
|
|
25
|
+
if not instance and request and request.session.get('instance_id'):
|
|
26
|
+
from simo.core.models import Instance
|
|
27
|
+
instance = Instance.objects.filter(
|
|
28
|
+
id=request.session['instance_id']
|
|
29
|
+
).first()
|
|
30
|
+
if not instance:
|
|
31
|
+
del request.session['instance_id']
|
|
32
|
+
else:
|
|
33
|
+
introduce_instance(instance, request)
|
|
34
|
+
return instance
|
|
35
|
+
|
|
36
|
+
|
|
17
37
|
def simo_router_middleware(get_response):
|
|
18
38
|
|
|
19
39
|
def middleware(request):
|
|
@@ -34,11 +54,15 @@ def instance_middleware(get_response):
|
|
|
34
54
|
from simo.core.models import Instance
|
|
35
55
|
|
|
36
56
|
instance = None
|
|
57
|
+
# API calls
|
|
37
58
|
if request.resolver_match:
|
|
38
59
|
instance = Instance.objects.filter(
|
|
39
60
|
slug=request.resolver_match.kwargs.get('instance_slug')
|
|
40
61
|
).first()
|
|
41
62
|
|
|
63
|
+
if not instance:
|
|
64
|
+
instance = get_current_instance(request)
|
|
65
|
+
|
|
42
66
|
if not instance:
|
|
43
67
|
if request.user.is_authenticated:
|
|
44
68
|
if len(request.user.instances) == 1:
|
|
@@ -46,6 +70,7 @@ def instance_middleware(get_response):
|
|
|
46
70
|
instance = inst
|
|
47
71
|
|
|
48
72
|
if instance:
|
|
73
|
+
introduce_instance(instance, request)
|
|
49
74
|
tz = pytz.timezone(instance.timezone)
|
|
50
75
|
timezone.activate(tz)
|
|
51
76
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Generated by Django 3.2.9 on 2024-01-24 11:39
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
import django.db.models.deletion
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Migration(migrations.Migration):
|
|
8
|
+
|
|
9
|
+
dependencies = [
|
|
10
|
+
('core', '0025_auto_20240122_1321'),
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
operations = [
|
|
14
|
+
migrations.AddField(
|
|
15
|
+
model_name='category',
|
|
16
|
+
name='instance',
|
|
17
|
+
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='core.instance'),
|
|
18
|
+
preserve_default=False,
|
|
19
|
+
),
|
|
20
|
+
]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Generated by Django 3.2.9 on 2024-01-25 12:04
|
|
2
|
+
|
|
3
|
+
from django.db import migrations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
dependencies = [
|
|
9
|
+
('core', '0026_category_instance'),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.RemoveField(
|
|
14
|
+
model_name='component',
|
|
15
|
+
name='tags',
|
|
16
|
+
),
|
|
17
|
+
]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Generated by Django 3.2.9 on 2024-02-07 10:14
|
|
2
|
+
|
|
3
|
+
from django.db import migrations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
dependencies = [
|
|
9
|
+
('core', '0027_remove_component_tags'),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.RenameField(
|
|
14
|
+
model_name='component',
|
|
15
|
+
old_name='subcomponents',
|
|
16
|
+
new_name='slaves',
|
|
17
|
+
),
|
|
18
|
+
]
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Generated by Django 3.2.9 on 2024-02-29 13:31
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
dependencies = [
|
|
9
|
+
('core', '0028_rename_subcomponents_component_slaves'),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.AddField(
|
|
14
|
+
model_name='gateway',
|
|
15
|
+
name='discovery',
|
|
16
|
+
field=models.JSONField(blank=True, editable=False, null=True),
|
|
17
|
+
),
|
|
18
|
+
migrations.AlterField(
|
|
19
|
+
model_name='component',
|
|
20
|
+
name='instance_methods',
|
|
21
|
+
field=models.TextField(blank=True, default='\n\ndef is_in_alarm(self):\n return bool(self.value)\n\n', help_text='Add your own component methods or override existing ones'),
|
|
22
|
+
),
|
|
23
|
+
migrations.AlterField(
|
|
24
|
+
model_name='component',
|
|
25
|
+
name='slaves',
|
|
26
|
+
field=models.ManyToManyField(blank=True, null=True, related_name='masters', to='core.Component'),
|
|
27
|
+
),
|
|
28
|
+
migrations.AlterField(
|
|
29
|
+
model_name='gateway',
|
|
30
|
+
name='type',
|
|
31
|
+
field=models.CharField(choices=[], db_index=True, max_length=200, unique=True),
|
|
32
|
+
),
|
|
33
|
+
]
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
simo/core/models.py
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import sys
|
|
3
3
|
import inspect
|
|
4
|
-
|
|
4
|
+
import time
|
|
5
|
+
from collections.abc import Iterable
|
|
5
6
|
from django.utils.text import slugify
|
|
6
7
|
from django.core.cache import cache
|
|
8
|
+
from django.utils.functional import cached_property
|
|
7
9
|
from django.urls import reverse_lazy
|
|
8
10
|
from django.utils.translation import gettext_lazy as _
|
|
9
11
|
from django.db import models
|
|
@@ -21,6 +23,7 @@ from taggit.managers import TaggableManager
|
|
|
21
23
|
from simo.core.utils.mixins import SimoAdminMixin
|
|
22
24
|
from simo.core.storage import OverwriteStorage
|
|
23
25
|
from simo.core.utils.validators import validate_svg
|
|
26
|
+
from .managers import ZonesManager, CategoriesManager, ComponentsManager
|
|
24
27
|
from .events import GatewayObjectCommand, OnChangeMixin
|
|
25
28
|
|
|
26
29
|
|
|
@@ -112,6 +115,7 @@ class Instance(DirtyFieldsMixin, models.Model, SimoAdminMixin):
|
|
|
112
115
|
User, null=True, blank=True, on_delete=models.SET_NULL
|
|
113
116
|
)
|
|
114
117
|
|
|
118
|
+
|
|
115
119
|
def __str__(self):
|
|
116
120
|
return self.name
|
|
117
121
|
|
|
@@ -130,6 +134,7 @@ class Zone(DirtyFieldsMixin, models.Model, SimoAdminMixin):
|
|
|
130
134
|
order = models.PositiveIntegerField(
|
|
131
135
|
default=0, blank=False, null=False, db_index=True
|
|
132
136
|
)
|
|
137
|
+
objects = ZonesManager()
|
|
133
138
|
|
|
134
139
|
# TODO: Admin ordering not working via remote!
|
|
135
140
|
|
|
@@ -143,6 +148,7 @@ class Zone(DirtyFieldsMixin, models.Model, SimoAdminMixin):
|
|
|
143
148
|
|
|
144
149
|
|
|
145
150
|
class Category(DirtyFieldsMixin, models.Model, SimoAdminMixin):
|
|
151
|
+
instance = models.ForeignKey(Instance, on_delete=models.CASCADE)
|
|
146
152
|
name = models.CharField(_('name'), max_length=40)
|
|
147
153
|
icon = models.ForeignKey(Icon, on_delete=models.SET_NULL, null=True)
|
|
148
154
|
header_image = ThumbnailerImageField(
|
|
@@ -159,14 +165,13 @@ class Category(DirtyFieldsMixin, models.Model, SimoAdminMixin):
|
|
|
159
165
|
order = models.PositiveIntegerField(
|
|
160
166
|
default=0, blank=False, null=False, db_index=True
|
|
161
167
|
)
|
|
162
|
-
|
|
168
|
+
objects = CategoriesManager()
|
|
163
169
|
|
|
164
170
|
class Meta:
|
|
165
171
|
verbose_name = _("category")
|
|
166
172
|
verbose_name_plural = _("categories")
|
|
167
173
|
ordering = ('order', 'id')
|
|
168
174
|
|
|
169
|
-
|
|
170
175
|
def __str__(self):
|
|
171
176
|
return self.name
|
|
172
177
|
|
|
@@ -175,7 +180,9 @@ class Category(DirtyFieldsMixin, models.Model, SimoAdminMixin):
|
|
|
175
180
|
dirty_fields = self.get_dirty_fields()
|
|
176
181
|
if 'all' in dirty_fields:
|
|
177
182
|
if self.all:
|
|
178
|
-
Category.objects.
|
|
183
|
+
Category.objects.filter(
|
|
184
|
+
instance=self.instance
|
|
185
|
+
).update(all=False)
|
|
179
186
|
if 'header_image' in dirty_fields:
|
|
180
187
|
self.header_image_last_change = timezone.now()
|
|
181
188
|
return super().save(*args, **kwargs)
|
|
@@ -197,6 +204,11 @@ class Gateway(DirtyFieldsMixin, models.Model, SimoAdminMixin):
|
|
|
197
204
|
(key, val) for key, val in RUN_STATUS_CHOICES_MAP.items()
|
|
198
205
|
),
|
|
199
206
|
)
|
|
207
|
+
discovery = models.JSONField(
|
|
208
|
+
null=True, blank=True, editable=False
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
|
|
200
212
|
handler = None
|
|
201
213
|
|
|
202
214
|
def __str__(self):
|
|
@@ -243,6 +255,52 @@ class Gateway(DirtyFieldsMixin, models.Model, SimoAdminMixin):
|
|
|
243
255
|
urlconf=settings.CHANNELS_URLCONF
|
|
244
256
|
)
|
|
245
257
|
|
|
258
|
+
def start_discovery(self, controller_uid, init_data, timeout=None):
|
|
259
|
+
self.discovery = {
|
|
260
|
+
'start': time.time(),
|
|
261
|
+
'timeout': timeout if timeout else 60,
|
|
262
|
+
'controller_uid': controller_uid,
|
|
263
|
+
'init_data': init_data,
|
|
264
|
+
'result': []
|
|
265
|
+
}
|
|
266
|
+
self.save()
|
|
267
|
+
|
|
268
|
+
def retry_discovery(self):
|
|
269
|
+
self.discovery['start'] = time.time()
|
|
270
|
+
self.discovery.pop('finished', None)
|
|
271
|
+
self.save()
|
|
272
|
+
|
|
273
|
+
def process_discovery(self, data):
|
|
274
|
+
self.refresh_from_db()
|
|
275
|
+
from .utils.type_constants import get_controller_types_map
|
|
276
|
+
ControllerClass = get_controller_types_map().get(
|
|
277
|
+
self.discovery['controller_uid']
|
|
278
|
+
)
|
|
279
|
+
if ControllerClass and hasattr(
|
|
280
|
+
ControllerClass, '_process_discovery'
|
|
281
|
+
):
|
|
282
|
+
result = ControllerClass._process_discovery(
|
|
283
|
+
started_with=self.discovery['init_data'], data=data
|
|
284
|
+
)
|
|
285
|
+
if result:
|
|
286
|
+
if not isinstance(result, dict) and isinstance(result, Iterable):
|
|
287
|
+
for res in result:
|
|
288
|
+
if isinstance(res, models.Model):
|
|
289
|
+
self.discovery['result'].append(res.pk)
|
|
290
|
+
else:
|
|
291
|
+
self.discovery['result'].append(res)
|
|
292
|
+
else:
|
|
293
|
+
if isinstance(result, models.Model):
|
|
294
|
+
self.discovery['result'].append(result.pk)
|
|
295
|
+
else:
|
|
296
|
+
self.discovery['result'].append(result)
|
|
297
|
+
|
|
298
|
+
self.save(update_fields=['discovery'])
|
|
299
|
+
|
|
300
|
+
def finish_discovery(self):
|
|
301
|
+
self.discovery['finished'] = time.time()
|
|
302
|
+
self.save(update_fields=['discovery'])
|
|
303
|
+
|
|
246
304
|
|
|
247
305
|
class Component(DirtyFieldsMixin, models.Model, SimoAdminMixin, OnChangeMixin):
|
|
248
306
|
name = models.CharField(
|
|
@@ -258,19 +316,12 @@ class Component(DirtyFieldsMixin, models.Model, SimoAdminMixin, OnChangeMixin):
|
|
|
258
316
|
Category, related_name='components', on_delete=models.CASCADE,
|
|
259
317
|
null=True, blank=True
|
|
260
318
|
)
|
|
261
|
-
tags = TaggableManager(blank=True)
|
|
262
|
-
# TODO: Remove gateway instance from component.
|
|
263
|
-
# There can't be two instances of same type gateway, therefore its
|
|
264
|
-
# instance is only required to deliver configuration and
|
|
265
|
-
# background service responsible for components management.
|
|
266
|
-
# Convert this to CharField for components filtering.
|
|
267
319
|
gateway = models.ForeignKey(
|
|
268
320
|
Gateway, on_delete=models.CASCADE, related_name='components'
|
|
269
321
|
)
|
|
270
322
|
base_type = models.CharField(
|
|
271
323
|
_("base type"), max_length=200, db_index=True#, choices=BASE_TYPE_CHOICES
|
|
272
324
|
)
|
|
273
|
-
# Rename to controller_uid
|
|
274
325
|
controller_uid = models.CharField(
|
|
275
326
|
_("type"), max_length=200, choices=(), db_index=True,
|
|
276
327
|
)
|
|
@@ -282,9 +333,8 @@ class Component(DirtyFieldsMixin, models.Model, SimoAdminMixin, OnChangeMixin):
|
|
|
282
333
|
value_previous = models.JSONField(null=True, blank=True, editable=False)
|
|
283
334
|
value_units = models.CharField(max_length=100, null=True, blank=True)
|
|
284
335
|
|
|
285
|
-
|
|
286
|
-
'Component', null=True, blank=True,
|
|
287
|
-
related_name='masters'
|
|
336
|
+
slaves = models.ManyToManyField(
|
|
337
|
+
'Component', null=True, blank=True, related_name='masters'
|
|
288
338
|
)
|
|
289
339
|
|
|
290
340
|
change_init_by = models.ForeignKey(
|
|
@@ -318,15 +368,6 @@ class Component(DirtyFieldsMixin, models.Model, SimoAdminMixin, OnChangeMixin):
|
|
|
318
368
|
def is_in_alarm(self):
|
|
319
369
|
return bool(self.value)
|
|
320
370
|
|
|
321
|
-
def translate_before_send(self, value):
|
|
322
|
-
'''Perform value translation just before sending it to device.'''
|
|
323
|
-
return value
|
|
324
|
-
|
|
325
|
-
def translate_before_set(self, value):
|
|
326
|
-
'''Perform value translation just before value is set to component.
|
|
327
|
-
Must return a valid value for this component type.'''
|
|
328
|
-
return value
|
|
329
|
-
|
|
330
371
|
""")
|
|
331
372
|
|
|
332
373
|
alarm_category = models.CharField(
|
|
@@ -343,12 +384,13 @@ def translate_before_set(self, value):
|
|
|
343
384
|
)
|
|
344
385
|
)
|
|
345
386
|
|
|
387
|
+
objects = ComponentsManager()
|
|
388
|
+
|
|
346
389
|
tracker = FieldTracker(fields=('value', 'arm_status'))
|
|
347
390
|
# change this to False before saving to not record changes to history
|
|
348
391
|
track_history = True
|
|
349
392
|
|
|
350
393
|
controller_cls = None
|
|
351
|
-
controller = None
|
|
352
394
|
|
|
353
395
|
_mqtt_client = None
|
|
354
396
|
_on_change_function = None
|
|
@@ -359,41 +401,8 @@ def translate_before_set(self, value):
|
|
|
359
401
|
verbose_name_plural = _("Components")
|
|
360
402
|
ordering = 'zone', 'base_type', 'name'
|
|
361
403
|
|
|
362
|
-
|
|
363
404
|
def __init__(self, *args, **kwargs):
|
|
364
|
-
from .utils.type_constants import (
|
|
365
|
-
get_controller_types_map,
|
|
366
|
-
get_controller_types_choices
|
|
367
|
-
)
|
|
368
|
-
self._meta.get_field('controller_uid').choices = \
|
|
369
|
-
get_controller_types_choices()
|
|
370
405
|
super().__init__(*args, **kwargs)
|
|
371
|
-
if self.controller_uid and not self.controller:
|
|
372
|
-
if self.id and not 'test' in sys.argv:
|
|
373
|
-
try:
|
|
374
|
-
self.controller_cls = cache.get('c_%d_contr_cls' % self.id)
|
|
375
|
-
except:
|
|
376
|
-
pass
|
|
377
|
-
if not self.controller_cls:
|
|
378
|
-
self.controller_cls = get_controller_types_map(
|
|
379
|
-
self.gateway
|
|
380
|
-
).get(self.controller_uid)
|
|
381
|
-
if self.controller_cls and self.id and not 'test' in sys.argv:
|
|
382
|
-
cache.set(
|
|
383
|
-
'c_%d_contr_cls' % self.id,
|
|
384
|
-
self.controller_cls, None
|
|
385
|
-
)
|
|
386
|
-
|
|
387
|
-
if self.controller_cls:
|
|
388
|
-
self.controller = self.controller_cls(self)
|
|
389
|
-
controller_methods = [m for m in inspect.getmembers(
|
|
390
|
-
self.controller, predicate=inspect.ismethod
|
|
391
|
-
) if not m[0].startswith('_')]
|
|
392
|
-
for method in controller_methods:
|
|
393
|
-
setattr(self, method[0], method[1])
|
|
394
|
-
if not self.id:
|
|
395
|
-
self.value = self.controller.default_value
|
|
396
|
-
|
|
397
406
|
# Goes in zero seconds!
|
|
398
407
|
if self.instance_methods:
|
|
399
408
|
custom_methods = {}
|
|
@@ -412,6 +421,43 @@ def translate_before_set(self, value):
|
|
|
412
421
|
return '%s | %s' % (self.zone.name, self.name)
|
|
413
422
|
return self.name
|
|
414
423
|
|
|
424
|
+
@cached_property
|
|
425
|
+
def controller(self):
|
|
426
|
+
from .utils.type_constants import (
|
|
427
|
+
get_controller_types_map,
|
|
428
|
+
get_controller_types_choices
|
|
429
|
+
)
|
|
430
|
+
self._meta.get_field('controller_uid').choices = \
|
|
431
|
+
get_controller_types_choices()
|
|
432
|
+
if self.controller_uid:
|
|
433
|
+
controller_cls = None
|
|
434
|
+
if self.id and not 'test' in sys.argv:
|
|
435
|
+
try:
|
|
436
|
+
controller_cls = cache.get('c_%d_contr_cls' % self.id)
|
|
437
|
+
except:
|
|
438
|
+
pass
|
|
439
|
+
if not controller_cls:
|
|
440
|
+
controller_cls = get_controller_types_map(
|
|
441
|
+
self.gateway
|
|
442
|
+
).get(self.controller_uid)
|
|
443
|
+
if controller_cls and self.id and not 'test' in sys.argv:
|
|
444
|
+
cache.set(
|
|
445
|
+
'c_%d_contr_cls' % self.id,
|
|
446
|
+
controller_cls, None
|
|
447
|
+
)
|
|
448
|
+
if controller_cls:
|
|
449
|
+
return controller_cls(self)
|
|
450
|
+
|
|
451
|
+
def prepare_controller(self):
|
|
452
|
+
if not self.controller:
|
|
453
|
+
return
|
|
454
|
+
controller_methods = [m for m in inspect.getmembers(
|
|
455
|
+
self.controller, predicate=inspect.ismethod
|
|
456
|
+
) if not m[0].startswith('_')]
|
|
457
|
+
for method in controller_methods:
|
|
458
|
+
setattr(self, method[0], method[1])
|
|
459
|
+
if not self.id:
|
|
460
|
+
self.value = self.controller.default_value
|
|
415
461
|
|
|
416
462
|
def get_socket_url(self):
|
|
417
463
|
return reverse_lazy(
|
|
@@ -472,15 +518,6 @@ def translate_before_set(self, value):
|
|
|
472
518
|
def is_in_alarm(self):
|
|
473
519
|
return bool(self.value)
|
|
474
520
|
|
|
475
|
-
def translate_before_send(self, value):
|
|
476
|
-
'''Perform value translation just before sending it to device.'''
|
|
477
|
-
return value
|
|
478
|
-
|
|
479
|
-
def translate_before_set(self, value):
|
|
480
|
-
'''Perform value translation just before value is set to component.
|
|
481
|
-
Must return a valid value for this component type.'''
|
|
482
|
-
return value
|
|
483
|
-
|
|
484
521
|
def can_read(self, user):
|
|
485
522
|
if user.is_superuser:
|
|
486
523
|
return True
|
simo/core/permissions.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
from rest_framework.permissions import BasePermission
|
|
2
|
-
from .models import Instance
|
|
1
|
+
from rest_framework.permissions import BasePermission, SAFE_METHODS
|
|
2
|
+
from .models import Instance, Category, Zone
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
class InstancePermission(BasePermission):
|
|
@@ -21,3 +21,29 @@ class InstancePermission(BasePermission):
|
|
|
21
21
|
return False
|
|
22
22
|
|
|
23
23
|
return True
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class IsInstanceSuperuser(BasePermission):
|
|
27
|
+
|
|
28
|
+
def has_permission(self, request, view):
|
|
29
|
+
if request.user.is_master:
|
|
30
|
+
return True
|
|
31
|
+
user_role = request.user.get_role(view.instance)
|
|
32
|
+
return user_role.is_superuser
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class InstanceSuperuserCanEdit(BasePermission):
|
|
36
|
+
|
|
37
|
+
def has_object_permission(self, request, view, obj):
|
|
38
|
+
|
|
39
|
+
# allow deleting only empty categories and zones
|
|
40
|
+
if type(obj) in (Zone, Category) and request.method == 'DELETE'\
|
|
41
|
+
and obj.components.all().count():
|
|
42
|
+
return False
|
|
43
|
+
|
|
44
|
+
if request.user.is_master:
|
|
45
|
+
return True
|
|
46
|
+
user_role = request.user.get_role(view.instance)
|
|
47
|
+
if user_role.is_superuser:
|
|
48
|
+
return True
|
|
49
|
+
return request.method in SAFE_METHODS
|