simo 1.7.20__py3-none-any.whl → 2.0.1__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 +134 -36
- 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 +53 -29
- 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 +140 -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.1.dist-info}/METADATA +8 -9
- {simo-1.7.20.dist-info → simo-2.0.1.dist-info}/RECORD +173 -189
- {simo-1.7.20.dist-info → simo-2.0.1.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.1.dist-info}/LICENSE.md +0 -0
- {simo-1.7.20.dist-info → simo-2.0.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from __future__ import absolute_import, print_function, unicode_literals
|
|
2
|
+
import unittest
|
|
3
|
+
|
|
4
|
+
from rest_framework import fields
|
|
5
|
+
|
|
6
|
+
from ..utils import (
|
|
7
|
+
find_class_args,
|
|
8
|
+
find_function_args,
|
|
9
|
+
get_attr_from_base_classes,
|
|
10
|
+
get_class_name_with_new_suffix,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TestUtils(unittest.TestCase):
|
|
15
|
+
def test_get_class_name_with_new_suffix(self):
|
|
16
|
+
new_name = get_class_name_with_new_suffix(
|
|
17
|
+
klass=fields.IntegerField,
|
|
18
|
+
existing_suffix='Field',
|
|
19
|
+
new_suffix='StrawberryFields'
|
|
20
|
+
)
|
|
21
|
+
self.assertEqual(new_name, 'IntegerStrawberryFields')
|
|
22
|
+
|
|
23
|
+
new_name = get_class_name_with_new_suffix(
|
|
24
|
+
klass=fields.IntegerField,
|
|
25
|
+
existing_suffix='straws',
|
|
26
|
+
new_suffix='Blueberries'
|
|
27
|
+
)
|
|
28
|
+
self.assertEqual(new_name, 'IntegerFieldBlueberries')
|
|
29
|
+
|
|
30
|
+
def test_get_attr_from_base_classes(self):
|
|
31
|
+
Parent = type(str('Parent'), (), {'fields': 'pancakes'})
|
|
32
|
+
|
|
33
|
+
self.assertEqual(
|
|
34
|
+
get_attr_from_base_classes((Parent,), [], 'fields'), 'pancakes'
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
self.assertEqual(
|
|
38
|
+
get_attr_from_base_classes(
|
|
39
|
+
(Parent,), {'fields': 'mushrooms'}, 'fields'
|
|
40
|
+
),
|
|
41
|
+
'mushrooms'
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
self.assertEqual(
|
|
45
|
+
get_attr_from_base_classes((Parent,), [], '', default='maple_syrup'),
|
|
46
|
+
'maple_syrup'
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
with self.assertRaises(AttributeError):
|
|
50
|
+
get_attr_from_base_classes(
|
|
51
|
+
(Parent,), {'fields': 'mushrooms'}, 'catchmeifyoucan'
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
def test_find_function_args(self):
|
|
55
|
+
def foo(a, b, c):
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
self.assertListEqual(find_function_args(foo), ['a', 'b', 'c'])
|
|
59
|
+
|
|
60
|
+
def test_find_function_args_invalid(self):
|
|
61
|
+
self.assertListEqual(find_function_args(None), [])
|
|
62
|
+
|
|
63
|
+
def test_find_class_args(self):
|
|
64
|
+
class Bar(object):
|
|
65
|
+
def __init__(self, a, b):
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
class Foo(Bar):
|
|
69
|
+
def __init__(self, c, d):
|
|
70
|
+
super(Foo, self).__init__(None, None)
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
self.assertSetEqual(set(find_class_args(Foo)), {'a', 'b', 'c', 'd'})
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
from __future__ import absolute_import, print_function, unicode_literals
|
|
2
|
+
import inspect
|
|
3
|
+
import itertools
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
IGNORE_ARGS = ['self', 'cls']
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def find_function_args(func):
|
|
10
|
+
"""
|
|
11
|
+
Get the list of parameter names which function accepts.
|
|
12
|
+
"""
|
|
13
|
+
func_args = []
|
|
14
|
+
try:
|
|
15
|
+
spec = inspect.getfullargspec(func) if hasattr(inspect, 'getfullargspec') else inspect.getargspec(func)
|
|
16
|
+
except TypeError:
|
|
17
|
+
return []
|
|
18
|
+
|
|
19
|
+
func_args.extend([i for i in spec[0] if i not in IGNORE_ARGS])
|
|
20
|
+
func_args.extend([i for i in spec.kwonlyargs])
|
|
21
|
+
|
|
22
|
+
return func_args
|
|
23
|
+
|
|
24
|
+
def find_class_args(klass):
|
|
25
|
+
"""
|
|
26
|
+
Find all class arguments (parameters) which can be passed in ``__init__``.
|
|
27
|
+
"""
|
|
28
|
+
args = set()
|
|
29
|
+
|
|
30
|
+
for i in klass.__mro__:
|
|
31
|
+
if i is object or not hasattr(i, '__init__'):
|
|
32
|
+
continue
|
|
33
|
+
args |= set(find_function_args(i.__init__))
|
|
34
|
+
|
|
35
|
+
return list(args)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def find_matching_class_kwargs(reference_object, klass):
|
|
39
|
+
return {
|
|
40
|
+
i: getattr(reference_object, i) for i in find_class_args(klass)
|
|
41
|
+
if hasattr(reference_object, i)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def add_base_class_to_instance(instance, base_class=None, new_name=None):
|
|
46
|
+
"""
|
|
47
|
+
Generic utility for adding a base class to an instance.
|
|
48
|
+
|
|
49
|
+
This function returns a copy of the given instance which
|
|
50
|
+
will then include the new base_class in its ``__mro__``.
|
|
51
|
+
|
|
52
|
+
The way that is done internally is it creates a brand new
|
|
53
|
+
class with correct bases. Then the newly created class is
|
|
54
|
+
instantiated. Since ``__init__`` could be expensive operation
|
|
55
|
+
in any of the base classes of the original instance mro,
|
|
56
|
+
nto make it cheap, we temporarily switch __init__ with
|
|
57
|
+
super simple implementation which does nothing but only
|
|
58
|
+
instantiates class. Once instantiated, then we copy all of the
|
|
59
|
+
instance attributes to the newly created instance.
|
|
60
|
+
Finally, then we pop our mock ``__init__`` implementation.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
instance (object): Instance of any object
|
|
64
|
+
base_class (type): Any class which will be added as first class
|
|
65
|
+
in the newly copied instance mro.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Shallow copy of ``instance`` which will also inherit ``base_class``.
|
|
69
|
+
"""
|
|
70
|
+
# overwrite __init__ since that is mainly responsible for setting
|
|
71
|
+
# instance state but since we explicitly copy it, we can
|
|
72
|
+
# make __init__ a noop method
|
|
73
|
+
def __init__(self, *args, **kwargs):
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
if base_class is not None and base_class not in instance.__class__.mro():
|
|
77
|
+
base_classes = (base_class, instance.__class__)
|
|
78
|
+
else:
|
|
79
|
+
base_classes = (instance.__class__,)
|
|
80
|
+
|
|
81
|
+
new_field_class = type(
|
|
82
|
+
str(new_name or instance.__class__.__name__),
|
|
83
|
+
base_classes,
|
|
84
|
+
{'__init__': __init__}
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
new_instance = new_field_class()
|
|
88
|
+
new_instance.__dict__.update(instance.__dict__)
|
|
89
|
+
|
|
90
|
+
# we added __init__ just for faster instantiation
|
|
91
|
+
# since then we dont have to copy all the parameters
|
|
92
|
+
# when creating new instance and then update its state
|
|
93
|
+
# however after we instantiated the class, we want to
|
|
94
|
+
# pop our silly __init__ implementation so that if somebody
|
|
95
|
+
# wants to instantiate instance.__class__(), it will
|
|
96
|
+
# use the original __init__ method
|
|
97
|
+
del new_field_class.__init__
|
|
98
|
+
|
|
99
|
+
return new_instance
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def initialize_class_using_reference_object(reference_object, klass, **kwargs):
|
|
103
|
+
"""
|
|
104
|
+
Utility function which instantiates ``klass`` by extracting ``__init__``
|
|
105
|
+
kwargs from ``reference_object`` attributes.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
reference_object (object): Any object instance from which matching
|
|
109
|
+
attributes will be used as ``klass``'s ``__init__`` kwargs.
|
|
110
|
+
klass (type): Class which will be instantiated by using
|
|
111
|
+
``reference_object`` attributes.
|
|
112
|
+
**kwargs: Any additional kwargs which will be passed during instantiation.
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Instantiated ``klass`` object.
|
|
116
|
+
"""
|
|
117
|
+
_kwargs = find_matching_class_kwargs(reference_object, klass)
|
|
118
|
+
_kwargs.update(kwargs)
|
|
119
|
+
|
|
120
|
+
return klass(**_kwargs)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def get_class_name_with_new_suffix(klass, existing_suffix, new_suffix):
|
|
124
|
+
"""
|
|
125
|
+
Generates new name by replacing the existing suffix with a new one.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
klass (type): original class from which new name is generated
|
|
129
|
+
existing_suffix (str): the suffix which needs to remain where it is
|
|
130
|
+
new_suffix (str): the new suffix desired
|
|
131
|
+
|
|
132
|
+
Example:
|
|
133
|
+
>>> get_class_name_with_new_suffix(FooForm, 'Form', 'NewForm')
|
|
134
|
+
'FooNewForm'
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
new_name (str): the name with the new suffix
|
|
138
|
+
"""
|
|
139
|
+
class_name = klass.__name__
|
|
140
|
+
|
|
141
|
+
if existing_suffix in class_name:
|
|
142
|
+
prefix, suffix = class_name.rsplit(existing_suffix, 1)
|
|
143
|
+
else:
|
|
144
|
+
prefix, suffix = class_name, ''
|
|
145
|
+
|
|
146
|
+
new_name = str('{}{}{}'.format(prefix, new_suffix, suffix))
|
|
147
|
+
|
|
148
|
+
return new_name
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def get_attr_from_base_classes(bases, attrs, attr, default=None):
|
|
152
|
+
"""
|
|
153
|
+
The attribute is retrieved from the base classes if they are not already
|
|
154
|
+
present on the object.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
bases (tuple, list): The base classes for a class.
|
|
158
|
+
attrs (dict): The attributes of the class.
|
|
159
|
+
attr (str): Specific attribute being looked for.
|
|
160
|
+
default (any): Whatever default value is expected if the
|
|
161
|
+
attr is not found.
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
attribute value as found in base classes or a default when attribute
|
|
165
|
+
is not found and default is provided.
|
|
166
|
+
|
|
167
|
+
Raises:
|
|
168
|
+
AttributeError: When the attribute is not present anywhere in the
|
|
169
|
+
call chain hierarchy specified through bases and the attributes
|
|
170
|
+
of the class itself
|
|
171
|
+
"""
|
|
172
|
+
if attr in attrs:
|
|
173
|
+
return attrs[attr]
|
|
174
|
+
|
|
175
|
+
for base in bases:
|
|
176
|
+
try:
|
|
177
|
+
return getattr(base, attr)
|
|
178
|
+
except AttributeError:
|
|
179
|
+
continue
|
|
180
|
+
|
|
181
|
+
if default is not None:
|
|
182
|
+
return default
|
|
183
|
+
|
|
184
|
+
raise AttributeError(
|
|
185
|
+
'None of the bases have {} attribute'
|
|
186
|
+
''.format(attr)
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def reduce_attr_dict_from_base_classes(bases, getter, default=None):
|
|
191
|
+
data = (default or {}).copy()
|
|
192
|
+
|
|
193
|
+
# get all field mappings from super methods
|
|
194
|
+
for base in itertools.chain(*[reversed(i.mro()) for i in reversed(bases)]):
|
|
195
|
+
data.update(getter(base) or {})
|
|
196
|
+
|
|
197
|
+
return data
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def reduce_attr_dict_from_instance(self, getter, default=None):
|
|
201
|
+
data = reduce_attr_dict_from_base_classes(type(self).mro(), getter, default)
|
|
202
|
+
|
|
203
|
+
# this should of been picked by reduce_attr_dict_from_base_classes
|
|
204
|
+
# however that only accounts base classes and not instance
|
|
205
|
+
# attribute which can be modified in __init__, etc so we
|
|
206
|
+
# explicitly account for it
|
|
207
|
+
data.update(getter(self) or {})
|
|
208
|
+
|
|
209
|
+
return data
|
simo/core/events.py
CHANGED
|
@@ -30,10 +30,10 @@ class ObjMqttAnnouncement:
|
|
|
30
30
|
assert self.data is not None
|
|
31
31
|
mqtt_publish.single(
|
|
32
32
|
self.get_topic(), json.dumps(self.data, default=str),
|
|
33
|
+
retain=retain,
|
|
33
34
|
hostname=settings.MQTT_HOST,
|
|
34
35
|
port=settings.MQTT_PORT,
|
|
35
36
|
auth={'username': 'root', 'password': settings.SECRET_KEY},
|
|
36
|
-
retain=retain
|
|
37
37
|
)
|
|
38
38
|
|
|
39
39
|
def get_topic(self):
|
|
@@ -54,8 +54,8 @@ class ObjectChangeEvent(ObjMqttAnnouncement):
|
|
|
54
54
|
return f"{self.TOPIC}/{self.instance.id if self.instance else 'global'}/" \
|
|
55
55
|
f"{type(self.obj).__name__}-{self.data['obj_pk']}"
|
|
56
56
|
|
|
57
|
-
def publish(self, retain=
|
|
58
|
-
return super().publish(retain=
|
|
57
|
+
def publish(self, retain=True):
|
|
58
|
+
return super().publish(retain=retain)
|
|
59
59
|
|
|
60
60
|
|
|
61
61
|
class GatewayObjectCommand(ObjMqttAnnouncement):
|
simo/core/forms.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import traceback
|
|
3
3
|
import requests
|
|
4
|
+
from dal import forward
|
|
4
5
|
from django import forms
|
|
5
6
|
from django.contrib.admin.forms import AdminAuthenticationForm as OrgAdminAuthenticationForm
|
|
6
7
|
from django.db import models
|
|
@@ -22,6 +23,7 @@ from .widgets import SVGFileWidget, PythonCode, LogOutputWidget
|
|
|
22
23
|
from .widgets import ImageWidget
|
|
23
24
|
from .utils.helpers import get_random_string
|
|
24
25
|
from .utils.formsets import FormsetField
|
|
26
|
+
from .utils.validators import validate_slaves
|
|
25
27
|
|
|
26
28
|
|
|
27
29
|
class HubConfigForm(forms.Form):
|
|
@@ -156,7 +158,9 @@ class ConfigFieldsMixin:
|
|
|
156
158
|
|
|
157
159
|
def __init__(self, *args, **kwargs):
|
|
158
160
|
super().__init__(*args, **kwargs)
|
|
159
|
-
self.model_fields = [
|
|
161
|
+
self.model_fields = [
|
|
162
|
+
f.name for f in Component._meta.fields
|
|
163
|
+
] + ['slaves', ]
|
|
160
164
|
self.config_fields = []
|
|
161
165
|
for field_name, field in self.fields.items():
|
|
162
166
|
if field_name in self.model_fields:
|
|
@@ -164,21 +168,22 @@ class ConfigFieldsMixin:
|
|
|
164
168
|
self.config_fields.append(field_name)
|
|
165
169
|
if self.instance.pk:
|
|
166
170
|
for field_name in self.config_fields:
|
|
167
|
-
if self.instance.config
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
)
|
|
174
|
-
|
|
175
|
-
self.fields[field_name].initial = \
|
|
176
|
-
self.fields[field_name].queryset.filter(
|
|
177
|
-
pk=self.instance.config[field_name]
|
|
178
|
-
).first()
|
|
171
|
+
if field_name not in self.instance.config:
|
|
172
|
+
continue
|
|
173
|
+
if hasattr(self.fields[field_name], 'queryset'):
|
|
174
|
+
if isinstance(self.instance.config.get(field_name), list):
|
|
175
|
+
self.fields[field_name].initial = \
|
|
176
|
+
self.fields[field_name].queryset.filter(
|
|
177
|
+
pk__in=self.instance.config.get(field_name)
|
|
178
|
+
)
|
|
179
179
|
else:
|
|
180
180
|
self.fields[field_name].initial = \
|
|
181
|
-
self.
|
|
181
|
+
self.fields[field_name].queryset.filter(
|
|
182
|
+
pk=self.instance.config.get(field_name)
|
|
183
|
+
).first()
|
|
184
|
+
else:
|
|
185
|
+
self.fields[field_name].initial = \
|
|
186
|
+
self.instance.config.get(field_name)
|
|
182
187
|
|
|
183
188
|
def save(self, commit=True):
|
|
184
189
|
for field_name in self.config_fields:
|
|
@@ -272,7 +277,7 @@ class ComponentAdminForm(forms.ModelForm):
|
|
|
272
277
|
fields = '__all__'
|
|
273
278
|
exclude = (
|
|
274
279
|
'gateway', 'controller_uid', 'base_type',
|
|
275
|
-
'alive', 'value_type', 'value'
|
|
280
|
+
'alive', 'value_type', 'value', 'arm_status',
|
|
276
281
|
)
|
|
277
282
|
widgets = {
|
|
278
283
|
'icon': autocomplete.ModelSelect2(
|
|
@@ -289,29 +294,31 @@ class ComponentAdminForm(forms.ModelForm):
|
|
|
289
294
|
|
|
290
295
|
def __init__(self, *args, **kwargs):
|
|
291
296
|
self.request = kwargs.pop('request', None)
|
|
292
|
-
|
|
293
|
-
self.gateway = kwargs.pop('gateway')
|
|
294
|
-
self.controller_cls = kwargs.pop('controller_cls')
|
|
297
|
+
self.controller_uid = kwargs.pop('controller_uid', '')
|
|
295
298
|
super().__init__(*args, **kwargs)
|
|
296
299
|
if self.instance.pk:
|
|
297
300
|
self.gateway = self.instance.gateway
|
|
298
301
|
self.controller = self.instance.controller
|
|
299
302
|
else:
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
303
|
+
from .utils.type_constants import get_controller_types_map
|
|
304
|
+
ControllerClass = get_controller_types_map().get(self.controller_uid)
|
|
305
|
+
if ControllerClass:
|
|
306
|
+
self.controller = ControllerClass(self.instance)
|
|
307
|
+
self.gateway = Gateway.objects.filter(
|
|
308
|
+
type=ControllerClass.gateway_class.uid
|
|
309
|
+
).first()
|
|
310
|
+
self.instance.gateway = self.gateway
|
|
311
|
+
self.instance.controller_uid = ControllerClass.uid
|
|
312
|
+
self.instance.base_type = self.controller.base_type
|
|
313
|
+
self.instance.value = self.controller.default_value
|
|
314
|
+
self.instance.value_previous = self.controller.default_value
|
|
315
|
+
self.instance.config = self.controller.default_config
|
|
316
|
+
self.instance.meta = self.controller.default_meta
|
|
310
317
|
|
|
311
318
|
@classmethod
|
|
312
319
|
def get_admin_fieldsets(cls, request, obj=None):
|
|
313
320
|
main_fields = (
|
|
314
|
-
'name', 'icon', 'zone', 'category',
|
|
321
|
+
'name', 'icon', 'zone', 'category',
|
|
315
322
|
'show_in_app', 'battery_level',
|
|
316
323
|
'instance_methods', 'value_units',
|
|
317
324
|
'alarm_category', 'arm_status',
|
|
@@ -323,7 +330,7 @@ class ComponentAdminForm(forms.ModelForm):
|
|
|
323
330
|
base_fields.append('zone')
|
|
324
331
|
base_fields.append('category')
|
|
325
332
|
|
|
326
|
-
for field_name in cls.
|
|
333
|
+
for field_name in cls.declared_fields:
|
|
327
334
|
if field_name not in main_fields:
|
|
328
335
|
base_fields.append(field_name)
|
|
329
336
|
|
|
@@ -384,6 +391,8 @@ class ComponentAdminForm(forms.ModelForm):
|
|
|
384
391
|
return self.cleaned_data['instance_methods']
|
|
385
392
|
|
|
386
393
|
|
|
394
|
+
|
|
395
|
+
|
|
387
396
|
class BaseComponentForm(ConfigFieldsMixin, ComponentAdminForm):
|
|
388
397
|
pass
|
|
389
398
|
|
|
@@ -405,8 +414,8 @@ class ValueLimitsMixin:
|
|
|
405
414
|
|
|
406
415
|
|
|
407
416
|
class NumericSensorForm(BaseComponentForm):
|
|
408
|
-
widget = forms.
|
|
409
|
-
|
|
417
|
+
widget = forms.ChoiceField(
|
|
418
|
+
initial='numeric-sensor', choices=(
|
|
410
419
|
('numeric-sensor', "Basic Sensor"),
|
|
411
420
|
('numeric-sensor-graph', "Graph"),
|
|
412
421
|
)
|
|
@@ -470,6 +479,28 @@ class MultiSensorConfigForm(BaseComponentForm):
|
|
|
470
479
|
has_icon = False
|
|
471
480
|
|
|
472
481
|
|
|
482
|
+
class SwitchForm(BaseComponentForm):
|
|
483
|
+
slaves = forms.ModelMultipleChoiceField(
|
|
484
|
+
required=False,
|
|
485
|
+
queryset=Component.objects.filter(
|
|
486
|
+
base_type__in=(
|
|
487
|
+
'dimmer', 'switch', 'blinds', 'script'
|
|
488
|
+
)
|
|
489
|
+
),
|
|
490
|
+
widget=autocomplete.ModelSelect2Multiple(
|
|
491
|
+
url='autocomplete-component', attrs={'data-html': True},
|
|
492
|
+
forward=(forward.Const(
|
|
493
|
+
['dimmer', 'switch', 'blinds', 'script'], 'base_type'),
|
|
494
|
+
)
|
|
495
|
+
)
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
def clean_slaves(self):
|
|
499
|
+
if not self.cleaned_data['slaves'] or not self.instance:
|
|
500
|
+
return self.cleaned_data['slaves']
|
|
501
|
+
return validate_slaves(self.cleaned_data['slaves'], self.instance)
|
|
502
|
+
|
|
503
|
+
|
|
473
504
|
class DoubleSwitchConfigForm(BaseComponentForm):
|
|
474
505
|
icon_1 = forms.ModelChoiceField(
|
|
475
506
|
queryset=Icon.objects.all(),
|
|
@@ -580,6 +611,21 @@ class DimmerConfigForm(BaseComponentForm):
|
|
|
580
611
|
inverse = forms.BooleanField(
|
|
581
612
|
label=_("Inverse dimmer signal"), required=False
|
|
582
613
|
)
|
|
614
|
+
slaves = forms.ModelMultipleChoiceField(
|
|
615
|
+
required=False,
|
|
616
|
+
queryset=Component.objects.filter(
|
|
617
|
+
base_type__in='dimmer',
|
|
618
|
+
),
|
|
619
|
+
widget=autocomplete.ModelSelect2Multiple(
|
|
620
|
+
url='autocomplete-component', attrs={'data-html': True},
|
|
621
|
+
forward=(forward.Const(['dimmer', ], 'base_type'),)
|
|
622
|
+
)
|
|
623
|
+
)
|
|
624
|
+
|
|
625
|
+
def clean_slaves(self):
|
|
626
|
+
if not self.cleaned_data['slaves'] or not self.instance:
|
|
627
|
+
return self.cleaned_data['slaves']
|
|
628
|
+
return validate_slaves(self.cleaned_data['slaves'], self.instance)
|
|
583
629
|
|
|
584
630
|
|
|
585
631
|
class DimmerPlusConfigForm(BaseComponentForm):
|
|
@@ -600,8 +646,4 @@ class DimmerPlusConfigForm(BaseComponentForm):
|
|
|
600
646
|
class RGBWConfigForm(BaseComponentForm):
|
|
601
647
|
has_white = forms.BooleanField(
|
|
602
648
|
label=_("Has WHITE color channel"), required=False,
|
|
603
|
-
)
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
649
|
+
)
|
simo/core/gateways.py
CHANGED
|
@@ -3,6 +3,7 @@ import time
|
|
|
3
3
|
import json
|
|
4
4
|
import paho.mqtt.client as mqtt
|
|
5
5
|
from django.conf import settings
|
|
6
|
+
from django.db import close_old_connections
|
|
6
7
|
from abc import ABC, abstractmethod
|
|
7
8
|
from simo.core.utils.helpers import classproperty
|
|
8
9
|
from simo.core.events import GatewayObjectCommand, get_event_obj
|
|
@@ -59,7 +60,7 @@ class BaseObjectCommandsGatewayHandler(BaseGatewayHandler):
|
|
|
59
60
|
|
|
60
61
|
for task, period in self.periodic_tasks:
|
|
61
62
|
threading.Thread(
|
|
62
|
-
target=self._run_periodic_task, args=(task, period), daemon=True
|
|
63
|
+
target=self._run_periodic_task, args=(self.exit, task, period), daemon=True
|
|
63
64
|
).start()
|
|
64
65
|
|
|
65
66
|
self.mqtt_client.connect(host=settings.MQTT_HOST, port=settings.MQTT_PORT)
|
|
@@ -70,8 +71,8 @@ class BaseObjectCommandsGatewayHandler(BaseGatewayHandler):
|
|
|
70
71
|
|
|
71
72
|
self.mqtt_client.loop_stop()
|
|
72
73
|
|
|
73
|
-
def _run_periodic_task(self, task, period):
|
|
74
|
-
while not
|
|
74
|
+
def _run_periodic_task(self, exit, task, period):
|
|
75
|
+
while not exit.is_set():
|
|
75
76
|
try:
|
|
76
77
|
print(f"Run periodic task {task}!")
|
|
77
78
|
getattr(self, task)()
|
|
@@ -86,19 +87,35 @@ class BaseObjectCommandsGatewayHandler(BaseGatewayHandler):
|
|
|
86
87
|
self.mqtt_client.subscribe(command.get_topic())
|
|
87
88
|
|
|
88
89
|
def _on_mqtt_message(self, client, userdata, msg):
|
|
89
|
-
payload = json.loads(msg.payload)
|
|
90
|
-
if 'set_val' not in payload:
|
|
91
|
-
return
|
|
92
90
|
from simo.core.models import Component
|
|
93
|
-
|
|
94
|
-
if
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
91
|
+
payload = json.loads(msg.payload)
|
|
92
|
+
if 'set_val' in payload:
|
|
93
|
+
component = get_event_obj(payload, Component)
|
|
94
|
+
if not component:
|
|
95
|
+
return
|
|
96
|
+
print(f"Perform Value ({str(payload['set_val'])}) Send to {component}")
|
|
97
|
+
try:
|
|
98
|
+
self.perform_value_send(component, payload['set_val'])
|
|
99
|
+
except Exception as e:
|
|
100
|
+
self.logger.error(e, exc_info=True)
|
|
101
|
+
|
|
102
|
+
if 'bulk_send' in payload:
|
|
103
|
+
self.perform_bulk_send(payload['bulk_send'])
|
|
101
104
|
|
|
102
105
|
def perform_value_send(self, component, value):
|
|
103
106
|
raise NotImplemented()
|
|
104
107
|
|
|
108
|
+
def perform_bulk_send(self, data):
|
|
109
|
+
from simo.core.models import Component
|
|
110
|
+
for comp_id, val in data.items():
|
|
111
|
+
component = Component.objects.filter(
|
|
112
|
+
pk=comp_id, gateway=self.gateway_instance
|
|
113
|
+
).first()
|
|
114
|
+
if not component:
|
|
115
|
+
continue
|
|
116
|
+
try:
|
|
117
|
+
self.perform_value_send(component, val)
|
|
118
|
+
except Exception as e:
|
|
119
|
+
self.logger.error(e, exc_info=True)
|
|
120
|
+
|
|
121
|
+
|
simo/core/managers.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import traceback
|
|
3
|
+
from .middleware import get_current_instance
|
|
4
|
+
from django.utils import timezone
|
|
5
|
+
from django.db import models
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ZonesManager(models.Manager):
|
|
9
|
+
|
|
10
|
+
def get_queryset(self):
|
|
11
|
+
qs = super().get_queryset()
|
|
12
|
+
instance = get_current_instance()
|
|
13
|
+
if instance:
|
|
14
|
+
qs = qs.filter(instance=instance)
|
|
15
|
+
return qs
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CategoriesManager(models.Manager):
|
|
19
|
+
|
|
20
|
+
def get_queryset(self):
|
|
21
|
+
qs = super().get_queryset()
|
|
22
|
+
instance = get_current_instance()
|
|
23
|
+
if instance:
|
|
24
|
+
qs = qs.filter(instance=instance)
|
|
25
|
+
return qs
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ComponentsManager(models.Manager):
|
|
29
|
+
|
|
30
|
+
def get_queryset(self):
|
|
31
|
+
qs = super().get_queryset()
|
|
32
|
+
instance = get_current_instance()
|
|
33
|
+
if instance:
|
|
34
|
+
qs = qs.filter(zone__instance=instance)
|
|
35
|
+
return qs
|
|
36
|
+
|
|
37
|
+
def bulk_send(self, data):
|
|
38
|
+
"""
|
|
39
|
+
:param data: {component1: True, component2: False, component3: 55.0}
|
|
40
|
+
:return:
|
|
41
|
+
"""
|
|
42
|
+
from .models import Component
|
|
43
|
+
from .controllers import BEFORE_SEND
|
|
44
|
+
from simo.users.middleware import get_current_user
|
|
45
|
+
from .events import GatewayObjectCommand
|
|
46
|
+
|
|
47
|
+
for component, value in data.items():
|
|
48
|
+
assert isinstance(component, Component), \
|
|
49
|
+
"Component: value map is required!"
|
|
50
|
+
|
|
51
|
+
gateway_components = {}
|
|
52
|
+
for comp, value in data.items():
|
|
53
|
+
try:
|
|
54
|
+
value = comp.controller._validate_val(value, BEFORE_SEND)
|
|
55
|
+
except:
|
|
56
|
+
print(traceback.format_exc(), file=sys.stderr)
|
|
57
|
+
continue
|
|
58
|
+
|
|
59
|
+
comp.change_init_by = get_current_user()
|
|
60
|
+
comp.change_init_date = timezone.now()
|
|
61
|
+
comp.save(
|
|
62
|
+
update_fields=['change_init_by', 'change_init_date']
|
|
63
|
+
)
|
|
64
|
+
try:
|
|
65
|
+
value = comp.controller._prepare_for_send(value)
|
|
66
|
+
except:
|
|
67
|
+
print(traceback.format_exc(), file=sys.stderr)
|
|
68
|
+
continue
|
|
69
|
+
|
|
70
|
+
if comp.gateway not in gateway_components:
|
|
71
|
+
gateway_components[comp.gateway] = {}
|
|
72
|
+
gateway_components[comp.gateway][comp.id] = value
|
|
73
|
+
|
|
74
|
+
print("BULK SEND: ", gateway_components)
|
|
75
|
+
for gateway, send_vals in gateway_components.items():
|
|
76
|
+
GatewayObjectCommand(gateway, bulk_send=send_vals).publish(
|
|
77
|
+
retain=False
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|