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.

Files changed (267) hide show
  1. simo/__pycache__/asgi.cpython-38.pyc +0 -0
  2. simo/__pycache__/settings.cpython-38.pyc +0 -0
  3. simo/__pycache__/urls.cpython-38.pyc +0 -0
  4. simo/__pycache__/wsgi.cpython-38.pyc +0 -0
  5. simo/core/__pycache__/admin.cpython-38.pyc +0 -0
  6. simo/core/__pycache__/api.cpython-38.pyc +0 -0
  7. simo/core/__pycache__/api_meta.cpython-38.pyc +0 -0
  8. simo/core/__pycache__/auto_urls.cpython-38.pyc +0 -0
  9. simo/core/__pycache__/autocomplete_views.cpython-38.pyc +0 -0
  10. simo/core/__pycache__/base_types.cpython-38.pyc +0 -0
  11. simo/core/__pycache__/context.cpython-38.pyc +0 -0
  12. simo/core/__pycache__/controllers.cpython-38.pyc +0 -0
  13. simo/core/__pycache__/events.cpython-38.pyc +0 -0
  14. simo/core/__pycache__/forms.cpython-38.pyc +0 -0
  15. simo/core/__pycache__/gateways.cpython-38.pyc +0 -0
  16. simo/core/__pycache__/managers.cpython-38.pyc +0 -0
  17. simo/core/__pycache__/middleware.cpython-38.pyc +0 -0
  18. simo/core/__pycache__/models.cpython-38.pyc +0 -0
  19. simo/core/__pycache__/permissions.cpython-38.pyc +0 -0
  20. simo/core/__pycache__/serializers.cpython-38.pyc +0 -0
  21. simo/core/__pycache__/signal_receivers.cpython-38.pyc +0 -0
  22. simo/core/__pycache__/socket_consumers.cpython-38.pyc +0 -0
  23. simo/core/__pycache__/tasks.cpython-38.pyc +0 -0
  24. simo/core/__pycache__/views.cpython-38.pyc +0 -0
  25. simo/core/admin.py +28 -18
  26. simo/core/api.py +157 -16
  27. simo/core/api_meta.py +87 -0
  28. simo/core/auto_urls.py +4 -1
  29. simo/core/autocomplete_views.py +8 -4
  30. simo/core/base_types.py +1 -0
  31. simo/core/context.py +3 -1
  32. simo/core/controllers.py +112 -32
  33. simo/core/db_backend/base.py +7 -22
  34. simo/core/drf_braces/README +3 -0
  35. simo/core/drf_braces/__init__.py +7 -0
  36. simo/core/drf_braces/__pycache__/__init__.cpython-38.pyc +0 -0
  37. simo/core/drf_braces/__pycache__/utils.cpython-38.pyc +0 -0
  38. simo/core/drf_braces/fields/__init__.py +5 -0
  39. simo/core/drf_braces/fields/__pycache__/__init__.cpython-38.pyc +0 -0
  40. simo/core/drf_braces/fields/__pycache__/_fields.cpython-38.pyc +0 -0
  41. simo/core/drf_braces/fields/__pycache__/custom.cpython-38.pyc +0 -0
  42. simo/core/drf_braces/fields/__pycache__/mixins.cpython-38.pyc +0 -0
  43. simo/core/drf_braces/fields/__pycache__/modified.cpython-38.pyc +0 -0
  44. simo/core/drf_braces/fields/_fields.py +48 -0
  45. simo/core/drf_braces/fields/custom.py +107 -0
  46. simo/core/drf_braces/fields/mixins.py +58 -0
  47. simo/core/drf_braces/fields/modified.py +41 -0
  48. simo/core/drf_braces/forms/__init__.py +0 -0
  49. simo/core/drf_braces/forms/fields.py +20 -0
  50. simo/core/drf_braces/forms/serializer_form.py +156 -0
  51. simo/core/drf_braces/mixins.py +52 -0
  52. simo/core/drf_braces/models.py +0 -0
  53. simo/core/drf_braces/parsers.py +72 -0
  54. simo/core/drf_braces/renderers.py +37 -0
  55. simo/core/drf_braces/serializers/__init__.py +0 -0
  56. simo/core/drf_braces/serializers/__pycache__/__init__.cpython-38.pyc +0 -0
  57. simo/core/drf_braces/serializers/__pycache__/form_serializer.cpython-38.pyc +0 -0
  58. simo/core/drf_braces/serializers/enforce_validation_serializer.py +214 -0
  59. simo/core/drf_braces/serializers/form_serializer.py +391 -0
  60. simo/core/drf_braces/serializers/swapping.py +48 -0
  61. simo/core/drf_braces/tests/__init__.py +0 -0
  62. simo/core/drf_braces/tests/fields/__init__.py +0 -0
  63. simo/core/drf_braces/tests/fields/test_custom.py +94 -0
  64. simo/core/drf_braces/tests/fields/test_fields.py +13 -0
  65. simo/core/drf_braces/tests/fields/test_mixins.py +96 -0
  66. simo/core/drf_braces/tests/fields/test_modified.py +40 -0
  67. simo/core/drf_braces/tests/forms/__init__.py +0 -0
  68. simo/core/drf_braces/tests/forms/test_fields.py +46 -0
  69. simo/core/drf_braces/tests/forms/test_serializer_form.py +256 -0
  70. simo/core/drf_braces/tests/serializers/__init__.py +0 -0
  71. simo/core/drf_braces/tests/serializers/test_enforce_validation_serializer.py +169 -0
  72. simo/core/drf_braces/tests/serializers/test_form_serializer.py +387 -0
  73. simo/core/drf_braces/tests/serializers/test_swapping.py +40 -0
  74. simo/core/drf_braces/tests/test_mixins.py +111 -0
  75. simo/core/drf_braces/tests/test_parsers.py +73 -0
  76. simo/core/drf_braces/tests/test_renderers.py +23 -0
  77. simo/core/drf_braces/tests/test_utils.py +73 -0
  78. simo/core/drf_braces/utils.py +209 -0
  79. simo/core/events.py +3 -3
  80. simo/core/forms.py +79 -37
  81. simo/core/gateways.py +31 -14
  82. simo/core/management/commands/gateways_manager.py +0 -1
  83. simo/core/managers.py +81 -0
  84. simo/core/middleware.py +25 -0
  85. simo/core/migrations/0026_category_instance.py +20 -0
  86. simo/core/migrations/0027_remove_component_tags.py +17 -0
  87. simo/core/migrations/0028_rename_subcomponents_component_slaves.py +18 -0
  88. simo/core/migrations/0029_auto_20240229_1331.py +33 -0
  89. simo/core/migrations/__pycache__/0026_category_instance.cpython-38.pyc +0 -0
  90. simo/core/migrations/__pycache__/0027_remove_component_tags.cpython-38.pyc +0 -0
  91. simo/core/migrations/__pycache__/0028_rename_subcomponents_component_slaves.cpython-38.pyc +0 -0
  92. simo/core/migrations/__pycache__/0029_auto_20240229_1331.cpython-38.pyc +0 -0
  93. simo/core/models.py +103 -66
  94. simo/core/permissions.py +28 -2
  95. simo/core/serializers.py +330 -26
  96. simo/core/socket_consumers.py +5 -14
  97. simo/core/tasks.py +11 -1
  98. simo/core/templates/admin/base.html +37 -10
  99. simo/core/templates/admin/wizard/discovery.html +188 -0
  100. simo/core/templates/admin/wizard/wizard_add.html +5 -5
  101. simo/core/utils/__pycache__/serialization.cpython-38.pyc +0 -0
  102. simo/core/utils/admin.py +9 -2
  103. simo/core/utils/formsets.py +17 -16
  104. simo/core/utils/helpers.py +1 -0
  105. simo/core/utils/serialization.py +56 -0
  106. simo/core/utils/type_constants.py +1 -1
  107. simo/core/utils/validators.py +14 -1
  108. simo/core/views.py +13 -0
  109. simo/fleet/__pycache__/admin.cpython-38.pyc +0 -0
  110. simo/fleet/__pycache__/api.cpython-38.pyc +0 -0
  111. simo/fleet/__pycache__/auto_urls.cpython-38.pyc +0 -0
  112. simo/fleet/__pycache__/controllers.cpython-38.pyc +0 -0
  113. simo/fleet/__pycache__/forms.cpython-38.pyc +0 -0
  114. simo/fleet/__pycache__/gateways.cpython-38.pyc +0 -0
  115. simo/fleet/__pycache__/managers.cpython-38.pyc +0 -0
  116. simo/fleet/__pycache__/models.cpython-38.pyc +0 -0
  117. simo/fleet/__pycache__/serializers.cpython-38.pyc +0 -0
  118. simo/fleet/__pycache__/socket_consumers.cpython-38.pyc +0 -0
  119. simo/fleet/__pycache__/utils.cpython-38.pyc +0 -0
  120. simo/fleet/__pycache__/views.cpython-38.pyc +0 -0
  121. simo/fleet/admin.py +54 -25
  122. simo/fleet/api.py +59 -3
  123. simo/fleet/auto_urls.py +2 -3
  124. simo/fleet/controllers.py +199 -16
  125. simo/fleet/forms.py +325 -483
  126. simo/fleet/gateways.py +44 -2
  127. simo/fleet/managers.py +32 -0
  128. simo/fleet/migrations/0025_auto_20240130_1334.py +27 -0
  129. simo/fleet/migrations/0026_rename_i2cinterface_scl_pin_and_more.py +64 -0
  130. simo/fleet/migrations/0027_auto_20240306_0802.py +170 -0
  131. simo/fleet/migrations/0028_remove_i2cinterface_scl_pin_no_and_more.py +21 -0
  132. simo/fleet/migrations/0029_alter_i2cinterface_scl_pin_and_more.py +24 -0
  133. simo/fleet/migrations/0030_colonelpin_label_alter_colonel_type.py +24 -0
  134. simo/fleet/migrations/0031_alter_colonel_type.py +18 -0
  135. simo/fleet/migrations/__pycache__/0025_auto_20240130_1334.cpython-38.pyc +0 -0
  136. simo/fleet/migrations/__pycache__/0026_rename_i2cinterface_scl_pin_and_more.cpython-38.pyc +0 -0
  137. simo/fleet/migrations/__pycache__/0027_auto_20240306_0802.cpython-38.pyc +0 -0
  138. simo/fleet/migrations/__pycache__/0028_remove_i2cinterface_scl_pin_no_and_more.cpython-38.pyc +0 -0
  139. simo/fleet/migrations/__pycache__/0029_alter_i2cinterface_scl_pin_and_more.cpython-38.pyc +0 -0
  140. simo/fleet/migrations/__pycache__/0030_colonelpin_label_alter_colonel_type.cpython-38.pyc +0 -0
  141. simo/fleet/migrations/__pycache__/0031_alter_colonel_type.cpython-38.pyc +0 -0
  142. simo/fleet/models.py +134 -82
  143. simo/fleet/serializers.py +35 -1
  144. simo/fleet/socket_consumers.py +239 -76
  145. simo/fleet/utils.py +15 -53
  146. simo/fleet/views.py +28 -14
  147. simo/generic/controllers.py +13 -89
  148. simo/generic/forms.py +29 -18
  149. simo/generic/gateways.py +73 -2
  150. simo/generic/models.py +3 -3
  151. simo/multimedia/controllers.py +9 -8
  152. simo/settings.py +7 -4
  153. simo/urls.py +4 -8
  154. simo/users/__pycache__/admin.cpython-38.pyc +0 -0
  155. simo/users/__pycache__/api.cpython-38.pyc +0 -0
  156. simo/users/__pycache__/auto_urls.cpython-38.pyc +0 -0
  157. simo/users/__pycache__/models.cpython-38.pyc +0 -0
  158. simo/users/__pycache__/serializers.cpython-38.pyc +0 -0
  159. simo/users/__pycache__/sso_urls.cpython-38.pyc +0 -0
  160. simo/users/admin.py +8 -1
  161. simo/users/api.py +38 -2
  162. simo/users/auto_urls.py +2 -2
  163. simo/users/migrations/0025_rename_name_fingerprint_type_and_more.py +22 -0
  164. simo/users/migrations/__pycache__/0025_rename_name_fingerprint_type_and_more.cpython-38.pyc +0 -0
  165. simo/users/models.py +2 -3
  166. simo/users/serializers.py +15 -1
  167. simo/users/sso_urls.py +3 -3
  168. simo/wsgi.py +7 -0
  169. {simo-1.7.20.dist-info → simo-2.0.0.dist-info}/METADATA +8 -9
  170. {simo-1.7.20.dist-info → simo-2.0.0.dist-info}/RECORD +173 -189
  171. {simo-1.7.20.dist-info → simo-2.0.0.dist-info}/WHEEL +1 -1
  172. simo/core/db_backend/__pycache__/__init__.cpython-38.pyc +0 -0
  173. simo/core/db_backend/__pycache__/base.cpython-38.pyc +0 -0
  174. simo/core/management/commands/__pycache__/gateways_manager.cpython-38.pyc +0 -0
  175. simo/core/migrations/__pycache__/0001_initial.cpython-38.pyc +0 -0
  176. simo/core/migrations/__pycache__/0002_load_icons.cpython-38.pyc +0 -0
  177. simo/core/migrations/__pycache__/0003_create_default_zones_and_categories.cpython-38.pyc +0 -0
  178. simo/core/migrations/__pycache__/0004_create_generic.cpython-38.pyc +0 -0
  179. simo/core/migrations/__pycache__/0005_component_subcomponents.cpython-38.pyc +0 -0
  180. simo/core/migrations/__pycache__/0006_alter_component_subcomponents.cpython-38.pyc +0 -0
  181. simo/core/migrations/__pycache__/0007_component_change_init_to.cpython-38.pyc +0 -0
  182. simo/core/migrations/__pycache__/0008_alter_component_change_init_to.cpython-38.pyc +0 -0
  183. simo/core/migrations/__pycache__/0009_auto_20220707_1404.cpython-38.pyc +0 -0
  184. simo/core/migrations/__pycache__/0010_historyaggregate.cpython-38.pyc +0 -0
  185. simo/core/migrations/__pycache__/0011_component_last_change.cpython-38.pyc +0 -0
  186. simo/core/migrations/__pycache__/0012_instance.cpython-38.pyc +0 -0
  187. simo/core/migrations/__pycache__/0013_auto_20231003_0754.cpython-38.pyc +0 -0
  188. simo/core/migrations/__pycache__/0014_zone_instance.cpython-38.pyc +0 -0
  189. simo/core/migrations/__pycache__/0015_auto_20231004_1113.cpython-38.pyc +0 -0
  190. simo/core/migrations/__pycache__/0016_auto_20231004_1113.cpython-38.pyc +0 -0
  191. simo/core/migrations/__pycache__/0017_auto_20231004_1313.cpython-38.pyc +0 -0
  192. simo/core/migrations/__pycache__/0018_auto_20231005_0622.cpython-38.pyc +0 -0
  193. simo/core/migrations/__pycache__/0019_alter_gateway_type.cpython-38.pyc +0 -0
  194. simo/core/migrations/__pycache__/0020_component_meta.cpython-38.pyc +0 -0
  195. simo/core/migrations/__pycache__/0021_auto_20231020_1041.cpython-38.pyc +0 -0
  196. simo/core/migrations/__pycache__/__init__.cpython-38.pyc +0 -0
  197. simo/core/templatetags/__pycache__/__init__.cpython-38.pyc +0 -0
  198. simo/core/templatetags/__pycache__/components_list.cpython-38.pyc +0 -0
  199. simo/core/utils/__pycache__/__init__.cpython-38.pyc +0 -0
  200. simo/core/utils/__pycache__/admin.cpython-38.pyc +0 -0
  201. simo/core/utils/__pycache__/config_values.cpython-38.pyc +0 -0
  202. simo/core/utils/__pycache__/easing.cpython-38.pyc +0 -0
  203. simo/core/utils/__pycache__/form_fields.cpython-38.pyc +0 -0
  204. simo/core/utils/__pycache__/form_widgets.cpython-38.pyc +0 -0
  205. simo/core/utils/__pycache__/formsets.cpython-38.pyc +0 -0
  206. simo/core/utils/__pycache__/helpers.cpython-38.pyc +0 -0
  207. simo/core/utils/__pycache__/logs.cpython-38.pyc +0 -0
  208. simo/core/utils/__pycache__/mixins.cpython-38.pyc +0 -0
  209. simo/core/utils/__pycache__/model_helpers.cpython-38.pyc +0 -0
  210. simo/core/utils/__pycache__/relay.cpython-38.pyc +0 -0
  211. simo/core/utils/__pycache__/type_constants.cpython-38.pyc +0 -0
  212. simo/core/utils/__pycache__/validators.cpython-38.pyc +0 -0
  213. simo/fleet/tasks.py +0 -25
  214. simo/generic/__pycache__/__init__.cpython-37.pyc +0 -0
  215. simo/generic/__pycache__/__init__.cpython-38.pyc +0 -0
  216. simo/generic/__pycache__/app_widgets.cpython-38.pyc +0 -0
  217. simo/generic/__pycache__/base_types.cpython-38.pyc +0 -0
  218. simo/generic/__pycache__/controllers.cpython-37.pyc +0 -0
  219. simo/generic/__pycache__/controllers.cpython-38.pyc +0 -0
  220. simo/generic/__pycache__/forms.cpython-38.pyc +0 -0
  221. simo/generic/__pycache__/gateways.cpython-38.pyc +0 -0
  222. simo/generic/__pycache__/models.cpython-38.pyc +0 -0
  223. simo/generic/__pycache__/routing.cpython-38.pyc +0 -0
  224. simo/generic/__pycache__/socket_consumers.cpython-38.pyc +0 -0
  225. simo/generic/__pycache__/widgets.cpython-37.pyc +0 -0
  226. simo/multimedia/__pycache__/__init__.cpython-38.pyc +0 -0
  227. simo/multimedia/__pycache__/admin.cpython-38.pyc +0 -0
  228. simo/multimedia/__pycache__/api.cpython-38.pyc +0 -0
  229. simo/multimedia/__pycache__/app_widgets.cpython-38.pyc +0 -0
  230. simo/multimedia/__pycache__/base_types.cpython-38.pyc +0 -0
  231. simo/multimedia/__pycache__/controllers.cpython-38.pyc +0 -0
  232. simo/multimedia/__pycache__/forms.cpython-38.pyc +0 -0
  233. simo/multimedia/__pycache__/models.cpython-38.pyc +0 -0
  234. simo/multimedia/__pycache__/serializers.cpython-38.pyc +0 -0
  235. simo/multimedia/migrations/__pycache__/0001_initial.cpython-38.pyc +0 -0
  236. simo/multimedia/migrations/__pycache__/0002_sound_length.cpython-38.pyc +0 -0
  237. simo/multimedia/migrations/__pycache__/0003_alter_sound_length.cpython-38.pyc +0 -0
  238. simo/multimedia/migrations/__pycache__/0004_auto_20231023_1055.cpython-38.pyc +0 -0
  239. simo/multimedia/migrations/__pycache__/__init__.cpython-38.pyc +0 -0
  240. simo/notifications/__pycache__/__init__.cpython-38.pyc +0 -0
  241. simo/notifications/__pycache__/admin.cpython-38.pyc +0 -0
  242. simo/notifications/__pycache__/api.cpython-38.pyc +0 -0
  243. simo/notifications/__pycache__/models.cpython-38.pyc +0 -0
  244. simo/notifications/__pycache__/serializers.cpython-38.pyc +0 -0
  245. simo/notifications/__pycache__/utils.cpython-38.pyc +0 -0
  246. simo/notifications/migrations/__pycache__/0001_initial.cpython-38.pyc +0 -0
  247. simo/notifications/migrations/__pycache__/0002_notification_instance.cpython-38.pyc +0 -0
  248. simo/notifications/migrations/__pycache__/__init__.cpython-38.pyc +0 -0
  249. simo/users/migrations/__pycache__/0001_initial.cpython-38.pyc +0 -0
  250. simo/users/migrations/__pycache__/0002_componentpermission.cpython-38.pyc +0 -0
  251. simo/users/migrations/__pycache__/0003_create_roles_and_system_user.cpython-38.pyc +0 -0
  252. simo/users/migrations/__pycache__/0004_user_secret_key.cpython-38.pyc +0 -0
  253. simo/users/migrations/__pycache__/0005_permissionsrole_instance.cpython-38.pyc +0 -0
  254. simo/users/migrations/__pycache__/0006_auto_20231003_0850.cpython-38.pyc +0 -0
  255. simo/users/migrations/__pycache__/0007_auto_20231003_1228.cpython-38.pyc +0 -0
  256. simo/users/migrations/__pycache__/0008_auto_20231003_1229.cpython-38.pyc +0 -0
  257. simo/users/migrations/__pycache__/0009_remove_user_role.cpython-38.pyc +0 -0
  258. simo/users/migrations/__pycache__/0010_auto_20231004_1313.cpython-38.pyc +0 -0
  259. simo/users/migrations/__pycache__/0011_auto_20231004_1313.cpython-38.pyc +0 -0
  260. simo/users/migrations/__pycache__/0012_alter_userinstancerole_unique_together.cpython-38.pyc +0 -0
  261. simo/users/migrations/__pycache__/0013_remove_user_roles.cpython-38.pyc +0 -0
  262. simo/users/migrations/__pycache__/0014_user_roles.cpython-38.pyc +0 -0
  263. simo/users/migrations/__pycache__/0015_remove_user_at_home.cpython-38.pyc +0 -0
  264. simo/users/migrations/__pycache__/0016_auto_20231005_1050.cpython-38.pyc +0 -0
  265. simo/users/migrations/__pycache__/__init__.cpython-38.pyc +0 -0
  266. {simo-1.7.20.dist-info → simo-2.0.0.dist-info}/LICENSE.md +0 -0
  267. {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
@@ -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
+ )