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,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
+ )