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/controllers.py
CHANGED
|
@@ -11,11 +11,13 @@ from django.utils.translation import gettext_lazy as _
|
|
|
11
11
|
from simo.users.middleware import introduce, get_current_user
|
|
12
12
|
from simo.users.utils import get_device_user
|
|
13
13
|
from .utils.helpers import is_hex_color, classproperty
|
|
14
|
+
# from django.utils.functional import classproperty
|
|
14
15
|
from .gateways import BaseGatewayHandler
|
|
15
16
|
from .app_widgets import *
|
|
16
17
|
from .forms import (
|
|
17
18
|
BaseComponentForm, NumericSensorForm,
|
|
18
|
-
MultiSensorConfigForm,
|
|
19
|
+
MultiSensorConfigForm,
|
|
20
|
+
SwitchForm, DoubleSwitchConfigForm,
|
|
19
21
|
TrippleSwitchConfigForm, QuadrupleSwitchConfigForm,
|
|
20
22
|
QuintupleSwitchConfigForm, DimmerConfigForm, DimmerPlusConfigForm,
|
|
21
23
|
RGBWConfigForm
|
|
@@ -31,6 +33,7 @@ class ControllerBase(ABC):
|
|
|
31
33
|
admin_widget_template = 'admin/controller_widgets/generic.html'
|
|
32
34
|
default_config = {}
|
|
33
35
|
default_meta = {}
|
|
36
|
+
discovery_msg = None
|
|
34
37
|
|
|
35
38
|
@property
|
|
36
39
|
@abstractmethod
|
|
@@ -83,7 +86,7 @@ class ControllerBase(ABC):
|
|
|
83
86
|
assert issubclass(self.config_form, BaseComponentForm)
|
|
84
87
|
assert issubclass(self.app_widget, BaseAppWidget)
|
|
85
88
|
assert self.base_type in ALL_BASE_TYPES, \
|
|
86
|
-
"base_type must be defined in BASE_TYPES"
|
|
89
|
+
f"{self.base_type} must be defined in BASE_TYPES!"
|
|
87
90
|
|
|
88
91
|
@classproperty
|
|
89
92
|
@classmethod
|
|
@@ -98,6 +101,15 @@ class ControllerBase(ABC):
|
|
|
98
101
|
"""
|
|
99
102
|
return cls.config_form
|
|
100
103
|
|
|
104
|
+
@classproperty
|
|
105
|
+
@classmethod
|
|
106
|
+
def is_discoverable(cls):
|
|
107
|
+
return hasattr(
|
|
108
|
+
cls, 'init_discovery'
|
|
109
|
+
) and hasattr(
|
|
110
|
+
cls, '_process_discovery'
|
|
111
|
+
)
|
|
112
|
+
|
|
101
113
|
def _aggregate_values(self, values):
|
|
102
114
|
if type(values[0]) in (float, int):
|
|
103
115
|
return [statistics.mean(values)]
|
|
@@ -165,7 +177,38 @@ class ControllerBase(ABC):
|
|
|
165
177
|
else:
|
|
166
178
|
return self.component.change_init_by
|
|
167
179
|
|
|
180
|
+
def send(self, value):
|
|
181
|
+
self.component.refresh_from_db()
|
|
182
|
+
|
|
183
|
+
# Bulk send if it is a switch or dimmer and has slaves
|
|
184
|
+
if self.component.base_type in ('switch', 'dimmer') \
|
|
185
|
+
and self.component.slaves.count():
|
|
186
|
+
bulk_send_map = {self.component: value}
|
|
187
|
+
for slave in self.component.slaves.all():
|
|
188
|
+
bulk_send_map[slave] = value
|
|
189
|
+
from .models import Component
|
|
190
|
+
Component.objects.bulk_send(bulk_send_map)
|
|
191
|
+
return
|
|
192
|
+
|
|
193
|
+
# Regular send
|
|
194
|
+
value = self._validate_val(value, BEFORE_SEND)
|
|
195
|
+
|
|
196
|
+
self.component.change_init_by = get_current_user()
|
|
197
|
+
self.component.change_init_date = timezone.now()
|
|
198
|
+
self.component.save(
|
|
199
|
+
update_fields=['change_init_by', 'change_init_date']
|
|
200
|
+
)
|
|
201
|
+
value = self._prepare_for_send(value)
|
|
202
|
+
GatewayObjectCommand(
|
|
203
|
+
self.component.gateway, self.component, set_val=value
|
|
204
|
+
).publish()
|
|
205
|
+
if value != self.component.value:
|
|
206
|
+
self.component.value_previous = self.component.value
|
|
207
|
+
self.component.value = value
|
|
208
|
+
|
|
168
209
|
def set(self, value, actor=None):
|
|
210
|
+
value = self._validate_val(value, BEFORE_SET)
|
|
211
|
+
|
|
169
212
|
if not actor:
|
|
170
213
|
actor = self._get_actor(value)
|
|
171
214
|
if not actor:
|
|
@@ -175,8 +218,6 @@ class ControllerBase(ABC):
|
|
|
175
218
|
# in relation to the change of this component
|
|
176
219
|
introduce(actor)
|
|
177
220
|
|
|
178
|
-
value = self.component.translate_before_set(value)
|
|
179
|
-
value = self._validate_val(value, BEFORE_SET)
|
|
180
221
|
self.component.refresh_from_db()
|
|
181
222
|
if value != self.component.value:
|
|
182
223
|
self.component.value_previous = self.component.value
|
|
@@ -187,16 +228,15 @@ class ControllerBase(ABC):
|
|
|
187
228
|
self.component.change_init_fingerprint = None
|
|
188
229
|
self.component.save()
|
|
189
230
|
|
|
190
|
-
def _send_to_device(self, value):
|
|
191
|
-
GatewayObjectCommand(
|
|
192
|
-
self.component.gateway, self.component, set_val=value
|
|
193
|
-
).publish()
|
|
194
|
-
|
|
195
231
|
def _receive_from_device(self, value, is_alive=True):
|
|
196
232
|
value = self._prepare_for_set(value)
|
|
197
233
|
actor = self._get_actor(value)
|
|
234
|
+
|
|
235
|
+
init_by_device = False
|
|
198
236
|
if not actor:
|
|
237
|
+
init_by_device = True
|
|
199
238
|
actor = get_device_user()
|
|
239
|
+
|
|
200
240
|
# Introducing user to this thread for changes that might happen to other components
|
|
201
241
|
# in relation to the change of this component
|
|
202
242
|
introduce(actor)
|
|
@@ -204,27 +244,17 @@ class ControllerBase(ABC):
|
|
|
204
244
|
self.component.save(update_fields=['alive'])
|
|
205
245
|
self.set(value, actor)
|
|
206
246
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
self.component.change_init_by = get_current_user()
|
|
219
|
-
self.component.change_init_date = timezone.now()
|
|
220
|
-
self.component.save(
|
|
221
|
-
update_fields=['change_init_by', 'change_init_date']
|
|
222
|
-
)
|
|
223
|
-
value = self._prepare_for_send(value)
|
|
224
|
-
self._send_to_device(value)
|
|
225
|
-
if value != self.component.value:
|
|
226
|
-
self.component.value_previous = self.component.value
|
|
227
|
-
self.component.value = value
|
|
247
|
+
if init_by_device and self.component.slaves.count():
|
|
248
|
+
slaves_qs = self.component.slaves.all()
|
|
249
|
+
# slaves are being controlled by colonels internally
|
|
250
|
+
if self.component.controller_uid.startswith('simo.fleet.') \
|
|
251
|
+
and self.component.config.get('colonel'):
|
|
252
|
+
slaves_qs = slaves_qs.exclude(
|
|
253
|
+
config__colonel=self.component.config['colonel']
|
|
254
|
+
)
|
|
255
|
+
bulk_send_map = {s: value for s in slaves_qs}
|
|
256
|
+
from .models import Component
|
|
257
|
+
Component.objects.bulk_send(bulk_send_map)
|
|
228
258
|
|
|
229
259
|
def history_display(self, values):
|
|
230
260
|
assert type(values) in (list, tuple)
|
|
@@ -245,6 +275,12 @@ class ControllerBase(ABC):
|
|
|
245
275
|
'val': icon if any(values) else None}
|
|
246
276
|
]
|
|
247
277
|
|
|
278
|
+
def _prepare_for_send(self, value):
|
|
279
|
+
return value
|
|
280
|
+
|
|
281
|
+
def _prepare_for_set(self, value):
|
|
282
|
+
return value
|
|
283
|
+
|
|
248
284
|
|
|
249
285
|
class TimerMixin:
|
|
250
286
|
|
|
@@ -408,8 +444,22 @@ class Dimmer(ControllerBase, TimerMixin):
|
|
|
408
444
|
default_config = {'min': 0.0, 'max': 100.0, 'inverse': False}
|
|
409
445
|
default_value = 0
|
|
410
446
|
|
|
447
|
+
def _prepare_for_send(self, value):
|
|
448
|
+
if isinstance(value, bool):
|
|
449
|
+
if value:
|
|
450
|
+
self.component.refresh_from_db()
|
|
451
|
+
if self.component.value:
|
|
452
|
+
return self.component.value
|
|
453
|
+
else:
|
|
454
|
+
if self.component.value_previous:
|
|
455
|
+
return self.component.value_previous
|
|
456
|
+
return self.component.config.get('max', 100.0)
|
|
457
|
+
else:
|
|
458
|
+
return 0
|
|
459
|
+
return value
|
|
460
|
+
|
|
411
461
|
def _validate_val(self, value, occasion=None):
|
|
412
|
-
if value > self.component.config.get('max',
|
|
462
|
+
if value > self.component.config.get('max', 100.0):
|
|
413
463
|
raise ValidationError("Value to big.")
|
|
414
464
|
elif value < self.component.config.get('min', 0.0):
|
|
415
465
|
raise ValidationError("Value to small.")
|
|
@@ -508,7 +558,7 @@ class DimmerPlus(ControllerBase, TimerMixin):
|
|
|
508
558
|
})
|
|
509
559
|
|
|
510
560
|
def toggle(self):
|
|
511
|
-
if self.component.value:
|
|
561
|
+
if self.component.value.get('main'):
|
|
512
562
|
self.turn_off()
|
|
513
563
|
else:
|
|
514
564
|
self.turn_on()
|
|
@@ -602,6 +652,7 @@ class Switch(MultiSwitchBase, TimerMixin):
|
|
|
602
652
|
name = _("Switch")
|
|
603
653
|
base_type = 'switch'
|
|
604
654
|
app_widget = SingleSwitchWidget
|
|
655
|
+
config_form = SwitchForm
|
|
605
656
|
admin_widget_template = 'admin/controller_widgets/switch.html'
|
|
606
657
|
default_value = False
|
|
607
658
|
|
|
@@ -642,6 +693,11 @@ class DoubleSwitch(MultiSwitchBase):
|
|
|
642
693
|
config_form = DoubleSwitchConfigForm
|
|
643
694
|
default_value = [False, False]
|
|
644
695
|
|
|
696
|
+
def _prepare_for_send(self, value):
|
|
697
|
+
if isinstance(value, bool):
|
|
698
|
+
return [value, value]
|
|
699
|
+
return value
|
|
700
|
+
|
|
645
701
|
|
|
646
702
|
class TripleSwitch(MultiSwitchBase):
|
|
647
703
|
name = _("Triple Switch")
|
|
@@ -650,6 +706,11 @@ class TripleSwitch(MultiSwitchBase):
|
|
|
650
706
|
config_form = TrippleSwitchConfigForm
|
|
651
707
|
default_value = [False, False, False]
|
|
652
708
|
|
|
709
|
+
def _prepare_for_send(self, value):
|
|
710
|
+
if isinstance(value, bool):
|
|
711
|
+
return [value, value, value]
|
|
712
|
+
return value
|
|
713
|
+
|
|
653
714
|
|
|
654
715
|
class QuadrupleSwitch(MultiSwitchBase):
|
|
655
716
|
name = _("Quadruple Switch")
|
|
@@ -658,6 +719,11 @@ class QuadrupleSwitch(MultiSwitchBase):
|
|
|
658
719
|
config_form = QuadrupleSwitchConfigForm
|
|
659
720
|
default_value = [False, False, False, False]
|
|
660
721
|
|
|
722
|
+
def _prepare_for_send(self, value):
|
|
723
|
+
if isinstance(value, bool):
|
|
724
|
+
return [value, value, value, value]
|
|
725
|
+
return value
|
|
726
|
+
|
|
661
727
|
|
|
662
728
|
class QuintupleSwitch(MultiSwitchBase):
|
|
663
729
|
name = _("Quintuple Switch")
|
|
@@ -666,6 +732,11 @@ class QuintupleSwitch(MultiSwitchBase):
|
|
|
666
732
|
config_form = QuintupleSwitchConfigForm
|
|
667
733
|
default_value = [False, False, False, False, False]
|
|
668
734
|
|
|
735
|
+
def _prepare_for_send(self, value):
|
|
736
|
+
if isinstance(value, bool):
|
|
737
|
+
return [value, value, value, value, value]
|
|
738
|
+
return value
|
|
739
|
+
|
|
669
740
|
|
|
670
741
|
class Lock(Switch):
|
|
671
742
|
name = _("Lock")
|
|
@@ -702,3 +773,12 @@ class Lock(Switch):
|
|
|
702
773
|
f"one of available values [{available_values}] for lock."
|
|
703
774
|
)
|
|
704
775
|
return value
|
|
776
|
+
|
|
777
|
+
def set(self, value, actor=None):
|
|
778
|
+
super().set(value, actor=actor)
|
|
779
|
+
if actor and value in ('locking', 'unlocking'):
|
|
780
|
+
self.component.change_init_by = actor
|
|
781
|
+
self.component.change_init_date = timezone.now()
|
|
782
|
+
self.component.save(
|
|
783
|
+
update_fields=['change_init_by', 'change_init_date']
|
|
784
|
+
)
|
simo/core/db_backend/base.py
CHANGED
|
@@ -2,6 +2,7 @@ import random, time
|
|
|
2
2
|
from django.contrib.gis.db.backends.postgis.base import (
|
|
3
3
|
DatabaseWrapper as PostGisPsycopg2DatabaseWrapper
|
|
4
4
|
)
|
|
5
|
+
from django.db import close_old_connections, connection as db_connection
|
|
5
6
|
from django.utils.asyncio import async_unsafe
|
|
6
7
|
from django.db.utils import InterfaceError
|
|
7
8
|
from django.conf import settings
|
|
@@ -9,28 +10,12 @@ from django.conf import settings
|
|
|
9
10
|
|
|
10
11
|
class DatabaseWrapper(PostGisPsycopg2DatabaseWrapper):
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
# new connection will be established by ensure_connection() method.
|
|
19
|
-
# We also wait for 0 - 10s, to not overwhelm anything in case
|
|
20
|
-
# there are many multiple threads trying to access the same
|
|
21
|
-
# db connection.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def _cursor(self, name=None):
|
|
25
|
-
self.ensure_connection()
|
|
26
|
-
with self.wrap_database_errors:
|
|
27
|
-
try:
|
|
28
|
-
return self._prepare_cursor(self.create_cursor(name))
|
|
29
|
-
except InterfaceError:
|
|
30
|
-
|
|
31
|
-
self.connection = None
|
|
32
|
-
time.sleep(random.randint(0, 100) / 10)
|
|
33
|
-
return self._cursor(name)
|
|
13
|
+
@async_unsafe
|
|
14
|
+
def create_cursor(self, name=None):
|
|
15
|
+
if not self.is_usable():
|
|
16
|
+
close_old_connections()
|
|
17
|
+
db_connection.connect()
|
|
18
|
+
return super().create_cursor(name=name)
|
|
34
19
|
|
|
35
20
|
|
|
36
21
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from __future__ import absolute_import, print_function, unicode_literals
|
|
2
|
+
import inspect
|
|
3
|
+
|
|
4
|
+
from rest_framework import fields
|
|
5
|
+
from rest_framework.fields import * # noqa
|
|
6
|
+
from rest_framework.fields import _UnvalidatedField # noqa
|
|
7
|
+
|
|
8
|
+
from .mixins import AllowBlankNullFieldMixin, EmptyStringFieldMixin
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
FIELDS = [
|
|
12
|
+
'BooleanField',
|
|
13
|
+
'CharField',
|
|
14
|
+
'ChoiceField',
|
|
15
|
+
'DateField',
|
|
16
|
+
'DateTimeField',
|
|
17
|
+
'DecimalField',
|
|
18
|
+
'DurationField',
|
|
19
|
+
'EmailField',
|
|
20
|
+
'FileField',
|
|
21
|
+
'FloatField',
|
|
22
|
+
'HiddenField',
|
|
23
|
+
'ImageField',
|
|
24
|
+
'IntegerField',
|
|
25
|
+
'IPAddressField',
|
|
26
|
+
'MultipleChoiceField',
|
|
27
|
+
'RegexField',
|
|
28
|
+
'SlugField',
|
|
29
|
+
'TimeField',
|
|
30
|
+
'URLField',
|
|
31
|
+
'UUIDField',
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def get_updated_fields(fields, base_classes):
|
|
36
|
+
fields = [globals()[i] for i in fields]
|
|
37
|
+
return {
|
|
38
|
+
field.__name__: type(field.__name__, base_classes + (field,), {})
|
|
39
|
+
for field in fields
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
locals().update(
|
|
44
|
+
get_updated_fields(FIELDS, (EmptyStringFieldMixin, AllowBlankNullFieldMixin))
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
__all__ = [name for name, value in locals().items()
|
|
48
|
+
if inspect.isclass(value) and issubclass(value, fields.Field)]
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
from __future__ import absolute_import, print_function, unicode_literals
|
|
2
|
+
import inspect
|
|
3
|
+
from decimal import Decimal, getcontext
|
|
4
|
+
|
|
5
|
+
import pytz
|
|
6
|
+
import six
|
|
7
|
+
from django.utils.translation import gettext as _
|
|
8
|
+
|
|
9
|
+
from . import _fields as fields
|
|
10
|
+
from .mixins import ValueAsTextFieldMixin
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class UnvalidatedField(fields._UnvalidatedField):
|
|
14
|
+
"""
|
|
15
|
+
Same as DRF's ``_UnvalidatedField``, except this is a public class.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def run_validators(self, value):
|
|
19
|
+
return
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class PositiveIntegerField(fields.IntegerField):
|
|
23
|
+
"""
|
|
24
|
+
Enhanced DRF's ``IntegerField`` as this default ``min_value`` to be 0
|
|
25
|
+
hence only allowing positive numbers.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, *args, **kwargs):
|
|
29
|
+
kwargs.setdefault('min_value', 0)
|
|
30
|
+
super(PositiveIntegerField, self).__init__(*args, **kwargs)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class UTCDateTimeField(fields.DateTimeField):
|
|
34
|
+
"""
|
|
35
|
+
Same as DateTimeField except this field guarantees to return time-zone aware dates.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(self, *args, **kwargs):
|
|
39
|
+
kwargs.setdefault('default_timezone', pytz.utc)
|
|
40
|
+
super(UTCDateTimeField, self).__init__(*args, **kwargs)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class NonValidatingChoiceField(fields.ChoiceField):
|
|
44
|
+
"""
|
|
45
|
+
ChoiceField subclass that skips the validation of "choices".
|
|
46
|
+
It does apply 'required' validation, and any other validation
|
|
47
|
+
done by the parent drf.Field class.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def __init__(self, *args, **kwargs):
|
|
51
|
+
kwargs.setdefault('choices', [])
|
|
52
|
+
super(NonValidatingChoiceField, self).__init__(*args, **kwargs)
|
|
53
|
+
|
|
54
|
+
def to_internal_value(self, data):
|
|
55
|
+
try:
|
|
56
|
+
return self.choice_strings_to_values[six.text_type(data)]
|
|
57
|
+
except KeyError:
|
|
58
|
+
return six.text_type(data)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class NumericField(ValueAsTextFieldMixin, fields.IntegerField):
|
|
62
|
+
default_error_messages = {
|
|
63
|
+
'invalid': _('Enter a whole number.'),
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class RoundedDecimalField(fields.DecimalField):
|
|
68
|
+
"""
|
|
69
|
+
Currency field subclass of Decimal used for rounding currencies
|
|
70
|
+
to two decimal places.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
def __init__(self, max_digits=None, decimal_places=2, rounding=None, *args, **kwargs):
|
|
74
|
+
super(RoundedDecimalField, self).__init__(
|
|
75
|
+
max_digits=max_digits,
|
|
76
|
+
decimal_places=decimal_places,
|
|
77
|
+
*args, **kwargs
|
|
78
|
+
)
|
|
79
|
+
self.rounding = rounding
|
|
80
|
+
|
|
81
|
+
def to_internal_value(self, data):
|
|
82
|
+
return self.quantize(super(RoundedDecimalField, self).to_internal_value(data))
|
|
83
|
+
|
|
84
|
+
def validate_precision(self, data):
|
|
85
|
+
return data
|
|
86
|
+
|
|
87
|
+
def quantize(self, data):
|
|
88
|
+
"""
|
|
89
|
+
Quantize the decimal value to the configured precision.
|
|
90
|
+
"""
|
|
91
|
+
if self.decimal_places is None:
|
|
92
|
+
return data
|
|
93
|
+
|
|
94
|
+
context = getcontext().copy()
|
|
95
|
+
|
|
96
|
+
if self.max_digits is not None:
|
|
97
|
+
context.prec = self.max_digits
|
|
98
|
+
if self.rounding is not None:
|
|
99
|
+
context.rounding = self.rounding
|
|
100
|
+
return data.quantize(
|
|
101
|
+
Decimal('.1') ** self.decimal_places,
|
|
102
|
+
context=context
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
__all__ = [name for name, value in locals().items()
|
|
107
|
+
if inspect.isclass(value) and issubclass(value, fields.Field)]
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from __future__ import absolute_import, print_function, unicode_literals
|
|
2
|
+
|
|
3
|
+
import six
|
|
4
|
+
from rest_framework.fields import CharField, empty
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class EmptyStringFieldMixin(object):
|
|
8
|
+
def validate_empty_values(self, data):
|
|
9
|
+
is_empty, data = super(EmptyStringFieldMixin, self).validate_empty_values(data)
|
|
10
|
+
if not is_empty and data == '':
|
|
11
|
+
if self.required:
|
|
12
|
+
self.fail('required')
|
|
13
|
+
else:
|
|
14
|
+
return True, data
|
|
15
|
+
return is_empty, data
|
|
16
|
+
|
|
17
|
+
def to_representation(self, value):
|
|
18
|
+
if value in ('', None) and not self.required:
|
|
19
|
+
return value
|
|
20
|
+
return super(EmptyStringFieldMixin, self).to_representation(value)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class AllowBlankNullFieldMixin(object):
|
|
24
|
+
def __init__(self, *args, **kwargs):
|
|
25
|
+
super(AllowBlankNullFieldMixin, self).__init__(*args, **kwargs)
|
|
26
|
+
|
|
27
|
+
# some DRF fields explicitly do not allow some kwargs
|
|
28
|
+
# therefore we adjust the field attributes directly
|
|
29
|
+
if not self.required:
|
|
30
|
+
if all([isinstance(self, CharField),
|
|
31
|
+
hasattr(self, 'allow_blank'),
|
|
32
|
+
'allow_blank' not in kwargs]):
|
|
33
|
+
self.allow_blank = True
|
|
34
|
+
if all([hasattr(self, 'allow_null'),
|
|
35
|
+
'allow_null' not in kwargs]):
|
|
36
|
+
self.allow_null = True
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class ValueAsTextFieldMixin(object):
|
|
40
|
+
def to_string_value(self, data):
|
|
41
|
+
if data:
|
|
42
|
+
return six.text_type(data)
|
|
43
|
+
return data
|
|
44
|
+
|
|
45
|
+
def prepare_value_for_validation(self, data):
|
|
46
|
+
return data
|
|
47
|
+
|
|
48
|
+
def run_validation(self, value=empty):
|
|
49
|
+
(is_empty_value, value) = self.validate_empty_values(value)
|
|
50
|
+
if is_empty_value:
|
|
51
|
+
return value
|
|
52
|
+
|
|
53
|
+
value = self.prepare_value_for_validation(value)
|
|
54
|
+
value = self.to_internal_value(value)
|
|
55
|
+
self.run_validators(value)
|
|
56
|
+
value = self.to_string_value(value)
|
|
57
|
+
|
|
58
|
+
return value
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from __future__ import absolute_import, print_function, unicode_literals
|
|
2
|
+
import inspect
|
|
3
|
+
|
|
4
|
+
from . import _fields as fields
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class BooleanField(fields.BooleanField):
|
|
8
|
+
def __init__(self, *args, **kwargs):
|
|
9
|
+
self.TRUE_VALUES = self.TRUE_VALUES | set(kwargs.pop('true_values', []))
|
|
10
|
+
self.FALSE_VALUES = self.FALSE_VALUES | set(kwargs.pop('false_values', []))
|
|
11
|
+
super(BooleanField, self).__init__(*args, **kwargs)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class DecimalField(fields.DecimalField):
|
|
15
|
+
def __init__(self, max_digits=None, decimal_places=None, *args, **kwargs):
|
|
16
|
+
super(DecimalField, self).__init__(
|
|
17
|
+
max_digits=max_digits, decimal_places=decimal_places,
|
|
18
|
+
*args, **kwargs
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
def quantize(self, value):
|
|
22
|
+
if self.max_digits is None or self.decimal_places is None:
|
|
23
|
+
return value
|
|
24
|
+
return super(DecimalField, self).quantize(value)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class DateTimeField(fields.DateTimeField):
|
|
28
|
+
def __init__(self, *args, **kwargs):
|
|
29
|
+
super(DateTimeField, self).__init__(*args, **kwargs)
|
|
30
|
+
# allow fo the case when default_timezone is passed as None
|
|
31
|
+
# since super will in that use Django's default timezone
|
|
32
|
+
if 'default_timezone' in kwargs:
|
|
33
|
+
# DRF >= 3.3
|
|
34
|
+
if callable(self.default_timezone):
|
|
35
|
+
self.timezone = kwargs['default_timezone']
|
|
36
|
+
else:
|
|
37
|
+
self.default_timezone = kwargs['default_timezone']
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
__all__ = [name for name, value in locals().items()
|
|
41
|
+
if inspect.isclass(value) and issubclass(value, fields.Field)]
|
|
File without changes
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from __future__ import absolute_import, print_function, unicode_literals
|
|
2
|
+
|
|
3
|
+
import six
|
|
4
|
+
from dateutil.parser import parse
|
|
5
|
+
from django import forms
|
|
6
|
+
from rest_framework import ISO_8601
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ISO8601DateTimeField(forms.DateTimeField):
|
|
10
|
+
def to_python(self, value):
|
|
11
|
+
if value in self.empty_values:
|
|
12
|
+
return
|
|
13
|
+
|
|
14
|
+
if isinstance(value, six.string_types) and ISO_8601 in self.input_formats:
|
|
15
|
+
try:
|
|
16
|
+
return parse(value)
|
|
17
|
+
except ValueError:
|
|
18
|
+
raise forms.ValidationError(self.error_messages['invalid'], code='invalid')
|
|
19
|
+
|
|
20
|
+
return super(ISO8601DateTimeField, self).to_python(value)
|