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