simo 1.7.19__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__/on_http_start.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__/__init__.cpython-38.pyc +0 -0
- simo/core/__pycache__/admin.cpython-38.pyc +0 -0
- simo/core/__pycache__/api.cpython-38.pyc +0 -0
- simo/core/__pycache__/api_meta.cpython-38.pyc +0 -0
- simo/core/__pycache__/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__/filters.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__/routing.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__/todos.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/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/core/management/commands/__pycache__/__init__.cpython-38.pyc +0 -0
- 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 +30 -23
- simo/generic/gateways.py +98 -10
- 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.19.dist-info → simo-2.0.0.dist-info}/METADATA +8 -9
- {simo-1.7.19.dist-info → simo-2.0.0.dist-info}/RECORD +180 -210
- {simo-1.7.19.dist-info → simo-2.0.0.dist-info}/WHEEL +1 -1
- simo/core/__pycache__/__init__.cpython-37.pyc +0 -0
- simo/core/__pycache__/admin.cpython-37.pyc +0 -0
- simo/core/__pycache__/api.cpython-37.pyc +0 -0
- simo/core/__pycache__/apps.cpython-38.pyc +0 -0
- simo/core/__pycache__/controllers.cpython-37.pyc +0 -0
- simo/core/__pycache__/events.cpython-37.pyc +0 -0
- simo/core/__pycache__/forms.cpython-37.pyc +0 -0
- simo/core/__pycache__/gateways.cpython-37.pyc +0 -0
- simo/core/__pycache__/models.cpython-37.pyc +0 -0
- simo/core/__pycache__/scripts.cpython-37.pyc +0 -0
- simo/core/__pycache__/serializers.cpython-37.pyc +0 -0
- simo/core/__pycache__/widgets.cpython-37.pyc +0 -0
- 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/management/commands/__pycache__/run_gateway.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__/tasks.cpython-38.pyc +0 -0
- simo/generic/__pycache__/widgets.cpython-37.pyc +0 -0
- simo/generic/tasks.py +0 -41
- 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.19.dist-info → simo-2.0.0.dist-info}/LICENSE.md +0 -0
- {simo-1.7.19.dist-info → simo-2.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
from __future__ import absolute_import, print_function, unicode_literals
|
|
2
|
+
import unittest
|
|
3
|
+
from collections import OrderedDict
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
|
|
6
|
+
import mock
|
|
7
|
+
import six
|
|
8
|
+
from django import forms
|
|
9
|
+
from rest_framework import fields, serializers
|
|
10
|
+
|
|
11
|
+
from ...serializers.form_serializer import (
|
|
12
|
+
FormSerializer,
|
|
13
|
+
FormSerializerBase,
|
|
14
|
+
FormSerializerFieldMixin,
|
|
15
|
+
FormSerializerMeta,
|
|
16
|
+
FormSerializerOptions,
|
|
17
|
+
LazyLoadingValidationsMixin,
|
|
18
|
+
make_form_serializer_field,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
TESTING_MODULE = 'drf_braces.serializers.form_serializer'
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class TestForm(forms.Form):
|
|
26
|
+
foo = forms.CharField(max_length=12)
|
|
27
|
+
bar = forms.IntegerField(max_value=500)
|
|
28
|
+
happy = forms.ChoiceField(required=False)
|
|
29
|
+
other = forms.DateTimeField()
|
|
30
|
+
|
|
31
|
+
def __init__(self, *args, **kwargs):
|
|
32
|
+
super(TestForm, self).__init__(*args, **kwargs)
|
|
33
|
+
self.fields['happy'].choices = [
|
|
34
|
+
('happy', 'choices'),
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
def clean(self):
|
|
38
|
+
data = super(TestForm, self).clean()
|
|
39
|
+
data.update({
|
|
40
|
+
'bar': 257,
|
|
41
|
+
})
|
|
42
|
+
return data
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class CaptureFailedFieldValidationFieldMixin(FormSerializerFieldMixin):
|
|
46
|
+
|
|
47
|
+
def capture_failed_field(self, field_name, field_data, error_msg):
|
|
48
|
+
self._failed_validation = {field_name: (field_data, error_msg)}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class TestFormSerializerFieldMixin(unittest.TestCase):
|
|
52
|
+
def setUp(self):
|
|
53
|
+
super(TestFormSerializerFieldMixin, self).setUp()
|
|
54
|
+
|
|
55
|
+
class Field(FormSerializerFieldMixin, fields.IntegerField):
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
class CaptureFailedField(CaptureFailedFieldValidationFieldMixin, fields.TimeField):
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
class Serializer(serializers.Serializer):
|
|
62
|
+
field = Field()
|
|
63
|
+
field_two = CaptureFailedField()
|
|
64
|
+
|
|
65
|
+
class Meta(object):
|
|
66
|
+
minimum_required = []
|
|
67
|
+
failure_mode = 'fail'
|
|
68
|
+
|
|
69
|
+
self.serializer = Serializer()
|
|
70
|
+
self.field = self.serializer.fields['field']
|
|
71
|
+
|
|
72
|
+
def test_run_validation_invalid(self):
|
|
73
|
+
with self.assertRaises(fields.ValidationError):
|
|
74
|
+
self.field.run_validation('a')
|
|
75
|
+
|
|
76
|
+
def test_run_validation_invalid_failure_mode(self):
|
|
77
|
+
self.serializer.partial = True
|
|
78
|
+
|
|
79
|
+
with self.assertRaises(fields.ValidationError):
|
|
80
|
+
self.field.run_validation('a')
|
|
81
|
+
|
|
82
|
+
def test_run_validation_invalid_required_field(self):
|
|
83
|
+
self.serializer.partial = True
|
|
84
|
+
self.serializer.Meta.failure_mode = 'drop'
|
|
85
|
+
self.serializer.Meta.minimum_required = ['field']
|
|
86
|
+
|
|
87
|
+
with self.assertRaises(fields.ValidationError):
|
|
88
|
+
self.field.run_validation('a')
|
|
89
|
+
|
|
90
|
+
def test_run_validation_skip(self):
|
|
91
|
+
self.serializer.partial = True
|
|
92
|
+
self.serializer.Meta.failure_mode = 'drop'
|
|
93
|
+
self.serializer.Meta.minimum_required = []
|
|
94
|
+
|
|
95
|
+
with self.assertRaises(fields.SkipField):
|
|
96
|
+
self.field.run_validation('a')
|
|
97
|
+
|
|
98
|
+
def test_run_validation_skip_capture(self):
|
|
99
|
+
self.serializer.partial = True
|
|
100
|
+
self.serializer.Meta.failure_mode = 'drop'
|
|
101
|
+
self.serializer.Meta.minimum_required = []
|
|
102
|
+
self.field_two = self.serializer.fields['field_two']
|
|
103
|
+
|
|
104
|
+
with self.assertRaises(fields.SkipField):
|
|
105
|
+
self.field_two.run_validation('Really Bad Time')
|
|
106
|
+
|
|
107
|
+
self.assertEqual('Really Bad Time', self.field_two._failed_validation['field_two'][0])
|
|
108
|
+
self.assertIn('Time has wrong format. Use one of these formats instead', six.text_type(self.field_two._failed_validation['field_two'][1]))
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class TestUtils(unittest.TestCase):
|
|
112
|
+
def test_make_form_serializer_field(self):
|
|
113
|
+
field_class = make_form_serializer_field(fields.IntegerField)
|
|
114
|
+
|
|
115
|
+
self.assertEqual(field_class.__name__, 'IntegerFormSerializerField')
|
|
116
|
+
self.assertTrue(issubclass(field_class, FormSerializerFieldMixin))
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class TestFormSerializerOptions(unittest.TestCase):
|
|
120
|
+
def test_init(self):
|
|
121
|
+
meta = mock.Mock(failure_mode='fail', foo='bar')
|
|
122
|
+
|
|
123
|
+
options = FormSerializerOptions(meta, 'foo')
|
|
124
|
+
|
|
125
|
+
self.assertEqual(options.form, meta.form)
|
|
126
|
+
self.assertEqual(options.failure_mode, 'fail')
|
|
127
|
+
self.assertEqual(options.minimum_required, meta.minimum_required)
|
|
128
|
+
self.assertEqual(options.field_mapping, meta.field_mapping)
|
|
129
|
+
self.assertEqual(options.foo, meta.foo)
|
|
130
|
+
|
|
131
|
+
def test_init_invalid(self):
|
|
132
|
+
with self.assertRaises(AssertionError):
|
|
133
|
+
FormSerializerOptions(mock.Mock(failure_mode='foo'), 'foo')
|
|
134
|
+
with self.assertRaises(AssertionError):
|
|
135
|
+
FormSerializerOptions(mock.Mock(form=None), 'foo')
|
|
136
|
+
with self.assertRaises(NotImplementedError):
|
|
137
|
+
FormSerializerOptions(mock.Mock(failure_mode='ignore'), 'foo')
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class TestFormSerializerMeta(unittest.TestCase):
|
|
141
|
+
@mock.patch(TESTING_MODULE + '.FormSerializerOptions')
|
|
142
|
+
def test_no_parents(self, mock_form_serializer_options):
|
|
143
|
+
class TestSerializer(six.with_metaclass(FormSerializerMeta, serializers.Serializer)):
|
|
144
|
+
pass
|
|
145
|
+
|
|
146
|
+
self.assertFalse(mock_form_serializer_options.called)
|
|
147
|
+
|
|
148
|
+
def test_no_meta(self):
|
|
149
|
+
with self.assertRaises(AssertionError):
|
|
150
|
+
class TestSerializer(six.with_metaclass(FormSerializerMeta, FormSerializer)):
|
|
151
|
+
pass
|
|
152
|
+
|
|
153
|
+
def test_new(self):
|
|
154
|
+
class TestSerializer(six.with_metaclass(FormSerializerMeta, FormSerializer)):
|
|
155
|
+
class Meta(object):
|
|
156
|
+
form = TestForm
|
|
157
|
+
|
|
158
|
+
self.assertIsInstance(TestSerializer.Meta, FormSerializerOptions)
|
|
159
|
+
self.assertIs(TestSerializer.Meta.form, TestForm)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class TestFormSerializerBase(unittest.TestCase):
|
|
163
|
+
def setUp(self):
|
|
164
|
+
super(TestFormSerializerBase, self).setUp()
|
|
165
|
+
|
|
166
|
+
class Serializer(FormSerializerBase):
|
|
167
|
+
other = fields.CharField()
|
|
168
|
+
|
|
169
|
+
class Meta(object):
|
|
170
|
+
failure_mode = 'drop'
|
|
171
|
+
form = TestForm
|
|
172
|
+
minimum_required = ['foo']
|
|
173
|
+
field_mapping = {}
|
|
174
|
+
|
|
175
|
+
def capture_failed_fields(self, raw_data, form_errors):
|
|
176
|
+
self._failed_validation = {k: v for k, v in raw_data.items() if k in form_errors}
|
|
177
|
+
|
|
178
|
+
self.serializer_class = Serializer
|
|
179
|
+
|
|
180
|
+
def test_init(self):
|
|
181
|
+
serializer = self.serializer_class()
|
|
182
|
+
|
|
183
|
+
self.assertTrue(serializer.partial)
|
|
184
|
+
|
|
185
|
+
def test_get_form(self):
|
|
186
|
+
serializer = self.serializer_class()
|
|
187
|
+
|
|
188
|
+
form = serializer.get_form()
|
|
189
|
+
|
|
190
|
+
self.assertIsInstance(form, TestForm)
|
|
191
|
+
self.assertIn('foo', form.fields)
|
|
192
|
+
self.assertIn('bar', form.fields)
|
|
193
|
+
self.assertIn('other', form.fields)
|
|
194
|
+
self.assertTrue(form.fields['foo'].required)
|
|
195
|
+
self.assertFalse(form.fields['bar'].required)
|
|
196
|
+
self.assertFalse(form.fields['other'].required)
|
|
197
|
+
|
|
198
|
+
def test_get_fields(self):
|
|
199
|
+
serializer = self.serializer_class()
|
|
200
|
+
serializer.Meta.field_mapping.update({
|
|
201
|
+
forms.CharField: fields.BooleanField,
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
serializer_fields = serializer.get_fields()
|
|
205
|
+
|
|
206
|
+
self.assertIsInstance(serializer_fields, OrderedDict)
|
|
207
|
+
self.assertIn('foo', serializer_fields)
|
|
208
|
+
self.assertIn('bar', serializer_fields)
|
|
209
|
+
self.assertIn('other', serializer_fields)
|
|
210
|
+
self.assertIsInstance(serializer_fields['foo'], fields.BooleanField)
|
|
211
|
+
self.assertIsInstance(serializer_fields['bar'], fields.IntegerField)
|
|
212
|
+
self.assertIsInstance(serializer_fields['other'], fields.CharField)
|
|
213
|
+
|
|
214
|
+
def test_get_fields_excluded(self):
|
|
215
|
+
serializer = self.serializer_class()
|
|
216
|
+
serializer.Meta.exclude = ['foo']
|
|
217
|
+
serializer.Meta.field_mapping.update({
|
|
218
|
+
forms.CharField: fields.BooleanField,
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
serializer_fields = serializer.get_fields()
|
|
222
|
+
|
|
223
|
+
self.assertIsInstance(serializer_fields, OrderedDict)
|
|
224
|
+
self.assertNotIn('foo', serializer_fields)
|
|
225
|
+
|
|
226
|
+
def test_get_fields_not_mapped(self):
|
|
227
|
+
serializer = self.serializer_class()
|
|
228
|
+
|
|
229
|
+
class FooField(forms.Field):
|
|
230
|
+
pass
|
|
231
|
+
|
|
232
|
+
class FooForm(TestForm):
|
|
233
|
+
stuff = FooField()
|
|
234
|
+
|
|
235
|
+
serializer.Meta.form = FooForm
|
|
236
|
+
|
|
237
|
+
with self.assertRaises(TypeError):
|
|
238
|
+
serializer.get_fields()
|
|
239
|
+
|
|
240
|
+
def test_get_field(self):
|
|
241
|
+
serializer = self.serializer_class()
|
|
242
|
+
form_field = forms.ChoiceField(
|
|
243
|
+
choices=[('foo', 'bar')],
|
|
244
|
+
required=False,
|
|
245
|
+
validators=[mock.sentinel.validator],
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
field = serializer._get_field(form_field, fields.ChoiceField)
|
|
249
|
+
|
|
250
|
+
self.assertIsInstance(field, fields.ChoiceField)
|
|
251
|
+
self.assertTrue(field.allow_blank)
|
|
252
|
+
self.assertTrue(field.allow_null)
|
|
253
|
+
self.assertListEqual(field.validators, [mock.sentinel.validator])
|
|
254
|
+
self.assertDictEqual(field.choice_strings_to_values, {
|
|
255
|
+
'foo': 'foo',
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
def test_get_field_kwargs(self):
|
|
259
|
+
serializer = self.serializer_class()
|
|
260
|
+
form_field = forms.IntegerField(
|
|
261
|
+
max_value=500,
|
|
262
|
+
initial=100,
|
|
263
|
+
required=True,
|
|
264
|
+
validators=[mock.sentinel.validator],
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
kwargs = serializer._get_field_kwargs(form_field, fields.IntegerField)
|
|
268
|
+
|
|
269
|
+
self.assertDictContainsSubset({
|
|
270
|
+
'default': 100,
|
|
271
|
+
'validators': [mock.sentinel.validator, mock.ANY],
|
|
272
|
+
}, kwargs)
|
|
273
|
+
self.assertNotIn('required', kwargs)
|
|
274
|
+
|
|
275
|
+
def test_get_field_kwargs_choice_field(self):
|
|
276
|
+
serializer = self.serializer_class()
|
|
277
|
+
form_field = forms.ChoiceField(
|
|
278
|
+
choices=[('foo', 'FOO'), ('bar', 'BAR')]
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
kwargs = serializer._get_field_kwargs(form_field, fields.ChoiceField)
|
|
282
|
+
|
|
283
|
+
self.assertDictContainsSubset({
|
|
284
|
+
'choices': OrderedDict([
|
|
285
|
+
('foo', 'foo'),
|
|
286
|
+
('bar', 'bar'),
|
|
287
|
+
]),
|
|
288
|
+
}, kwargs)
|
|
289
|
+
|
|
290
|
+
def test_validate(self):
|
|
291
|
+
serializer = self.serializer_class(data={
|
|
292
|
+
'foo': 'hello',
|
|
293
|
+
'bar': '100',
|
|
294
|
+
'other': 'stuff',
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
self.assertTrue(serializer.is_valid())
|
|
298
|
+
self.assertDictEqual(serializer.validated_data, {
|
|
299
|
+
'foo': 'hello',
|
|
300
|
+
'bar': 257,
|
|
301
|
+
'happy': '',
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
def test_validate_valid(self):
|
|
305
|
+
serializer = self.serializer_class(data={
|
|
306
|
+
'foo': 'hello',
|
|
307
|
+
'bar': '100',
|
|
308
|
+
'other': '2015-01-01 12:30',
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
self.assertTrue(serializer.is_valid())
|
|
312
|
+
self.assertDictEqual(serializer.validated_data, {
|
|
313
|
+
'other': datetime(2015, 1, 1, 12, 30),
|
|
314
|
+
'foo': 'hello',
|
|
315
|
+
'bar': 257,
|
|
316
|
+
'happy': '',
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
def test_validate_fail(self):
|
|
320
|
+
self.serializer_class.Meta.failure_mode = 'fail'
|
|
321
|
+
serializer = self.serializer_class(data={
|
|
322
|
+
'foo': 'hello',
|
|
323
|
+
'bar': '100',
|
|
324
|
+
'other': 'stuff',
|
|
325
|
+
})
|
|
326
|
+
|
|
327
|
+
self.assertFalse(serializer.is_valid())
|
|
328
|
+
self.assertDictEqual(serializer.errors, {
|
|
329
|
+
'other': ['Enter a valid date/time.'],
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
def test_validate_capture_errors(self):
|
|
333
|
+
self.serializer_class.Meta.failure_mode = 'drop'
|
|
334
|
+
serializer = self.serializer_class(data={
|
|
335
|
+
'foo': 'Chime Oduzo',
|
|
336
|
+
'bar': 45,
|
|
337
|
+
'other': 'Extremely bad time'
|
|
338
|
+
})
|
|
339
|
+
self.assertTrue(serializer.is_valid())
|
|
340
|
+
self.assertDictEqual(serializer.validated_data, {
|
|
341
|
+
'foo': 'Chime Oduzo',
|
|
342
|
+
'bar': 257,
|
|
343
|
+
'happy': '',
|
|
344
|
+
})
|
|
345
|
+
self.assertDictEqual({'other': 'Extremely bad time'}, serializer._failed_validation)
|
|
346
|
+
|
|
347
|
+
def test_to_representation(self):
|
|
348
|
+
with self.assertRaises(NotImplementedError):
|
|
349
|
+
self.serializer_class().to_representation({})
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
class TestFormSerializer(unittest.TestCase):
|
|
353
|
+
def test_bases(self):
|
|
354
|
+
self.assertTrue(issubclass(FormSerializer, FormSerializerBase))
|
|
355
|
+
self.assertIsInstance(FormSerializer, FormSerializerMeta)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
class TestLazyLoadingValidationsMixin(unittest.TestCase):
|
|
359
|
+
def setUp(self):
|
|
360
|
+
super(TestLazyLoadingValidationsMixin, self).setUp()
|
|
361
|
+
|
|
362
|
+
class Serializer(LazyLoadingValidationsMixin, FormSerializer):
|
|
363
|
+
class Meta(object):
|
|
364
|
+
form = TestForm
|
|
365
|
+
|
|
366
|
+
self.serializer_class = Serializer
|
|
367
|
+
|
|
368
|
+
def test_repopulate_form_fields(self):
|
|
369
|
+
serializer = self.serializer_class()
|
|
370
|
+
|
|
371
|
+
# sanity check
|
|
372
|
+
self.assertDictEqual(serializer.fields['happy'].choices, {})
|
|
373
|
+
|
|
374
|
+
serializer.repopulate_form_fields()
|
|
375
|
+
|
|
376
|
+
self.assertDictEqual(dict(serializer.fields['happy'].choices), {'happy': 'happy'})
|
|
377
|
+
self.assertDictEqual(dict(serializer.fields['happy'].choice_strings_to_values),
|
|
378
|
+
{'happy': 'happy'})
|
|
379
|
+
|
|
380
|
+
@mock.patch.object(serializers.Serializer, 'to_internal_value')
|
|
381
|
+
@mock.patch.object(LazyLoadingValidationsMixin, 'repopulate_form_fields')
|
|
382
|
+
def test_to_internal_value(self, mock_repopulate_form_fields, mock_super_to_internal_value):
|
|
383
|
+
serializer = self.serializer_class()
|
|
384
|
+
|
|
385
|
+
serializer.to_internal_value({})
|
|
386
|
+
|
|
387
|
+
mock_repopulate_form_fields.assert_called_once_with()
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from __future__ import absolute_import, print_function, unicode_literals
|
|
3
|
+
import unittest
|
|
4
|
+
|
|
5
|
+
from rest_framework import serializers
|
|
6
|
+
|
|
7
|
+
from ...serializers.swapping import SwappingSerializerMixin
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ChildSerializer(serializers.Serializer):
|
|
11
|
+
foo = serializers.IntegerField()
|
|
12
|
+
bar = serializers.CharField()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ChildAlternativeSerializer(serializers.Serializer):
|
|
16
|
+
foo = serializers.CharField()
|
|
17
|
+
bar = serializers.CharField()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ParentSerializer(serializers.Serializer):
|
|
21
|
+
child = ChildSerializer()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class GrandParentSerializer(serializers.Serializer):
|
|
25
|
+
parent = ParentSerializer()
|
|
26
|
+
parents = ParentSerializer(many=True)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class TestSwappingSerializerMixin(unittest.TestCase):
|
|
30
|
+
def test_swapping(self):
|
|
31
|
+
class Swappable(SwappingSerializerMixin, GrandParentSerializer):
|
|
32
|
+
class Meta(object):
|
|
33
|
+
swappable_fields = {
|
|
34
|
+
ChildSerializer: ChildAlternativeSerializer,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
swapped = Swappable()
|
|
38
|
+
|
|
39
|
+
self.assertIsInstance(swapped.fields['parent'].fields['child'], ChildAlternativeSerializer)
|
|
40
|
+
self.assertIsInstance(swapped.fields['parents'].child.fields['child'], ChildAlternativeSerializer)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
from __future__ import absolute_import, print_function, unicode_literals
|
|
2
|
+
import unittest
|
|
3
|
+
|
|
4
|
+
import mock
|
|
5
|
+
from rest_framework.generics import GenericAPIView
|
|
6
|
+
|
|
7
|
+
from ..mixins import (
|
|
8
|
+
MapDataViewMixin,
|
|
9
|
+
MultipleSerializersViewMixin,
|
|
10
|
+
StrippingJSONViewMixin,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TestMultipleSerializersViewMixin(unittest.TestCase):
|
|
15
|
+
def setUp(self):
|
|
16
|
+
super(TestMultipleSerializersViewMixin, self).setUp()
|
|
17
|
+
|
|
18
|
+
class View(MultipleSerializersViewMixin, GenericAPIView):
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
self.view = View()
|
|
22
|
+
|
|
23
|
+
@mock.patch.object(GenericAPIView, 'get_serializer_context')
|
|
24
|
+
@mock.patch.object(GenericAPIView, 'get_serializer_class')
|
|
25
|
+
def test_get_serializer(self,
|
|
26
|
+
mock_get_serializer_class,
|
|
27
|
+
mock_get_serializer_context):
|
|
28
|
+
context = {'context': 'here'}
|
|
29
|
+
mock_get_serializer_context.return_value = context
|
|
30
|
+
|
|
31
|
+
serializer = self.view.get_serializer(hello='world')
|
|
32
|
+
|
|
33
|
+
self.assertEqual(serializer, mock_get_serializer_class.return_value.return_value)
|
|
34
|
+
mock_get_serializer_class.assert_called_once_with()
|
|
35
|
+
mock_get_serializer_class.return_value.assert_called_once_with(
|
|
36
|
+
hello='world', context=context
|
|
37
|
+
)
|
|
38
|
+
mock_get_serializer_context.assert_called_once_with()
|
|
39
|
+
|
|
40
|
+
@mock.patch.object(GenericAPIView, 'get_serializer_context')
|
|
41
|
+
@mock.patch.object(GenericAPIView, 'get_serializer_class')
|
|
42
|
+
def test_get_serializer_with_class(self,
|
|
43
|
+
mock_get_serializer_class,
|
|
44
|
+
mock_get_serializer_context):
|
|
45
|
+
context = {'context': 'here'}
|
|
46
|
+
mock_get_serializer_context.return_value = context
|
|
47
|
+
serializer_class = mock.MagicMock()
|
|
48
|
+
|
|
49
|
+
serializer = self.view.get_serializer(hello='world', serializer_class=serializer_class)
|
|
50
|
+
|
|
51
|
+
self.assertEqual(serializer, serializer_class.return_value)
|
|
52
|
+
self.assertFalse(mock_get_serializer_class.called)
|
|
53
|
+
serializer_class.assert_called_once_with(hello='world', context=context)
|
|
54
|
+
mock_get_serializer_context.assert_called_once_with()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class TestMapDataViewMixin(unittest.TestCase):
|
|
58
|
+
def setUp(self):
|
|
59
|
+
super(TestMapDataViewMixin, self).setUp()
|
|
60
|
+
|
|
61
|
+
class View(MapDataViewMixin, GenericAPIView):
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
self.view = View()
|
|
65
|
+
self.view.request = mock.MagicMock(data=mock.sentinel.data)
|
|
66
|
+
|
|
67
|
+
def test_get_data_no_mapper(self):
|
|
68
|
+
actual = self.view.get_data()
|
|
69
|
+
|
|
70
|
+
self.assertEqual(actual, mock.sentinel.data)
|
|
71
|
+
|
|
72
|
+
@mock.patch.object(GenericAPIView, 'get_serializer_context')
|
|
73
|
+
def test_get_data_attribute_mapper(self, mock_get_serializer_context):
|
|
74
|
+
mapper = self.view.data_mapper_class = mock.MagicMock()
|
|
75
|
+
actual = self.view.get_data()
|
|
76
|
+
|
|
77
|
+
self.assertEqual(actual, mapper.return_value.return_value)
|
|
78
|
+
mapper.assert_called_once_with(
|
|
79
|
+
context=mock_get_serializer_context.return_value
|
|
80
|
+
)
|
|
81
|
+
mapper.return_value.assert_called_once_with(mock.sentinel.data)
|
|
82
|
+
|
|
83
|
+
@mock.patch.object(GenericAPIView, 'get_serializer_context')
|
|
84
|
+
def test_get_data_provided(self, mock_get_serializer_context):
|
|
85
|
+
mapper = mock.MagicMock()
|
|
86
|
+
actual = self.view.get_data(mapper_class=mapper)
|
|
87
|
+
|
|
88
|
+
self.assertEqual(actual, mapper.return_value.return_value)
|
|
89
|
+
mapper.assert_called_once_with(
|
|
90
|
+
context=mock_get_serializer_context.return_value
|
|
91
|
+
)
|
|
92
|
+
mapper.return_value.assert_called_once_with(mock.sentinel.data)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class TestStrippingJSONViewMixin(unittest.TestCase):
|
|
96
|
+
def setUp(self):
|
|
97
|
+
super(TestStrippingJSONViewMixin, self).setUp()
|
|
98
|
+
|
|
99
|
+
class View(StrippingJSONViewMixin, GenericAPIView):
|
|
100
|
+
pass
|
|
101
|
+
|
|
102
|
+
self.view = View()
|
|
103
|
+
self.view.request = mock.MagicMock()
|
|
104
|
+
|
|
105
|
+
def test_get_parser_context(self):
|
|
106
|
+
self.view.parser_root = mock.sentinel.parser_root
|
|
107
|
+
|
|
108
|
+
actual = self.view.get_parser_context(self.view.request)
|
|
109
|
+
|
|
110
|
+
self.assertIn('parse_root', actual)
|
|
111
|
+
self.assertEqual(actual['parse_root'], mock.sentinel.parser_root)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from __future__ import absolute_import, print_function, unicode_literals
|
|
2
|
+
import json
|
|
3
|
+
import unittest
|
|
4
|
+
from collections import OrderedDict
|
|
5
|
+
|
|
6
|
+
import six
|
|
7
|
+
from rest_framework import parsers
|
|
8
|
+
|
|
9
|
+
from ..parsers import SortedJSONParser, StrippingJSONParser
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TestSortedJSONParser(unittest.TestCase):
|
|
13
|
+
def setUp(self):
|
|
14
|
+
super(TestSortedJSONParser, self).setUp()
|
|
15
|
+
self.parser = SortedJSONParser()
|
|
16
|
+
|
|
17
|
+
def test_parser(self):
|
|
18
|
+
content = json.dumps({'hello': 'world'}).encode('utf-8')
|
|
19
|
+
stream = six.BytesIO(content)
|
|
20
|
+
|
|
21
|
+
actual_data = self.parser.parse(stream=stream)
|
|
22
|
+
|
|
23
|
+
self.assertEqual(actual_data, OrderedDict([('hello', 'world')]))
|
|
24
|
+
|
|
25
|
+
def test_parser_invalid_json(self):
|
|
26
|
+
content = (
|
|
27
|
+
json.dumps({'hello': 'world'})
|
|
28
|
+
.replace('"', "'")
|
|
29
|
+
.encode('utf-8')
|
|
30
|
+
)
|
|
31
|
+
stream = six.BytesIO(content)
|
|
32
|
+
|
|
33
|
+
with self.assertRaises(parsers.ParseError):
|
|
34
|
+
self.parser.parse(stream=stream)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class TestStrippingJSONParser(unittest.TestCase):
|
|
38
|
+
def setUp(self):
|
|
39
|
+
super(TestStrippingJSONParser, self).setUp()
|
|
40
|
+
self.parser = StrippingJSONParser()
|
|
41
|
+
|
|
42
|
+
def test_parser(self):
|
|
43
|
+
content = json.dumps({'root': {'hello': 'world'}}).encode('utf-8')
|
|
44
|
+
stream = six.BytesIO(content)
|
|
45
|
+
|
|
46
|
+
actual_data = self.parser.parse(
|
|
47
|
+
stream=stream,
|
|
48
|
+
parser_context={'parse_root': 'root'}
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
self.assertEqual(actual_data, OrderedDict([('hello', 'world')]))
|
|
52
|
+
|
|
53
|
+
def test_parser_no_root(self):
|
|
54
|
+
content = json.dumps({'root': {'hello': 'world'}}).encode('utf-8')
|
|
55
|
+
stream = six.BytesIO(content)
|
|
56
|
+
|
|
57
|
+
actual_data = self.parser.parse(
|
|
58
|
+
stream=stream,
|
|
59
|
+
parser_context={}
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
self.assertEqual(actual_data, {'root': {'hello': 'world'}})
|
|
63
|
+
|
|
64
|
+
def test_parser_different_root(self):
|
|
65
|
+
content = json.dumps({'root': {'hello': 'world'}}).encode('utf-8')
|
|
66
|
+
stream = six.BytesIO(content)
|
|
67
|
+
|
|
68
|
+
actual_data = self.parser.parse(
|
|
69
|
+
stream=stream,
|
|
70
|
+
parser_context={'parse_root': 'foo'}
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
self.assertEqual(actual_data, {'root': {'hello': 'world'}})
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from __future__ import absolute_import, print_function, unicode_literals
|
|
3
|
+
import datetime
|
|
4
|
+
import json
|
|
5
|
+
import unittest
|
|
6
|
+
|
|
7
|
+
from ..renderers import DoubleAsStrJsonEncoder
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TestDoubleAsStrJsonEncoder(unittest.TestCase):
|
|
11
|
+
def test_encode(self):
|
|
12
|
+
self.assertEqual(
|
|
13
|
+
json.loads(json.dumps({
|
|
14
|
+
'a': 12345678901234567890,
|
|
15
|
+
'b': [123],
|
|
16
|
+
'c': datetime.date(2010, 1, 2)
|
|
17
|
+
}, cls=DoubleAsStrJsonEncoder)),
|
|
18
|
+
{
|
|
19
|
+
'a': '12345678901234567890',
|
|
20
|
+
'b': [123],
|
|
21
|
+
'c': '2010-01-02',
|
|
22
|
+
}
|
|
23
|
+
)
|