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
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
from __future__ import absolute_import, print_function, unicode_literals
|
|
2
|
+
import inspect
|
|
3
|
+
|
|
4
|
+
import six
|
|
5
|
+
from django import forms
|
|
6
|
+
from django.forms.forms import DeclarativeFieldsMetaclass
|
|
7
|
+
from rest_framework import serializers
|
|
8
|
+
|
|
9
|
+
from .. import fields
|
|
10
|
+
from ..utils import (
|
|
11
|
+
initialize_class_using_reference_object,
|
|
12
|
+
reduce_attr_dict_from_base_classes,
|
|
13
|
+
)
|
|
14
|
+
from .fields import ISO8601DateTimeField
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
SERIALIZER_FORM_FIELD_MAPPING = {
|
|
18
|
+
fields.BooleanField: forms.BooleanField,
|
|
19
|
+
fields.CharField: forms.CharField,
|
|
20
|
+
fields.ChoiceField: forms.ChoiceField,
|
|
21
|
+
fields.DateTimeField: ISO8601DateTimeField,
|
|
22
|
+
fields.EmailField: forms.EmailField,
|
|
23
|
+
fields.IntegerField: forms.IntegerField,
|
|
24
|
+
fields.UUIDField: forms.UUIDField,
|
|
25
|
+
fields.URLField: forms.URLField,
|
|
26
|
+
fields.DateField: forms.DateField,
|
|
27
|
+
serializers.BooleanField: forms.BooleanField,
|
|
28
|
+
serializers.CharField: forms.CharField,
|
|
29
|
+
serializers.ChoiceField: forms.ChoiceField,
|
|
30
|
+
serializers.DateTimeField: ISO8601DateTimeField,
|
|
31
|
+
serializers.EmailField: forms.EmailField,
|
|
32
|
+
serializers.IntegerField: forms.IntegerField,
|
|
33
|
+
serializers.UUIDField: forms.UUIDField,
|
|
34
|
+
serializers.URLField: forms.URLField,
|
|
35
|
+
serializers.DateField: forms.DateField,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class SerializerFormOptions(object):
|
|
40
|
+
def __init__(self, options=None, name=None):
|
|
41
|
+
self.serializer = getattr(options, 'serializer', None)
|
|
42
|
+
self.fields = getattr(options, 'fields', [])
|
|
43
|
+
self.exclude = getattr(options, 'exclude', [])
|
|
44
|
+
self.field_mapping = getattr(options, 'field_mapping', {})
|
|
45
|
+
|
|
46
|
+
assert self.serializer is not None, (
|
|
47
|
+
'{}.Meta.serializer must be provided'
|
|
48
|
+
''.format(name)
|
|
49
|
+
)
|
|
50
|
+
assert issubclass(self.serializer, serializers.BaseSerializer), (
|
|
51
|
+
'{}.Meta.serializer must be a subclass of DRF serializer'
|
|
52
|
+
''.format(name)
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class SerializerFormMeta(DeclarativeFieldsMetaclass):
|
|
57
|
+
def __new__(cls, name, bases, attrs):
|
|
58
|
+
try:
|
|
59
|
+
parents = [b for b in bases if issubclass(b, SerializerForm)]
|
|
60
|
+
except NameError:
|
|
61
|
+
# We are defining SerializerForm itself
|
|
62
|
+
parents = None
|
|
63
|
+
|
|
64
|
+
meta = attrs.pop('Meta', None)
|
|
65
|
+
|
|
66
|
+
if not parents or attrs.pop('_is_base', False):
|
|
67
|
+
return super(SerializerFormMeta, cls).__new__(cls, name, bases, attrs)
|
|
68
|
+
|
|
69
|
+
attrs['_meta'] = options = SerializerFormOptions(meta, name=name)
|
|
70
|
+
|
|
71
|
+
new_attrs = cls.get_form_fields_from_serializer(bases, options)
|
|
72
|
+
# attrs should take priority in case a specific field is overwritten
|
|
73
|
+
new_attrs.update(attrs)
|
|
74
|
+
|
|
75
|
+
return super(SerializerFormMeta, cls).__new__(cls, name, bases, new_attrs)
|
|
76
|
+
|
|
77
|
+
@classmethod
|
|
78
|
+
def get_field_mapping(cls, bases, options):
|
|
79
|
+
mapping = reduce_attr_dict_from_base_classes(
|
|
80
|
+
bases,
|
|
81
|
+
lambda i: getattr(getattr(i, '_meta', None), 'field_mapping', {}),
|
|
82
|
+
SERIALIZER_FORM_FIELD_MAPPING
|
|
83
|
+
)
|
|
84
|
+
mapping.update(options.field_mapping)
|
|
85
|
+
return mapping
|
|
86
|
+
|
|
87
|
+
@classmethod
|
|
88
|
+
def get_form_fields_from_serializer(cls, bases, options):
|
|
89
|
+
fields = {}
|
|
90
|
+
|
|
91
|
+
mapping = cls.get_field_mapping(bases, options)
|
|
92
|
+
|
|
93
|
+
for name, field in options.serializer._declared_fields.items():
|
|
94
|
+
if field.read_only:
|
|
95
|
+
continue
|
|
96
|
+
|
|
97
|
+
if name not in options.fields or name in options.exclude:
|
|
98
|
+
continue
|
|
99
|
+
|
|
100
|
+
form_field_class = mapping.get(type(field))
|
|
101
|
+
|
|
102
|
+
if not form_field_class:
|
|
103
|
+
raise TypeError(
|
|
104
|
+
'{} is not mapped to appropriate form field class. '
|
|
105
|
+
'Please add it to the mapping via `field_mapping` '
|
|
106
|
+
'Meta attribute.'
|
|
107
|
+
''.format(type(field))
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
fields[name] = initialize_class_using_reference_object(field, form_field_class)
|
|
111
|
+
|
|
112
|
+
return fields
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class SerializerFormBase(forms.Form):
|
|
116
|
+
def __init__(self, *args, **kwargs):
|
|
117
|
+
super(SerializerFormBase, self).__init__(*args, **kwargs)
|
|
118
|
+
# instantiated during validation
|
|
119
|
+
self.serializer = None
|
|
120
|
+
|
|
121
|
+
def get_serializer_context(self):
|
|
122
|
+
return {}
|
|
123
|
+
|
|
124
|
+
def get_serializer_data(self):
|
|
125
|
+
data = self.initial.copy()
|
|
126
|
+
data.update(self.cleaned_data or {})
|
|
127
|
+
return data
|
|
128
|
+
|
|
129
|
+
def get_serializer(self):
|
|
130
|
+
return self._meta.serializer(
|
|
131
|
+
data=self.get_serializer_data(),
|
|
132
|
+
context=self.get_serializer_context()
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
def _clean_form(self):
|
|
136
|
+
super(SerializerFormBase, self)._clean_form()
|
|
137
|
+
|
|
138
|
+
self.serializer = self.get_serializer()
|
|
139
|
+
|
|
140
|
+
if not self.serializer.is_valid():
|
|
141
|
+
self._errors.update(self.serializer.errors)
|
|
142
|
+
else:
|
|
143
|
+
self.cleaned_data.update(self.serializer.validated_data)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class SerializerForm(six.with_metaclass(SerializerFormMeta, SerializerFormBase)):
|
|
147
|
+
_is_base = True
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def form_from_serializer(serializer, **kwargs):
|
|
151
|
+
assert inspect.isclass(serializer) and issubclass(serializer, serializers.BaseSerializer), (
|
|
152
|
+
'Can only create forms from DRF Serializers'
|
|
153
|
+
)
|
|
154
|
+
kwargs.update({'serializer': serializer})
|
|
155
|
+
meta = type(str('Meta'), (object,), kwargs)
|
|
156
|
+
return type(str('{}Form'.format(serializer.__name__)), (SerializerForm,), {'Meta': meta})
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from __future__ import absolute_import, print_function, unicode_literals
|
|
2
|
+
|
|
3
|
+
from .parsers import StrippingJSONParser
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class MultipleSerializersViewMixin(object):
|
|
7
|
+
def get_serializer(self, *args, **kwargs):
|
|
8
|
+
serializer_class = kwargs.pop('serializer_class', None)
|
|
9
|
+
if serializer_class is None:
|
|
10
|
+
serializer_class = self.get_serializer_class()
|
|
11
|
+
|
|
12
|
+
kwargs['context'] = self.get_serializer_context()
|
|
13
|
+
|
|
14
|
+
return serializer_class(*args, **kwargs)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class MapDataViewMixin(object):
|
|
18
|
+
# Configuration for data mapper.
|
|
19
|
+
# Leave None if you don't require mapping
|
|
20
|
+
data_mapper_class = None
|
|
21
|
+
|
|
22
|
+
def get_mapper_context(self):
|
|
23
|
+
return self.get_serializer_context()
|
|
24
|
+
|
|
25
|
+
def get_data(self, mapper_class=None):
|
|
26
|
+
"""
|
|
27
|
+
Get data for serialization.
|
|
28
|
+
"""
|
|
29
|
+
if not mapper_class:
|
|
30
|
+
mapper_class = self.data_mapper_class
|
|
31
|
+
|
|
32
|
+
if mapper_class is not None:
|
|
33
|
+
return mapper_class(context=self.get_mapper_context())(self.request.data)
|
|
34
|
+
|
|
35
|
+
return self.request.data
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class StrippingJSONViewMixin(object):
|
|
39
|
+
parser_classes = (StrippingJSONParser,)
|
|
40
|
+
|
|
41
|
+
# Location of the root of the data for this api.
|
|
42
|
+
parser_root = None
|
|
43
|
+
|
|
44
|
+
def get_parser_context(self, http_request):
|
|
45
|
+
"""
|
|
46
|
+
Add 'parser_root' to this view's parser's parse_context.
|
|
47
|
+
Since this view uses a DescendingJSONParser, it uses
|
|
48
|
+
this information to decide what to pull out.
|
|
49
|
+
"""
|
|
50
|
+
context = super(StrippingJSONViewMixin, self).get_parser_context(http_request)
|
|
51
|
+
context.update({'parse_root': self.parser_root})
|
|
52
|
+
return context
|
|
File without changes
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from __future__ import absolute_import, print_function, unicode_literals
|
|
2
|
+
import json
|
|
3
|
+
from collections import OrderedDict
|
|
4
|
+
|
|
5
|
+
import six
|
|
6
|
+
from django.conf import settings
|
|
7
|
+
from rest_framework import parsers
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class SortedJSONParser(parsers.JSONParser):
|
|
11
|
+
"""
|
|
12
|
+
Parses JSON-serialized data into OrderedDict.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def parse(self, stream, media_type=None, parser_context=None):
|
|
16
|
+
"""
|
|
17
|
+
Parses the incoming bytestream as JSON and returns the resulting data.
|
|
18
|
+
"""
|
|
19
|
+
parser_context = parser_context or {}
|
|
20
|
+
encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET)
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
data = stream.read().decode(encoding)
|
|
24
|
+
return json.loads(data, object_pairs_hook=OrderedDict)
|
|
25
|
+
except ValueError as exc:
|
|
26
|
+
raise parsers.ParseError('JSON parse error - %s' % six.text_type(exc))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# TODO Create a Renderer that does the opposite of this parser.
|
|
30
|
+
class StrippingJSONParser(parsers.JSONParser):
|
|
31
|
+
"""
|
|
32
|
+
Strip the outer layer of JSON, returning only inner layer.
|
|
33
|
+
|
|
34
|
+
This is a convenience class, so that API creators do not need
|
|
35
|
+
to wrap their serializers in a "parent serializer" for the sole
|
|
36
|
+
purpose of stripping out the top-level node.
|
|
37
|
+
|
|
38
|
+
Place desired root into parser-context as 'parse-root'.
|
|
39
|
+
Only supports descending one level of nesting.
|
|
40
|
+
|
|
41
|
+
Caller is expected to add 'parse_root' to the parser's
|
|
42
|
+
context; a convenient place to do this is in a GenericApiView
|
|
43
|
+
subclass's `get_parser_context()` method.
|
|
44
|
+
|
|
45
|
+
Example, for parse_root of "dt_application"::
|
|
46
|
+
|
|
47
|
+
input json:
|
|
48
|
+
{
|
|
49
|
+
"dt_application": {
|
|
50
|
+
"node1": 1234
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
output dictionary:
|
|
54
|
+
{
|
|
55
|
+
"node1": 1234
|
|
56
|
+
}
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def parse(self, stream, media_type=None, parser_context=None):
|
|
60
|
+
data = super(StrippingJSONParser, self).parse(
|
|
61
|
+
stream, media_type=media_type, parser_context=parser_context
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
root = parser_context.pop('parse_root')
|
|
66
|
+
except KeyError:
|
|
67
|
+
pass
|
|
68
|
+
else:
|
|
69
|
+
if root in data:
|
|
70
|
+
return data[root]
|
|
71
|
+
|
|
72
|
+
return data
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from __future__ import absolute_import, print_function, unicode_literals
|
|
3
|
+
from collections import Mapping
|
|
4
|
+
|
|
5
|
+
import six
|
|
6
|
+
from rest_framework.renderers import JSONRenderer
|
|
7
|
+
from rest_framework.utils.encoders import JSONEncoder
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class DoubleAsStrJsonEncoder(JSONEncoder):
|
|
11
|
+
def _encode(self, o):
|
|
12
|
+
if isinstance(o, Mapping):
|
|
13
|
+
return {k: self._encode(v) for k, v in o.items()}
|
|
14
|
+
elif isinstance(o, (list, tuple)):
|
|
15
|
+
return [self._encode(i) for i in o]
|
|
16
|
+
elif isinstance(o, six.integer_types):
|
|
17
|
+
int_str = six.text_type(o)
|
|
18
|
+
if len(int_str) >= 15:
|
|
19
|
+
o = int_str
|
|
20
|
+
return o
|
|
21
|
+
|
|
22
|
+
def encode(self, o):
|
|
23
|
+
return super(DoubleAsStrJsonEncoder, self).encode(self._encode(o))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class DoubleAsStrJsonRenderer(JSONRenderer):
|
|
27
|
+
"""
|
|
28
|
+
Regular Json renderer except big integers are converted to strings
|
|
29
|
+
|
|
30
|
+
Not all json clients support big integers (e.g. js) hence we need to convert
|
|
31
|
+
all big integers to strings for compatibility reasons.
|
|
32
|
+
|
|
33
|
+
For usage, custom ``Accept: application/json; double=str`` needs to be passed.
|
|
34
|
+
"""
|
|
35
|
+
encoder_class = DoubleAsStrJsonEncoder
|
|
36
|
+
media_type = 'application/json; double=str'
|
|
37
|
+
format = 'json'
|
|
File without changes
|
|
Binary file
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
from __future__ import absolute_import, print_function, unicode_literals
|
|
2
|
+
import inspect
|
|
3
|
+
|
|
4
|
+
from rest_framework import fields, serializers
|
|
5
|
+
from rest_framework.fields import empty
|
|
6
|
+
|
|
7
|
+
from ..utils import add_base_class_to_instance, get_class_name_with_new_suffix
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class EnforceValidationFieldMixin(object):
|
|
11
|
+
"""
|
|
12
|
+
Custom DRF field mixin which allows to ignore validation error
|
|
13
|
+
if the field is not mandatory.
|
|
14
|
+
|
|
15
|
+
The field is mandatory when the parent serializer includes it
|
|
16
|
+
in the ``must_validate_fields`` list or ``must_validate_fields``
|
|
17
|
+
list is completely omitted. If the list is omitted, then all
|
|
18
|
+
fields in that serializer are mandatory and must validate.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def run_validation(self, data=empty):
|
|
22
|
+
try:
|
|
23
|
+
return super(EnforceValidationFieldMixin, self).run_validation(data)
|
|
24
|
+
except serializers.ValidationError as e:
|
|
25
|
+
must_validate_fields = getattr(self.parent, 'must_validate_fields', None)
|
|
26
|
+
field_name = getattr(self, 'field_name')
|
|
27
|
+
|
|
28
|
+
# only re-raise validation error when this field must be validated
|
|
29
|
+
# as defined by must_validate_fields list on the parent serializer
|
|
30
|
+
# or if must_validate_fields is not defined
|
|
31
|
+
if must_validate_fields is None or field_name in must_validate_fields:
|
|
32
|
+
raise
|
|
33
|
+
else:
|
|
34
|
+
self.capture_failed_field(field_name, data, e.detail)
|
|
35
|
+
raise fields.SkipField(
|
|
36
|
+
'This field "{}" is being skipped as per enforce validation logic.'
|
|
37
|
+
''.format(field_name)
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
def capture_failed_field(self, field_name, field_data, error_msg):
|
|
41
|
+
"""
|
|
42
|
+
Hook for capturing invalid fields. This is used to track which fields have been skipped.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
field_name (str): the name of the field whose data failed to validate
|
|
46
|
+
field_data (object): the data of the field that failed validation
|
|
47
|
+
error_msg (str): validation error message
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
Not meant to return anything.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _create_enforce_validation_serializer(serializer, strict_mode_by_default=True, validation_serializer_field_mixin_class=EnforceValidationFieldMixin):
|
|
55
|
+
"""
|
|
56
|
+
Recursively creates a copy of a given serializer which enforces ``must_validate_fields``.
|
|
57
|
+
|
|
58
|
+
This function recursively copies serializers and replaces all fields
|
|
59
|
+
by adding ``EnforceValidationFieldMixin`` to their mro which then enforces validation
|
|
60
|
+
for fields defined in ``must_validate_fields`` and drops their data for all other fields.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
serializer (Serializer): Serializer class or instance to be copied
|
|
64
|
+
strict_mode_by_default (bool): Whether serializer should use strict mode
|
|
65
|
+
when ``must_validate_fields`` is not defined.
|
|
66
|
+
If ``True``, then all fields must be validated by default
|
|
67
|
+
and if ``False``, then all fields can be dropped.
|
|
68
|
+
validation_serializer_field_mixin_class (type): the class used to validate serializer fields
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Recursive copy of the ``serializer`` which will enforce ``must_validate_fields``.
|
|
72
|
+
"""
|
|
73
|
+
if inspect.isclass(serializer):
|
|
74
|
+
serializer = type(
|
|
75
|
+
get_class_name_with_new_suffix(
|
|
76
|
+
serializer,
|
|
77
|
+
'Serializer',
|
|
78
|
+
'EnforceValidationSerializer'
|
|
79
|
+
),
|
|
80
|
+
(serializer,),
|
|
81
|
+
{}
|
|
82
|
+
)
|
|
83
|
+
fields = serializer._declared_fields
|
|
84
|
+
declared_fields = None
|
|
85
|
+
|
|
86
|
+
else:
|
|
87
|
+
# when serializer is instance, we still want to create a modified
|
|
88
|
+
# serializer class copy since if we only adjust the fields
|
|
89
|
+
# on the instance, that can complicate introspecting,
|
|
90
|
+
# especially when ``_create_enforce_validation_serializer``
|
|
91
|
+
# is used as decorator
|
|
92
|
+
serializer = add_base_class_to_instance(
|
|
93
|
+
serializer,
|
|
94
|
+
new_name=get_class_name_with_new_suffix(
|
|
95
|
+
serializer.__class__,
|
|
96
|
+
'Serializer',
|
|
97
|
+
'EnforceValidationSerializer'
|
|
98
|
+
)
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
if isinstance(serializer, serializers.ListSerializer):
|
|
102
|
+
serializer.child = _create_enforce_validation_serializer(
|
|
103
|
+
serializer.child,
|
|
104
|
+
strict_mode_by_default=strict_mode_by_default,
|
|
105
|
+
validation_serializer_field_mixin_class=validation_serializer_field_mixin_class,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# kwargs are used to take a deepcopy of the fields
|
|
109
|
+
# so we need to adjust the child kwargs not to loose
|
|
110
|
+
# reference to our custom child serializer
|
|
111
|
+
if 'child' in serializer._kwargs:
|
|
112
|
+
serializer._kwargs['child'] = serializer.child
|
|
113
|
+
|
|
114
|
+
return serializer
|
|
115
|
+
|
|
116
|
+
fields = serializer.fields
|
|
117
|
+
declared_fields = serializer._declared_fields
|
|
118
|
+
|
|
119
|
+
if not strict_mode_by_default and not hasattr(serializer, 'must_validate_fields'):
|
|
120
|
+
serializer.must_validate_fields = []
|
|
121
|
+
|
|
122
|
+
# this is necessary for deepcopy to work when
|
|
123
|
+
# root serializer is instantiated it does deepcopy
|
|
124
|
+
# on serializer._declared_fields which re-instantiates
|
|
125
|
+
# all child fields hence must_validate_fields will be lost
|
|
126
|
+
# adding it to the class makes it persistent
|
|
127
|
+
if not inspect.isclass(serializer):
|
|
128
|
+
serializer.__class__.must_validate_fields = []
|
|
129
|
+
|
|
130
|
+
# cant use .items() since we need to adjust dictionary
|
|
131
|
+
# within the loop so we cant be looping over the dict
|
|
132
|
+
# at the same time
|
|
133
|
+
# Python 3 even raises exception for this:
|
|
134
|
+
# RuntimeError: dictionary changed size during iteration
|
|
135
|
+
for name in list(fields.keys()):
|
|
136
|
+
field = fields[name]
|
|
137
|
+
replacement = None
|
|
138
|
+
|
|
139
|
+
if isinstance(field, serializers.BaseSerializer):
|
|
140
|
+
replacement = _create_enforce_validation_serializer(
|
|
141
|
+
field,
|
|
142
|
+
strict_mode_by_default=strict_mode_by_default,
|
|
143
|
+
validation_serializer_field_mixin_class=validation_serializer_field_mixin_class,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
elif isinstance(field, serializers.Field):
|
|
147
|
+
replacement = add_base_class_to_instance(
|
|
148
|
+
field,
|
|
149
|
+
validation_serializer_field_mixin_class,
|
|
150
|
+
new_name=get_class_name_with_new_suffix(
|
|
151
|
+
field.__class__,
|
|
152
|
+
'Field',
|
|
153
|
+
'EnforceValidationField'
|
|
154
|
+
)
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
if replacement is not None:
|
|
158
|
+
if declared_fields is not None:
|
|
159
|
+
declared_fields[name] = replacement
|
|
160
|
+
|
|
161
|
+
if replacement.source == name:
|
|
162
|
+
replacement.source = None
|
|
163
|
+
|
|
164
|
+
fields[name] = replacement
|
|
165
|
+
|
|
166
|
+
return serializer
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def create_enforce_validation_serializer(serializer=None, **kwargs):
|
|
170
|
+
"""
|
|
171
|
+
Public function that creates a copy of a serializer which enforces ``must_validate_fields``.
|
|
172
|
+
The difference between this function and ``_create_enforce_validation_serializer``
|
|
173
|
+
is that this function can be used both as a direct decorator and decorator with
|
|
174
|
+
parameters.
|
|
175
|
+
|
|
176
|
+
For example::
|
|
177
|
+
|
|
178
|
+
@create_enforce_validation_serializer
|
|
179
|
+
class MySerializer(BaseSerializer): pass
|
|
180
|
+
|
|
181
|
+
# or
|
|
182
|
+
|
|
183
|
+
@create_enforce_validation_serializer(param=value)
|
|
184
|
+
class MySerializer(BaseSerializer): pass
|
|
185
|
+
|
|
186
|
+
# or
|
|
187
|
+
|
|
188
|
+
create_enforce_validation_serializer(
|
|
189
|
+
MySerializer,
|
|
190
|
+
param=value
|
|
191
|
+
)
|
|
192
|
+
"""
|
|
193
|
+
# used as direct decorator so then simply return new serializer
|
|
194
|
+
# e.g. @decorator
|
|
195
|
+
# class MySerializer(...)
|
|
196
|
+
# or used as regular function
|
|
197
|
+
# e.g. function(Serializer, foo=bar)
|
|
198
|
+
if inspect.isclass(serializer) and issubclass(serializer, serializers.Serializer):
|
|
199
|
+
return _create_enforce_validation_serializer(serializer, **kwargs)
|
|
200
|
+
|
|
201
|
+
# used as decorator with parameters
|
|
202
|
+
# e.g. @decorator(foo=bar)
|
|
203
|
+
# class MySerializer(...)
|
|
204
|
+
elif serializer is None:
|
|
205
|
+
def inner(serializer):
|
|
206
|
+
return _create_enforce_validation_serializer(serializer, **kwargs)
|
|
207
|
+
|
|
208
|
+
return inner
|
|
209
|
+
|
|
210
|
+
else:
|
|
211
|
+
raise TypeError(
|
|
212
|
+
'create_enforce_validation_serializer can only be only on serializers. '
|
|
213
|
+
'It was called with "{}"'.format(type(serializer))
|
|
214
|
+
)
|