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
simo/core/controllers.py CHANGED
@@ -11,11 +11,13 @@ from django.utils.translation import gettext_lazy as _
11
11
  from simo.users.middleware import introduce, get_current_user
12
12
  from simo.users.utils import get_device_user
13
13
  from .utils.helpers import is_hex_color, classproperty
14
+ # from django.utils.functional import classproperty
14
15
  from .gateways import BaseGatewayHandler
15
16
  from .app_widgets import *
16
17
  from .forms import (
17
18
  BaseComponentForm, NumericSensorForm,
18
- MultiSensorConfigForm, DoubleSwitchConfigForm,
19
+ MultiSensorConfigForm,
20
+ SwitchForm, DoubleSwitchConfigForm,
19
21
  TrippleSwitchConfigForm, QuadrupleSwitchConfigForm,
20
22
  QuintupleSwitchConfigForm, DimmerConfigForm, DimmerPlusConfigForm,
21
23
  RGBWConfigForm
@@ -31,6 +33,7 @@ class ControllerBase(ABC):
31
33
  admin_widget_template = 'admin/controller_widgets/generic.html'
32
34
  default_config = {}
33
35
  default_meta = {}
36
+ discovery_msg = None
34
37
 
35
38
  @property
36
39
  @abstractmethod
@@ -83,7 +86,7 @@ class ControllerBase(ABC):
83
86
  assert issubclass(self.config_form, BaseComponentForm)
84
87
  assert issubclass(self.app_widget, BaseAppWidget)
85
88
  assert self.base_type in ALL_BASE_TYPES, \
86
- "base_type must be defined in BASE_TYPES"
89
+ f"{self.base_type} must be defined in BASE_TYPES!"
87
90
 
88
91
  @classproperty
89
92
  @classmethod
@@ -98,6 +101,15 @@ class ControllerBase(ABC):
98
101
  """
99
102
  return cls.config_form
100
103
 
104
+ @classproperty
105
+ @classmethod
106
+ def is_discoverable(cls):
107
+ return hasattr(
108
+ cls, 'init_discovery'
109
+ ) and hasattr(
110
+ cls, '_process_discovery'
111
+ )
112
+
101
113
  def _aggregate_values(self, values):
102
114
  if type(values[0]) in (float, int):
103
115
  return [statistics.mean(values)]
@@ -165,7 +177,38 @@ class ControllerBase(ABC):
165
177
  else:
166
178
  return self.component.change_init_by
167
179
 
180
+ def send(self, value):
181
+ self.component.refresh_from_db()
182
+
183
+ # Bulk send if it is a switch or dimmer and has slaves
184
+ if self.component.base_type in ('switch', 'dimmer') \
185
+ and self.component.slaves.count():
186
+ bulk_send_map = {self.component: value}
187
+ for slave in self.component.slaves.all():
188
+ bulk_send_map[slave] = value
189
+ from .models import Component
190
+ Component.objects.bulk_send(bulk_send_map)
191
+ return
192
+
193
+ # Regular send
194
+ value = self._validate_val(value, BEFORE_SEND)
195
+
196
+ self.component.change_init_by = get_current_user()
197
+ self.component.change_init_date = timezone.now()
198
+ self.component.save(
199
+ update_fields=['change_init_by', 'change_init_date']
200
+ )
201
+ value = self._prepare_for_send(value)
202
+ GatewayObjectCommand(
203
+ self.component.gateway, self.component, set_val=value
204
+ ).publish()
205
+ if value != self.component.value:
206
+ self.component.value_previous = self.component.value
207
+ self.component.value = value
208
+
168
209
  def set(self, value, actor=None):
210
+ value = self._validate_val(value, BEFORE_SET)
211
+
169
212
  if not actor:
170
213
  actor = self._get_actor(value)
171
214
  if not actor:
@@ -175,8 +218,6 @@ class ControllerBase(ABC):
175
218
  # in relation to the change of this component
176
219
  introduce(actor)
177
220
 
178
- value = self.component.translate_before_set(value)
179
- value = self._validate_val(value, BEFORE_SET)
180
221
  self.component.refresh_from_db()
181
222
  if value != self.component.value:
182
223
  self.component.value_previous = self.component.value
@@ -187,16 +228,15 @@ class ControllerBase(ABC):
187
228
  self.component.change_init_fingerprint = None
188
229
  self.component.save()
189
230
 
190
- def _send_to_device(self, value):
191
- GatewayObjectCommand(
192
- self.component.gateway, self.component, set_val=value
193
- ).publish()
194
-
195
231
  def _receive_from_device(self, value, is_alive=True):
196
232
  value = self._prepare_for_set(value)
197
233
  actor = self._get_actor(value)
234
+
235
+ init_by_device = False
198
236
  if not actor:
237
+ init_by_device = True
199
238
  actor = get_device_user()
239
+
200
240
  # Introducing user to this thread for changes that might happen to other components
201
241
  # in relation to the change of this component
202
242
  introduce(actor)
@@ -204,27 +244,17 @@ class ControllerBase(ABC):
204
244
  self.component.save(update_fields=['alive'])
205
245
  self.set(value, actor)
206
246
 
207
- def _prepare_for_send(self, value):
208
- return value
209
-
210
- def _prepare_for_set(self, value):
211
- return value
212
-
213
- def send(self, value):
214
- self.component.refresh_from_db()
215
- value = self.component.translate_before_send(value)
216
- value = self._validate_val(value, BEFORE_SEND)
217
-
218
- self.component.change_init_by = get_current_user()
219
- self.component.change_init_date = timezone.now()
220
- self.component.save(
221
- update_fields=['change_init_by', 'change_init_date']
222
- )
223
- value = self._prepare_for_send(value)
224
- self._send_to_device(value)
225
- if value != self.component.value:
226
- self.component.value_previous = self.component.value
227
- self.component.value = value
247
+ if init_by_device and self.component.slaves.count():
248
+ slaves_qs = self.component.slaves.all()
249
+ # slaves are being controlled by colonels internally
250
+ if self.component.controller_uid.startswith('simo.fleet.') \
251
+ and self.component.config.get('colonel'):
252
+ slaves_qs = slaves_qs.exclude(
253
+ config__colonel=self.component.config['colonel']
254
+ )
255
+ bulk_send_map = {s: value for s in slaves_qs}
256
+ from .models import Component
257
+ Component.objects.bulk_send(bulk_send_map)
228
258
 
229
259
  def history_display(self, values):
230
260
  assert type(values) in (list, tuple)
@@ -245,6 +275,12 @@ class ControllerBase(ABC):
245
275
  'val': icon if any(values) else None}
246
276
  ]
247
277
 
278
+ def _prepare_for_send(self, value):
279
+ return value
280
+
281
+ def _prepare_for_set(self, value):
282
+ return value
283
+
248
284
 
249
285
  class TimerMixin:
250
286
 
@@ -408,8 +444,22 @@ class Dimmer(ControllerBase, TimerMixin):
408
444
  default_config = {'min': 0.0, 'max': 100.0, 'inverse': False}
409
445
  default_value = 0
410
446
 
447
+ def _prepare_for_send(self, value):
448
+ if isinstance(value, bool):
449
+ if value:
450
+ self.component.refresh_from_db()
451
+ if self.component.value:
452
+ return self.component.value
453
+ else:
454
+ if self.component.value_previous:
455
+ return self.component.value_previous
456
+ return self.component.config.get('max', 100.0)
457
+ else:
458
+ return 0
459
+ return value
460
+
411
461
  def _validate_val(self, value, occasion=None):
412
- if value > self.component.config.get('max', 1.0):
462
+ if value > self.component.config.get('max', 100.0):
413
463
  raise ValidationError("Value to big.")
414
464
  elif value < self.component.config.get('min', 0.0):
415
465
  raise ValidationError("Value to small.")
@@ -508,7 +558,7 @@ class DimmerPlus(ControllerBase, TimerMixin):
508
558
  })
509
559
 
510
560
  def toggle(self):
511
- if self.component.value:
561
+ if self.component.value.get('main'):
512
562
  self.turn_off()
513
563
  else:
514
564
  self.turn_on()
@@ -602,6 +652,7 @@ class Switch(MultiSwitchBase, TimerMixin):
602
652
  name = _("Switch")
603
653
  base_type = 'switch'
604
654
  app_widget = SingleSwitchWidget
655
+ config_form = SwitchForm
605
656
  admin_widget_template = 'admin/controller_widgets/switch.html'
606
657
  default_value = False
607
658
 
@@ -642,6 +693,11 @@ class DoubleSwitch(MultiSwitchBase):
642
693
  config_form = DoubleSwitchConfigForm
643
694
  default_value = [False, False]
644
695
 
696
+ def _prepare_for_send(self, value):
697
+ if isinstance(value, bool):
698
+ return [value, value]
699
+ return value
700
+
645
701
 
646
702
  class TripleSwitch(MultiSwitchBase):
647
703
  name = _("Triple Switch")
@@ -650,6 +706,11 @@ class TripleSwitch(MultiSwitchBase):
650
706
  config_form = TrippleSwitchConfigForm
651
707
  default_value = [False, False, False]
652
708
 
709
+ def _prepare_for_send(self, value):
710
+ if isinstance(value, bool):
711
+ return [value, value, value]
712
+ return value
713
+
653
714
 
654
715
  class QuadrupleSwitch(MultiSwitchBase):
655
716
  name = _("Quadruple Switch")
@@ -658,6 +719,11 @@ class QuadrupleSwitch(MultiSwitchBase):
658
719
  config_form = QuadrupleSwitchConfigForm
659
720
  default_value = [False, False, False, False]
660
721
 
722
+ def _prepare_for_send(self, value):
723
+ if isinstance(value, bool):
724
+ return [value, value, value, value]
725
+ return value
726
+
661
727
 
662
728
  class QuintupleSwitch(MultiSwitchBase):
663
729
  name = _("Quintuple Switch")
@@ -666,6 +732,11 @@ class QuintupleSwitch(MultiSwitchBase):
666
732
  config_form = QuintupleSwitchConfigForm
667
733
  default_value = [False, False, False, False, False]
668
734
 
735
+ def _prepare_for_send(self, value):
736
+ if isinstance(value, bool):
737
+ return [value, value, value, value, value]
738
+ return value
739
+
669
740
 
670
741
  class Lock(Switch):
671
742
  name = _("Lock")
@@ -702,3 +773,12 @@ class Lock(Switch):
702
773
  f"one of available values [{available_values}] for lock."
703
774
  )
704
775
  return value
776
+
777
+ def set(self, value, actor=None):
778
+ super().set(value, actor=actor)
779
+ if actor and value in ('locking', 'unlocking'):
780
+ self.component.change_init_by = actor
781
+ self.component.change_init_date = timezone.now()
782
+ self.component.save(
783
+ update_fields=['change_init_by', 'change_init_date']
784
+ )
@@ -2,6 +2,7 @@ import random, time
2
2
  from django.contrib.gis.db.backends.postgis.base import (
3
3
  DatabaseWrapper as PostGisPsycopg2DatabaseWrapper
4
4
  )
5
+ from django.db import close_old_connections, connection as db_connection
5
6
  from django.utils.asyncio import async_unsafe
6
7
  from django.db.utils import InterfaceError
7
8
  from django.conf import settings
@@ -9,28 +10,12 @@ from django.conf import settings
9
10
 
10
11
  class DatabaseWrapper(PostGisPsycopg2DatabaseWrapper):
11
12
 
12
- # connection already closed raised by default django postgresql db backend
13
- # which means that ensure_connection didn't ensured connection!
14
- # It happens when connection to the postgresql server is already lost,
15
- # but this backend handler knows nothing about it.
16
- # So we set connection to None immediately, therefore if there
17
- # is other thread trying to access the cursor
18
- # new connection will be established by ensure_connection() method.
19
- # We also wait for 0 - 10s, to not overwhelm anything in case
20
- # there are many multiple threads trying to access the same
21
- # db connection.
22
-
23
-
24
- def _cursor(self, name=None):
25
- self.ensure_connection()
26
- with self.wrap_database_errors:
27
- try:
28
- return self._prepare_cursor(self.create_cursor(name))
29
- except InterfaceError:
30
-
31
- self.connection = None
32
- time.sleep(random.randint(0, 100) / 10)
33
- return self._cursor(name)
13
+ @async_unsafe
14
+ def create_cursor(self, name=None):
15
+ if not self.is_usable():
16
+ close_old_connections()
17
+ db_connection.connect()
18
+ return super().create_cursor(name=name)
34
19
 
35
20
 
36
21
 
@@ -0,0 +1,3 @@
1
+ Remove this from here once this pulll request is resolved
2
+
3
+ https://github.com/dealertrack/django-rest-framework-braces/pull/36
@@ -0,0 +1,7 @@
1
+ # -*- coding: utf-8 -*-
2
+ from __future__ import absolute_import, print_function, unicode_literals
3
+
4
+
5
+ __author__ = 'Miroslav Shubernetskiy'
6
+ __email__ = 'miroslav@miki725.com'
7
+ __version__ = '0.3.4'
@@ -0,0 +1,5 @@
1
+ from __future__ import absolute_import, print_function, unicode_literals
2
+
3
+ from ._fields import * # noqa
4
+ from .custom import * # noqa
5
+ from .modified import * # noqa
@@ -0,0 +1,48 @@
1
+ from __future__ import absolute_import, print_function, unicode_literals
2
+ import inspect
3
+
4
+ from rest_framework import fields
5
+ from rest_framework.fields import * # noqa
6
+ from rest_framework.fields import _UnvalidatedField # noqa
7
+
8
+ from .mixins import AllowBlankNullFieldMixin, EmptyStringFieldMixin
9
+
10
+
11
+ FIELDS = [
12
+ 'BooleanField',
13
+ 'CharField',
14
+ 'ChoiceField',
15
+ 'DateField',
16
+ 'DateTimeField',
17
+ 'DecimalField',
18
+ 'DurationField',
19
+ 'EmailField',
20
+ 'FileField',
21
+ 'FloatField',
22
+ 'HiddenField',
23
+ 'ImageField',
24
+ 'IntegerField',
25
+ 'IPAddressField',
26
+ 'MultipleChoiceField',
27
+ 'RegexField',
28
+ 'SlugField',
29
+ 'TimeField',
30
+ 'URLField',
31
+ 'UUIDField',
32
+ ]
33
+
34
+
35
+ def get_updated_fields(fields, base_classes):
36
+ fields = [globals()[i] for i in fields]
37
+ return {
38
+ field.__name__: type(field.__name__, base_classes + (field,), {})
39
+ for field in fields
40
+ }
41
+
42
+
43
+ locals().update(
44
+ get_updated_fields(FIELDS, (EmptyStringFieldMixin, AllowBlankNullFieldMixin))
45
+ )
46
+
47
+ __all__ = [name for name, value in locals().items()
48
+ if inspect.isclass(value) and issubclass(value, fields.Field)]
@@ -0,0 +1,107 @@
1
+ from __future__ import absolute_import, print_function, unicode_literals
2
+ import inspect
3
+ from decimal import Decimal, getcontext
4
+
5
+ import pytz
6
+ import six
7
+ from django.utils.translation import gettext as _
8
+
9
+ from . import _fields as fields
10
+ from .mixins import ValueAsTextFieldMixin
11
+
12
+
13
+ class UnvalidatedField(fields._UnvalidatedField):
14
+ """
15
+ Same as DRF's ``_UnvalidatedField``, except this is a public class.
16
+ """
17
+
18
+ def run_validators(self, value):
19
+ return
20
+
21
+
22
+ class PositiveIntegerField(fields.IntegerField):
23
+ """
24
+ Enhanced DRF's ``IntegerField`` as this default ``min_value`` to be 0
25
+ hence only allowing positive numbers.
26
+ """
27
+
28
+ def __init__(self, *args, **kwargs):
29
+ kwargs.setdefault('min_value', 0)
30
+ super(PositiveIntegerField, self).__init__(*args, **kwargs)
31
+
32
+
33
+ class UTCDateTimeField(fields.DateTimeField):
34
+ """
35
+ Same as DateTimeField except this field guarantees to return time-zone aware dates.
36
+ """
37
+
38
+ def __init__(self, *args, **kwargs):
39
+ kwargs.setdefault('default_timezone', pytz.utc)
40
+ super(UTCDateTimeField, self).__init__(*args, **kwargs)
41
+
42
+
43
+ class NonValidatingChoiceField(fields.ChoiceField):
44
+ """
45
+ ChoiceField subclass that skips the validation of "choices".
46
+ It does apply 'required' validation, and any other validation
47
+ done by the parent drf.Field class.
48
+ """
49
+
50
+ def __init__(self, *args, **kwargs):
51
+ kwargs.setdefault('choices', [])
52
+ super(NonValidatingChoiceField, self).__init__(*args, **kwargs)
53
+
54
+ def to_internal_value(self, data):
55
+ try:
56
+ return self.choice_strings_to_values[six.text_type(data)]
57
+ except KeyError:
58
+ return six.text_type(data)
59
+
60
+
61
+ class NumericField(ValueAsTextFieldMixin, fields.IntegerField):
62
+ default_error_messages = {
63
+ 'invalid': _('Enter a whole number.'),
64
+ }
65
+
66
+
67
+ class RoundedDecimalField(fields.DecimalField):
68
+ """
69
+ Currency field subclass of Decimal used for rounding currencies
70
+ to two decimal places.
71
+ """
72
+
73
+ def __init__(self, max_digits=None, decimal_places=2, rounding=None, *args, **kwargs):
74
+ super(RoundedDecimalField, self).__init__(
75
+ max_digits=max_digits,
76
+ decimal_places=decimal_places,
77
+ *args, **kwargs
78
+ )
79
+ self.rounding = rounding
80
+
81
+ def to_internal_value(self, data):
82
+ return self.quantize(super(RoundedDecimalField, self).to_internal_value(data))
83
+
84
+ def validate_precision(self, data):
85
+ return data
86
+
87
+ def quantize(self, data):
88
+ """
89
+ Quantize the decimal value to the configured precision.
90
+ """
91
+ if self.decimal_places is None:
92
+ return data
93
+
94
+ context = getcontext().copy()
95
+
96
+ if self.max_digits is not None:
97
+ context.prec = self.max_digits
98
+ if self.rounding is not None:
99
+ context.rounding = self.rounding
100
+ return data.quantize(
101
+ Decimal('.1') ** self.decimal_places,
102
+ context=context
103
+ )
104
+
105
+
106
+ __all__ = [name for name, value in locals().items()
107
+ if inspect.isclass(value) and issubclass(value, fields.Field)]
@@ -0,0 +1,58 @@
1
+ from __future__ import absolute_import, print_function, unicode_literals
2
+
3
+ import six
4
+ from rest_framework.fields import CharField, empty
5
+
6
+
7
+ class EmptyStringFieldMixin(object):
8
+ def validate_empty_values(self, data):
9
+ is_empty, data = super(EmptyStringFieldMixin, self).validate_empty_values(data)
10
+ if not is_empty and data == '':
11
+ if self.required:
12
+ self.fail('required')
13
+ else:
14
+ return True, data
15
+ return is_empty, data
16
+
17
+ def to_representation(self, value):
18
+ if value in ('', None) and not self.required:
19
+ return value
20
+ return super(EmptyStringFieldMixin, self).to_representation(value)
21
+
22
+
23
+ class AllowBlankNullFieldMixin(object):
24
+ def __init__(self, *args, **kwargs):
25
+ super(AllowBlankNullFieldMixin, self).__init__(*args, **kwargs)
26
+
27
+ # some DRF fields explicitly do not allow some kwargs
28
+ # therefore we adjust the field attributes directly
29
+ if not self.required:
30
+ if all([isinstance(self, CharField),
31
+ hasattr(self, 'allow_blank'),
32
+ 'allow_blank' not in kwargs]):
33
+ self.allow_blank = True
34
+ if all([hasattr(self, 'allow_null'),
35
+ 'allow_null' not in kwargs]):
36
+ self.allow_null = True
37
+
38
+
39
+ class ValueAsTextFieldMixin(object):
40
+ def to_string_value(self, data):
41
+ if data:
42
+ return six.text_type(data)
43
+ return data
44
+
45
+ def prepare_value_for_validation(self, data):
46
+ return data
47
+
48
+ def run_validation(self, value=empty):
49
+ (is_empty_value, value) = self.validate_empty_values(value)
50
+ if is_empty_value:
51
+ return value
52
+
53
+ value = self.prepare_value_for_validation(value)
54
+ value = self.to_internal_value(value)
55
+ self.run_validators(value)
56
+ value = self.to_string_value(value)
57
+
58
+ return value
@@ -0,0 +1,41 @@
1
+ from __future__ import absolute_import, print_function, unicode_literals
2
+ import inspect
3
+
4
+ from . import _fields as fields
5
+
6
+
7
+ class BooleanField(fields.BooleanField):
8
+ def __init__(self, *args, **kwargs):
9
+ self.TRUE_VALUES = self.TRUE_VALUES | set(kwargs.pop('true_values', []))
10
+ self.FALSE_VALUES = self.FALSE_VALUES | set(kwargs.pop('false_values', []))
11
+ super(BooleanField, self).__init__(*args, **kwargs)
12
+
13
+
14
+ class DecimalField(fields.DecimalField):
15
+ def __init__(self, max_digits=None, decimal_places=None, *args, **kwargs):
16
+ super(DecimalField, self).__init__(
17
+ max_digits=max_digits, decimal_places=decimal_places,
18
+ *args, **kwargs
19
+ )
20
+
21
+ def quantize(self, value):
22
+ if self.max_digits is None or self.decimal_places is None:
23
+ return value
24
+ return super(DecimalField, self).quantize(value)
25
+
26
+
27
+ class DateTimeField(fields.DateTimeField):
28
+ def __init__(self, *args, **kwargs):
29
+ super(DateTimeField, self).__init__(*args, **kwargs)
30
+ # allow fo the case when default_timezone is passed as None
31
+ # since super will in that use Django's default timezone
32
+ if 'default_timezone' in kwargs:
33
+ # DRF >= 3.3
34
+ if callable(self.default_timezone):
35
+ self.timezone = kwargs['default_timezone']
36
+ else:
37
+ self.default_timezone = kwargs['default_timezone']
38
+
39
+
40
+ __all__ = [name for name, value in locals().items()
41
+ if inspect.isclass(value) and issubclass(value, fields.Field)]
File without changes
@@ -0,0 +1,20 @@
1
+ from __future__ import absolute_import, print_function, unicode_literals
2
+
3
+ import six
4
+ from dateutil.parser import parse
5
+ from django import forms
6
+ from rest_framework import ISO_8601
7
+
8
+
9
+ class ISO8601DateTimeField(forms.DateTimeField):
10
+ def to_python(self, value):
11
+ if value in self.empty_values:
12
+ return
13
+
14
+ if isinstance(value, six.string_types) and ISO_8601 in self.input_formats:
15
+ try:
16
+ return parse(value)
17
+ except ValueError:
18
+ raise forms.ValidationError(self.error_messages['invalid'], code='invalid')
19
+
20
+ return super(ISO8601DateTimeField, self).to_python(value)