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,46 @@
|
|
|
1
|
+
from __future__ import absolute_import, print_function, unicode_literals
|
|
2
|
+
import unittest
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
import pytz
|
|
6
|
+
from dateutil.tz import tzoffset
|
|
7
|
+
from django import forms
|
|
8
|
+
from rest_framework import ISO_8601
|
|
9
|
+
|
|
10
|
+
from ...forms.fields import ISO8601DateTimeField
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestISO8601DateTimeField(unittest.TestCase):
|
|
14
|
+
def test_to_python_empty(self):
|
|
15
|
+
field = ISO8601DateTimeField(required=False)
|
|
16
|
+
|
|
17
|
+
self.assertIsNone(field.clean(''))
|
|
18
|
+
|
|
19
|
+
def test_to_python_not_iso8601(self):
|
|
20
|
+
field = ISO8601DateTimeField()
|
|
21
|
+
|
|
22
|
+
self.assertEqual(field.clean('2015-01-01 16:30'), datetime(2015, 1, 1, 16, 30))
|
|
23
|
+
with self.assertRaises(forms.ValidationError):
|
|
24
|
+
field.clean('2015-01-01T16:30')
|
|
25
|
+
|
|
26
|
+
def test_to_python_iso8601(self):
|
|
27
|
+
field = ISO8601DateTimeField(input_formats=[ISO_8601])
|
|
28
|
+
|
|
29
|
+
self.assertEqual(
|
|
30
|
+
field.clean('2015-01-01 16:30'),
|
|
31
|
+
datetime(2015, 1, 1, 16, 30)
|
|
32
|
+
)
|
|
33
|
+
self.assertEqual(
|
|
34
|
+
field.clean('2015-01-01T16:30'),
|
|
35
|
+
datetime(2015, 1, 1, 16, 30)
|
|
36
|
+
)
|
|
37
|
+
self.assertEqual(
|
|
38
|
+
field.clean('2015-01-01T16:30+00:00'),
|
|
39
|
+
datetime(2015, 1, 1, 16, 30).replace(tzinfo=pytz.UTC)
|
|
40
|
+
)
|
|
41
|
+
self.assertEqual(
|
|
42
|
+
field.clean('2015-01-01T16:30+04:00'),
|
|
43
|
+
datetime(2015, 1, 1, 16, 30).replace(tzinfo=tzoffset(None, 4 * 60 * 60))
|
|
44
|
+
)
|
|
45
|
+
with self.assertRaises(forms.ValidationError):
|
|
46
|
+
field.clean('2015-01-01T16:30+A')
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
from __future__ import absolute_import, print_function, unicode_literals
|
|
2
|
+
import unittest
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
import mock
|
|
6
|
+
import six
|
|
7
|
+
from django import forms
|
|
8
|
+
from rest_framework import serializers
|
|
9
|
+
|
|
10
|
+
from ...forms.serializer_form import (
|
|
11
|
+
SERIALIZER_FORM_FIELD_MAPPING,
|
|
12
|
+
SerializerForm,
|
|
13
|
+
SerializerFormBase,
|
|
14
|
+
SerializerFormMeta,
|
|
15
|
+
SerializerFormOptions,
|
|
16
|
+
form_from_serializer,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
TESTING_MODULE = 'drf_braces.forms.serializer_form'
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class TestSerializer(serializers.Serializer):
|
|
24
|
+
foo = serializers.CharField(read_only=True)
|
|
25
|
+
bar = serializers.IntegerField(min_value=0)
|
|
26
|
+
exclude = serializers.DateTimeField()
|
|
27
|
+
|
|
28
|
+
def validate_bar(self, value):
|
|
29
|
+
if value >= 1000:
|
|
30
|
+
raise serializers.ValidationError('Serializer failure')
|
|
31
|
+
return value
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class TestForm(SerializerForm):
|
|
35
|
+
class Meta(object):
|
|
36
|
+
serializer = TestSerializer
|
|
37
|
+
fields = ['bar', 'foo']
|
|
38
|
+
exclude = ['exclude']
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class TestSerializerFormOptions(unittest.TestCase):
|
|
42
|
+
def test_init(self):
|
|
43
|
+
_options = mock.Mock(serializer=serializers.Serializer)
|
|
44
|
+
|
|
45
|
+
options = SerializerFormOptions(_options)
|
|
46
|
+
|
|
47
|
+
self.assertEqual(options.serializer, _options.serializer)
|
|
48
|
+
self.assertEqual(options.fields, _options.fields)
|
|
49
|
+
self.assertEqual(options.exclude, _options.exclude)
|
|
50
|
+
self.assertEqual(options.field_mapping, _options.field_mapping)
|
|
51
|
+
|
|
52
|
+
def test_init_no_serializer(self):
|
|
53
|
+
with self.assertRaises(AssertionError):
|
|
54
|
+
SerializerFormOptions(None)
|
|
55
|
+
|
|
56
|
+
def test_init_not_drf_serializer(self):
|
|
57
|
+
with self.assertRaises(AssertionError):
|
|
58
|
+
SerializerFormOptions(mock.Mock(serializer=int))
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class TestSerializerFormMeta(unittest.TestCase):
|
|
62
|
+
@mock.patch.dict(SERIALIZER_FORM_FIELD_MAPPING, {'base': 'mapping'}, clear=True)
|
|
63
|
+
def test_get_field_mapping(self):
|
|
64
|
+
class Base1B(object):
|
|
65
|
+
class _meta(object):
|
|
66
|
+
field_mapping = {
|
|
67
|
+
'hello': 'mars',
|
|
68
|
+
'hi': 'there',
|
|
69
|
+
'foo': 'bar',
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
class Base1A(Base1B):
|
|
73
|
+
class _meta(object):
|
|
74
|
+
field_mapping = {
|
|
75
|
+
'hello': 'world',
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
class Base2(object):
|
|
79
|
+
class _meta(object):
|
|
80
|
+
field_mapping = {
|
|
81
|
+
'hello': 'sun',
|
|
82
|
+
'something': 'here',
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
class Options(object):
|
|
86
|
+
field_mapping = {
|
|
87
|
+
'foo': 'baar',
|
|
88
|
+
'happy': 'rainbows',
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
mapping = SerializerFormMeta.get_field_mapping((Base1A, Base2), Options)
|
|
92
|
+
|
|
93
|
+
self.assertDictEqual(mapping, {
|
|
94
|
+
'hi': 'there',
|
|
95
|
+
'hello': 'world',
|
|
96
|
+
'something': 'here',
|
|
97
|
+
'base': 'mapping',
|
|
98
|
+
'foo': 'baar',
|
|
99
|
+
'happy': 'rainbows',
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
def test_get_form_fields_from_serializer(self):
|
|
103
|
+
fields = SerializerFormMeta.get_form_fields_from_serializer(
|
|
104
|
+
(object,), mock.Mock(serializer=TestSerializer,
|
|
105
|
+
fields=['foo', 'bar'],
|
|
106
|
+
exclude=['exclude'],
|
|
107
|
+
field_mapping={})
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
self.assertSetEqual(set(fields.keys()), {'bar'})
|
|
111
|
+
self.assertIsInstance(fields['bar'], forms.IntegerField)
|
|
112
|
+
|
|
113
|
+
def test_get_form_fields_from_serializer_unmapped_field(self):
|
|
114
|
+
class CustomField(serializers.Field):
|
|
115
|
+
pass
|
|
116
|
+
|
|
117
|
+
class MySerializer(serializers.Serializer):
|
|
118
|
+
foo = serializers.CharField(read_only=True)
|
|
119
|
+
bar = CustomField()
|
|
120
|
+
|
|
121
|
+
with self.assertRaises(TypeError):
|
|
122
|
+
SerializerFormMeta.get_form_fields_from_serializer(
|
|
123
|
+
(object,), mock.Mock(serializer=MySerializer,
|
|
124
|
+
fields=['foo', 'bar'],
|
|
125
|
+
exclude=[],
|
|
126
|
+
field_mapping={})
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
@mock.patch(TESTING_MODULE + '.SerializerFormOptions')
|
|
130
|
+
def test_new_no_parents(self, mock_serializer_form_options):
|
|
131
|
+
class TestForm(six.with_metaclass(SerializerFormMeta, forms.Form)):
|
|
132
|
+
pass
|
|
133
|
+
|
|
134
|
+
self.assertFalse(mock_serializer_form_options.called)
|
|
135
|
+
|
|
136
|
+
def test_new(self):
|
|
137
|
+
class TestForm(six.with_metaclass(SerializerFormMeta, SerializerForm)):
|
|
138
|
+
class Meta(object):
|
|
139
|
+
serializer = TestSerializer
|
|
140
|
+
fields = ['bar', 'foo']
|
|
141
|
+
exclude = ['exclude']
|
|
142
|
+
|
|
143
|
+
self.assertIsInstance(TestForm._meta, SerializerFormOptions)
|
|
144
|
+
self.assertSetEqual(set(TestForm.base_fields.keys()), {'bar'})
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class TestSerializerFormBase(unittest.TestCase):
|
|
148
|
+
def test_init(self):
|
|
149
|
+
self.assertIsNone(SerializerFormBase().serializer)
|
|
150
|
+
|
|
151
|
+
def test_get_serializer_context(self):
|
|
152
|
+
self.assertDictEqual(SerializerFormBase().get_serializer_context(), {})
|
|
153
|
+
|
|
154
|
+
def test_get_serializer_data(self):
|
|
155
|
+
form = SerializerFormBase()
|
|
156
|
+
form.cleaned_data = {'hello': 'world'}
|
|
157
|
+
form.initial = {'stuff': 'here', 'hello': 'mars'}
|
|
158
|
+
|
|
159
|
+
self.assertDictEqual(form.get_serializer_data(), {
|
|
160
|
+
'hello': 'world',
|
|
161
|
+
'stuff': 'here',
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
@mock.patch.object(SerializerFormBase, 'get_serializer_data')
|
|
165
|
+
@mock.patch.object(SerializerFormBase, 'get_serializer_context')
|
|
166
|
+
def test_get_serializer(self, mock_get_serializer_context, mock_get_serializer_data):
|
|
167
|
+
form = SerializerFormBase()
|
|
168
|
+
mock_serializer = mock.Mock()
|
|
169
|
+
form._meta = mock.Mock(serializer=mock_serializer)
|
|
170
|
+
|
|
171
|
+
actual = form.get_serializer()
|
|
172
|
+
|
|
173
|
+
self.assertEqual(actual, mock_serializer.return_value)
|
|
174
|
+
mock_serializer.assert_called_once_with(
|
|
175
|
+
data=mock_get_serializer_data.return_value,
|
|
176
|
+
context=mock_get_serializer_context.return_value,
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
@mock.patch.object(SerializerFormBase, 'get_serializer')
|
|
180
|
+
def test_clean_form_valid(self, mock_get_serializer):
|
|
181
|
+
class TestForm(SerializerFormBase):
|
|
182
|
+
hello = forms.CharField()
|
|
183
|
+
|
|
184
|
+
form = TestForm(data={'hello': 'world'})
|
|
185
|
+
mock_get_serializer.return_value.is_valid.return_value = True
|
|
186
|
+
mock_get_serializer.return_value.validated_data = {
|
|
187
|
+
'and': 'mars'
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
self.assertTrue(form.is_valid(), form.errors)
|
|
191
|
+
self.assertEqual(form.cleaned_data, {
|
|
192
|
+
'hello': 'world',
|
|
193
|
+
'and': 'mars',
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
@mock.patch.object(SerializerFormBase, 'get_serializer')
|
|
197
|
+
def test_clean_form_invalid(self, mock_get_serializer):
|
|
198
|
+
form = SerializerFormBase(data={})
|
|
199
|
+
mock_get_serializer.return_value.is_valid.return_value = False
|
|
200
|
+
mock_get_serializer.return_value.errors = {
|
|
201
|
+
'field': ['error'],
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
self.assertFalse(form.is_valid(), form.errors)
|
|
205
|
+
|
|
206
|
+
self.assertEqual(form.errors, {'field': ['error']})
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class TestSerializerForm(unittest.TestCase):
|
|
210
|
+
def test_full_clean_valid(self):
|
|
211
|
+
data = {
|
|
212
|
+
'foo': 'anything',
|
|
213
|
+
'bar': '500',
|
|
214
|
+
'exclude': '2015-01-01T12:00'
|
|
215
|
+
}
|
|
216
|
+
initial = {
|
|
217
|
+
'exclude': datetime(2016, 1, 1, 16, 30)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
form = TestForm(data=data, initial=initial)
|
|
221
|
+
|
|
222
|
+
self.assertTrue(form.is_valid(), dict(form.errors))
|
|
223
|
+
self.assertDictEqual(form.cleaned_data, {
|
|
224
|
+
'bar': 500,
|
|
225
|
+
'exclude': datetime(2016, 1, 1, 16, 30)
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
def test_full_clean_invalid(self):
|
|
229
|
+
data = {
|
|
230
|
+
'foo': 'anything',
|
|
231
|
+
'bar': '1000',
|
|
232
|
+
'exclude': '2015-01-01T12:00'
|
|
233
|
+
}
|
|
234
|
+
initial = {
|
|
235
|
+
'exclude': datetime(2016, 1, 1, 16, 30)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
form = TestForm(data=data, initial=initial)
|
|
239
|
+
|
|
240
|
+
self.assertFalse(form.is_valid())
|
|
241
|
+
self.assertIn('bar', form.errors)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
class TestUtils(unittest.TestCase):
|
|
245
|
+
def test_form_from_serializer_not_serializer(self):
|
|
246
|
+
with self.assertRaises(AssertionError):
|
|
247
|
+
form_from_serializer(None)
|
|
248
|
+
|
|
249
|
+
def test_form_from_serializer(self):
|
|
250
|
+
form = form_from_serializer(TestSerializer, fields=['foo', 'bar'], exclude=['exclude'])
|
|
251
|
+
|
|
252
|
+
self.assertTrue(issubclass(form, SerializerForm))
|
|
253
|
+
self.assertIsInstance(form._meta, SerializerFormOptions)
|
|
254
|
+
self.assertListEqual(form._meta.fields, ['foo', 'bar'])
|
|
255
|
+
self.assertListEqual(form._meta.exclude, ['exclude'])
|
|
256
|
+
self.assertIs(form._meta.serializer, TestSerializer)
|
|
File without changes
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
from __future__ import absolute_import, print_function, unicode_literals
|
|
2
|
+
import unittest
|
|
3
|
+
|
|
4
|
+
import mock
|
|
5
|
+
import six
|
|
6
|
+
from rest_framework import fields, serializers
|
|
7
|
+
|
|
8
|
+
from ...serializers.enforce_validation_serializer import (
|
|
9
|
+
EnforceValidationFieldMixin,
|
|
10
|
+
_create_enforce_validation_serializer,
|
|
11
|
+
add_base_class_to_instance,
|
|
12
|
+
create_enforce_validation_serializer,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
TESTING_MODULE = 'drf_braces.serializers.enforce_validation_serializer'
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class InnerSerializer(serializers.Serializer):
|
|
20
|
+
field = fields.IntegerField()
|
|
21
|
+
field2 = fields.IntegerField()
|
|
22
|
+
must_validate_fields = ['field']
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class TestSerializer(serializers.Serializer):
|
|
26
|
+
field = fields.IntegerField()
|
|
27
|
+
field2 = fields.IntegerField()
|
|
28
|
+
inner = InnerSerializer()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class TestManySerializer(serializers.Serializer):
|
|
32
|
+
many = TestSerializer(many=True)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class CaptureFailedFieldValidationFieldMixin(EnforceValidationFieldMixin):
|
|
36
|
+
|
|
37
|
+
def capture_failed_field(self, field_name, field_data, error_msg):
|
|
38
|
+
self._failed_validation = {field_name: (field_data, error_msg)}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class TestEnforceValidationFieldMixin(unittest.TestCase):
|
|
42
|
+
class Field(EnforceValidationFieldMixin, fields.IntegerField):
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
class CaptureFailedField(CaptureFailedFieldValidationFieldMixin, fields.TimeField):
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
def test_run_validation_must_validate(self):
|
|
49
|
+
field = self.Field()
|
|
50
|
+
field.field_name = 'field'
|
|
51
|
+
field.parent = mock.MagicMock(must_validate_fields=None)
|
|
52
|
+
|
|
53
|
+
self.assertEqual(field.run_validation('5'), 5)
|
|
54
|
+
|
|
55
|
+
def test_run_validation_must_validate_all(self):
|
|
56
|
+
field = self.Field()
|
|
57
|
+
field.field_name = 'field'
|
|
58
|
+
field.parent = mock.MagicMock(must_validate_fields=None)
|
|
59
|
+
|
|
60
|
+
with self.assertRaises(serializers.ValidationError):
|
|
61
|
+
field.run_validation('hello')
|
|
62
|
+
|
|
63
|
+
def test_run_validation_must_validate_invalid(self):
|
|
64
|
+
field = self.Field()
|
|
65
|
+
field.field_name = 'field'
|
|
66
|
+
field.parent = mock.MagicMock(must_validate_fields=['field'])
|
|
67
|
+
|
|
68
|
+
with self.assertRaises(serializers.ValidationError):
|
|
69
|
+
field.run_validation('hello')
|
|
70
|
+
|
|
71
|
+
def test_run_validation_must_validate_ignore(self):
|
|
72
|
+
field = self.Field()
|
|
73
|
+
field.field_name = 'field'
|
|
74
|
+
field.parent = mock.MagicMock(must_validate_fields=[])
|
|
75
|
+
|
|
76
|
+
with self.assertRaises(serializers.SkipField):
|
|
77
|
+
field.run_validation('hello')
|
|
78
|
+
|
|
79
|
+
def test_run_validation_must_validate_ignore_capture(self):
|
|
80
|
+
field = self.CaptureFailedField()
|
|
81
|
+
field.field_name = 'field'
|
|
82
|
+
field.parent = mock.MagicMock(must_validate_fields=[''])
|
|
83
|
+
|
|
84
|
+
with self.assertRaises(serializers.SkipField):
|
|
85
|
+
field.run_validation('Bad Time')
|
|
86
|
+
|
|
87
|
+
self.assertEqual('Bad Time', field._failed_validation['field'][0])
|
|
88
|
+
self.assertIn('Time has wrong format. Use one of these formats instead', six.text_type(field._failed_validation['field'][1]))
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class TestUtils(unittest.TestCase):
|
|
92
|
+
def test_add_base_class_to_instance(self):
|
|
93
|
+
obj = fields.IntegerField(max_value=100)
|
|
94
|
+
|
|
95
|
+
self.assertNotIsInstance(obj, EnforceValidationFieldMixin)
|
|
96
|
+
|
|
97
|
+
new_obj = add_base_class_to_instance(obj, EnforceValidationFieldMixin)
|
|
98
|
+
|
|
99
|
+
self.assertIsInstance(new_obj, EnforceValidationFieldMixin)
|
|
100
|
+
self.assertEqual(vars(obj), vars(new_obj))
|
|
101
|
+
|
|
102
|
+
def test__create_enforce_validation_serializer_instance(self):
|
|
103
|
+
new_serializer_class = _create_enforce_validation_serializer(
|
|
104
|
+
TestManySerializer, strict_mode_by_default=False
|
|
105
|
+
)
|
|
106
|
+
new_serializer = new_serializer_class(data={
|
|
107
|
+
'many': [
|
|
108
|
+
{
|
|
109
|
+
'inner': {
|
|
110
|
+
'field': '5',
|
|
111
|
+
'field2': 'hello',
|
|
112
|
+
},
|
|
113
|
+
'field': 'hello',
|
|
114
|
+
'field2': 'world',
|
|
115
|
+
},
|
|
116
|
+
]
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
self.assertIsInstance(
|
|
120
|
+
new_serializer.fields['many'].child.fields['field'],
|
|
121
|
+
EnforceValidationFieldMixin
|
|
122
|
+
)
|
|
123
|
+
self.assertIsInstance(
|
|
124
|
+
new_serializer.fields['many'].child.fields['inner'].fields['field'],
|
|
125
|
+
EnforceValidationFieldMixin
|
|
126
|
+
)
|
|
127
|
+
self.assertListEqual(
|
|
128
|
+
new_serializer.fields['many'].child.must_validate_fields,
|
|
129
|
+
[]
|
|
130
|
+
)
|
|
131
|
+
self.assertEqual(
|
|
132
|
+
new_serializer.fields['many'].child.fields['inner'].must_validate_fields,
|
|
133
|
+
['field']
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
self.assertTrue(new_serializer.is_valid(), new_serializer.errors)
|
|
137
|
+
self.assertDictEqual(new_serializer.validated_data, {
|
|
138
|
+
'many': [
|
|
139
|
+
{
|
|
140
|
+
'inner': {
|
|
141
|
+
'field': 5,
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
]
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
@mock.patch(TESTING_MODULE + '._create_enforce_validation_serializer')
|
|
148
|
+
def test_create_enforce_validation_serializer_direct_decorator(
|
|
149
|
+
self, mock_create_enforce_validation_serializer):
|
|
150
|
+
@create_enforce_validation_serializer
|
|
151
|
+
class OtherSerializer(TestSerializer):
|
|
152
|
+
pass
|
|
153
|
+
|
|
154
|
+
self.assertEqual(OtherSerializer, mock_create_enforce_validation_serializer.return_value)
|
|
155
|
+
mock_create_enforce_validation_serializer.assert_called_once_with(mock.ANY)
|
|
156
|
+
|
|
157
|
+
@mock.patch(TESTING_MODULE + '._create_enforce_validation_serializer')
|
|
158
|
+
def test_create_enforce_validation_serializer_decorator_params(
|
|
159
|
+
self, mock_create_enforce_validation_serializer):
|
|
160
|
+
@create_enforce_validation_serializer(foo='bar')
|
|
161
|
+
class OtherSerializer(TestSerializer):
|
|
162
|
+
pass
|
|
163
|
+
|
|
164
|
+
self.assertEqual(OtherSerializer, mock_create_enforce_validation_serializer.return_value)
|
|
165
|
+
mock_create_enforce_validation_serializer.assert_called_once_with(mock.ANY, foo='bar')
|
|
166
|
+
|
|
167
|
+
def test_create_enforce_validation_serializer_invalid(self):
|
|
168
|
+
with self.assertRaises(TypeError):
|
|
169
|
+
create_enforce_validation_serializer(5)
|