simo 1.7.19__py3-none-any.whl → 2.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of simo might be problematic. Click here for more details.

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