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/serializers.py CHANGED
@@ -1,9 +1,20 @@
1
1
  import inspect
2
+ import datetime
3
+ import json
4
+ from django import forms
5
+ from django.forms.utils import ErrorDict
6
+ from collections.abc import Iterable
2
7
  from easy_thumbnails.files import get_thumbnailer
3
8
  from simo.core.middleware import get_current_request
4
9
  from rest_framework import serializers
5
- from .models import Category, Zone, Component, Icon, ComponentHistory
6
- from .utils.type_constants import APP_WIDGETS
10
+ from simo.core.forms import FormsetField
11
+ from rest_framework.relations import PrimaryKeyRelatedField, ManyRelatedField
12
+ from .drf_braces.serializers.form_serializer import (
13
+ FormSerializer, FormSerializerBase, reduce_attr_dict_from_instance,
14
+ FORM_SERIALIZER_FIELD_MAPPING, set_form_partial_validation
15
+ )
16
+ from .forms import ComponentAdminForm
17
+ from .models import Category, Zone, Icon, ComponentHistory
7
18
 
8
19
 
9
20
  class TimestampField(serializers.Field):
@@ -13,6 +24,9 @@ class TimestampField(serializers.Field):
13
24
  return value.timestamp()
14
25
  return value
15
26
 
27
+ def to_internal_value(self, data):
28
+ return datetime.datetime.fromtimestamp(data)
29
+
16
30
 
17
31
  class IconSerializer(serializers.ModelSerializer):
18
32
  last_modified = TimestampField()
@@ -23,13 +37,15 @@ class IconSerializer(serializers.ModelSerializer):
23
37
 
24
38
 
25
39
  class CategorySerializer(serializers.ModelSerializer):
26
- header_image = serializers.SerializerMethodField()
40
+ header_image_thumb = serializers.SerializerMethodField()
27
41
 
28
42
  class Meta:
29
43
  model = Category
30
- fields = 'id', 'name', 'all', 'icon', 'header_image'
44
+ fields = (
45
+ 'id', 'name', 'all', 'icon', 'header_image', 'header_image_thumb'
46
+ )
31
47
 
32
- def get_header_image(self, obj):
48
+ def get_header_image_thumb(self, obj):
33
49
  if obj.header_image:
34
50
  url = get_thumbnailer(obj.header_image).get_thumbnail(
35
51
  {'size': (830, 430), 'crop': True}
@@ -44,25 +60,318 @@ class CategorySerializer(serializers.ModelSerializer):
44
60
  return
45
61
 
46
62
 
47
- class ComponentSerializer(serializers.ModelSerializer):
63
+
64
+ class ObjectSerializerMethodField(serializers.SerializerMethodField):
65
+
66
+ def bind(self, field_name, parent):
67
+ self.field_name = field_name
68
+ super().bind(field_name, parent)
69
+
70
+ def to_representation(self, value):
71
+ return getattr(value, self.field_name)
72
+
73
+
74
+ class FormsetPrimaryKeyRelatedField(PrimaryKeyRelatedField):
75
+
76
+ def get_attribute(self, instance):
77
+ return self.queryset.model.objects.filter(
78
+ pk=instance.get(self.source_attrs[0], -1)
79
+ ).first()
80
+
81
+
82
+
83
+
84
+ # TODO: if form field has initial value and is required, it is serialized as not required field, howerver when trying to submit it fails with a message, that field is required.
85
+
86
+
87
+ class ComponentFormsetField(FormSerializer):
88
+
89
+ class Meta:
90
+ # fake form, but it is necessary for FormSerializer
91
+ # we set it to proper formset form on __init__
92
+ form = forms.Form
93
+ field_mapping = {
94
+ forms.ModelChoiceField: FormsetPrimaryKeyRelatedField,
95
+ forms.TypedChoiceField: serializers.ChoiceField,
96
+ forms.FloatField: serializers.FloatField,
97
+ forms.SlugField: serializers.CharField
98
+ }
99
+
100
+ def __init__(self, formset_field, *args, **kwargs):
101
+ self.Meta.form = formset_field.formset_cls.form
102
+ super().__init__(*args, **kwargs)
103
+
104
+ def get_fields(self):
105
+ ret = super(FormSerializerBase, self).get_fields()
106
+
107
+ field_mapping = reduce_attr_dict_from_instance(
108
+ self,
109
+ lambda i: getattr(getattr(i, 'Meta', None), 'field_mapping', {}),
110
+ FORM_SERIALIZER_FIELD_MAPPING
111
+ )
112
+
113
+ form = self.Meta.form
114
+ for field_name, form_field in getattr(form, 'all_base_fields', form.base_fields).items():
115
+
116
+ if field_name in getattr(self.Meta, 'exclude', []):
117
+ continue
118
+
119
+ if field_name in ret:
120
+ continue
121
+
122
+ cls_type = form_field.__class__
123
+ try:
124
+ serializer_field_class = field_mapping[cls_type]
125
+ except KeyError:
126
+ try:
127
+ serializer_field_class = field_mapping[cls_type.__bases__[0]]
128
+ except KeyError:
129
+ raise TypeError(
130
+ "{field} is not mapped to a serializer field. "
131
+ "Please add {field} to {serializer}.Meta.field_mapping. "
132
+ "Currently mapped fields: {mapped}".format(
133
+ field=form_field.__class__.__name__,
134
+ serializer=self.__class__.__name__,
135
+ mapped=', '.join(sorted([i.__name__ for i in field_mapping.keys()]))
136
+ )
137
+ )
138
+
139
+ ret[field_name] = self._get_field(form_field, serializer_field_class)
140
+
141
+ return ret
142
+
143
+ def _get_field_kwargs(self, form_field, serializer_field_class):
144
+ kwargs = super()._get_field_kwargs(form_field, serializer_field_class)
145
+ kwargs['style'] = {'form_field': form_field}
146
+ if serializer_field_class == FormsetPrimaryKeyRelatedField:
147
+ kwargs['queryset'] = form_field.queryset
148
+ return kwargs
149
+
150
+ def to_representation(self, instance):
151
+ return super(FormSerializerBase, self).to_representation(instance)
152
+
153
+ def get_form(self, data=None, **kwargs):
154
+ form = super().get_form(data=data, **kwargs)
155
+ form.prefix = ''
156
+ return form
157
+
158
+ def create(self, validated_data):
159
+ return validated_data
160
+
161
+
162
+ class ComponentPrimaryKeyRelatedField(PrimaryKeyRelatedField):
163
+
164
+ def get_attribute(self, instance):
165
+ if self.queryset.model in (Icon, Zone, Category):
166
+ return super().get_attribute(instance)
167
+ return self.queryset.model.objects.filter(
168
+ pk=instance.config.get(self.source_attrs[0])
169
+ ).first()
170
+
171
+
172
+ class ComponentManyToManyRelatedField(serializers.Field):
173
+
174
+ def __init__(self, *args, **kwargs):
175
+ self.queryset = kwargs.pop('queryset')
176
+ self.choices = {obj.pk: str(obj) for obj in self.queryset}
177
+ self.allow_blank = kwargs.pop('allow_blank', False)
178
+ super().__init__(*args, **kwargs)
179
+
180
+ def to_representation(self, value):
181
+ return [obj.pk for obj in value]
182
+
183
+ def to_internal_value(self, data):
184
+ if data == [] and self.allow_blank:
185
+ return []
186
+ return self.queryset.filter(pk__in=data)
187
+
188
+
189
+ class ComponentSerializer(FormSerializer):
190
+ id = ObjectSerializerMethodField()
48
191
  controller_methods = serializers.SerializerMethodField()
49
- last_change = TimestampField()
192
+ last_change = TimestampField(read_only=True)
50
193
  read_only = serializers.SerializerMethodField()
51
194
  app_widget = serializers.SerializerMethodField()
52
- subcomponents = serializers.SerializerMethodField()
195
+ slaves = serializers.SerializerMethodField()
196
+ base_type = ObjectSerializerMethodField()
197
+ controller_uid = ObjectSerializerMethodField()
198
+ alive = ObjectSerializerMethodField()
199
+ value = ObjectSerializerMethodField()
200
+ config = ObjectSerializerMethodField()
201
+ meta = ObjectSerializerMethodField()
202
+ arm_status = ObjectSerializerMethodField()
203
+ battery_level = ObjectSerializerMethodField()
53
204
 
54
205
  class Meta:
55
- model = Component
56
- fields = [
57
- 'id', 'name', 'icon', 'zone',
58
- 'base_type', 'app_widget',
59
- 'category', 'alive',
60
- 'value', 'value_units',
61
- 'config', 'meta', 'controller_methods',
62
- 'alarm_category', 'arm_status', 'last_change',
63
- 'read_only', 'show_in_app', 'battery_level',
64
- 'subcomponents'
65
- ]
206
+ form = ComponentAdminForm
207
+ exclude = ('instance_methods', )
208
+ field_mapping = {
209
+ forms.TypedChoiceField: serializers.ChoiceField,
210
+ forms.FloatField: serializers.FloatField,
211
+ forms.SlugField: serializers.CharField,
212
+ forms.ModelChoiceField: ComponentPrimaryKeyRelatedField,
213
+ forms.ModelMultipleChoiceField: ComponentManyToManyRelatedField,
214
+ FormsetField: ComponentFormsetField,
215
+ }
216
+
217
+ def get_fields(self):
218
+ self.set_form_cls()
219
+
220
+ ret = super(FormSerializerBase, self).get_fields()
221
+
222
+ field_mapping = reduce_attr_dict_from_instance(
223
+ self,
224
+ lambda i: getattr(getattr(i, 'Meta', None), 'field_mapping', {}),
225
+ FORM_SERIALIZER_FIELD_MAPPING
226
+ )
227
+
228
+ if not self.instance or isinstance(self.instance, Iterable):
229
+ form = self.Meta.form()
230
+ else:
231
+ form = self.Meta.form(instance=self.instance)
232
+ for field_name in form.fields:
233
+ # if field is specified as excluded field
234
+ if field_name in getattr(self.Meta, 'exclude', []):
235
+ continue
236
+
237
+ # if field is already defined via declared fields
238
+ # skip mapping it from forms which then honors
239
+ # the custom validation defined on the DRF declared field
240
+ if field_name in ret:
241
+ continue
242
+
243
+ form_field = form[field_name]
244
+
245
+ cls = form_field.field.__class__
246
+ try:
247
+ serializer_field_class = field_mapping[cls]
248
+ except KeyError:
249
+ cls = form_field.field.__class__.__bases__[0]
250
+ try:
251
+ serializer_field_class = field_mapping[cls]
252
+ except KeyError:
253
+ raise TypeError(
254
+ "{field} is not mapped to a serializer field. "
255
+ "Please add {field} to {serializer}.Meta.field_mapping. "
256
+ "Currently mapped fields: {mapped}".format(
257
+ field=form_field.field.__class__.__name__,
258
+ serializer=self.__class__.__name__,
259
+ mapped=', '.join(sorted([i.__name__ for i in field_mapping.keys()]))
260
+ )
261
+ )
262
+
263
+ ret[field_name] = self._get_field(
264
+ form_field.field, serializer_field_class
265
+ )
266
+ ret[field_name].initial = form_field.initial
267
+ ret[field_name].default = form_field.initial
268
+
269
+ return ret
270
+
271
+ def _get_field_kwargs(self, form_field, serializer_field_class):
272
+ kwargs = super()._get_field_kwargs(form_field, serializer_field_class)
273
+ kwargs['style'] = {'form_field': form_field}
274
+ if serializer_field_class == ComponentPrimaryKeyRelatedField:
275
+ kwargs['queryset'] = form_field.queryset
276
+ elif serializer_field_class == ComponentManyToManyRelatedField:
277
+ kwargs['queryset'] = form_field.queryset
278
+ elif serializer_field_class == ComponentFormsetField:
279
+ kwargs['formset_field'] = form_field
280
+ kwargs['many'] = True
281
+
282
+ return kwargs
283
+
284
+ def set_form_cls(self):
285
+ self.Meta.form = ComponentAdminForm
286
+ if not isinstance(self.instance, Iterable):
287
+ from .utils.type_constants import get_controller_types_map
288
+ controllers_map = get_controller_types_map()
289
+ if not self.instance:
290
+ controller = controllers_map.get(
291
+ #'simo.generic.controllers.AlarmClock'
292
+ self.context['request'].META.get('HTTP_CONTROLLER')
293
+ )
294
+ if controller:
295
+ self.Meta.form = controller.add_form
296
+ else:
297
+ controller = controllers_map.get(
298
+ self.instance.controller_uid
299
+ )
300
+ if controller:
301
+ self.Meta.form = controller.config_form
302
+
303
+ def get_form(self, data=None, **kwargs):
304
+ self.set_form_cls()
305
+ if not self.instance:
306
+ #controller_uid = 'simo.generic.controllers.AlarmClock'
307
+ controller_uid = self.context['request'].META.get('HTTP_CONTROLLER')
308
+ else:
309
+ controller_uid = self.instance.controller_uid
310
+ form = self.Meta.form(
311
+ data=data, request=self.context['request'],
312
+ controller_uid=controller_uid,
313
+ **kwargs
314
+ )
315
+ return form
316
+
317
+ def accomodate_formsets(self, form, data):
318
+ new_data = {}
319
+ field_types = {}
320
+ for field_name in form.fields:
321
+ field_types[field_name] = form[field_name]
322
+ for key, val in data.items():
323
+ if isinstance(field_types.get(key).field, FormsetField):
324
+ new_data[f'{key}-TOTAL_FORMS'] = len(val)
325
+ new_data[f'{key}-INITIAL_FORMS'] = len(val)
326
+ new_data[f'{key}-MIN_NUM_FORMS'] = 0
327
+ new_data[f'{key}-MAX_NUM_FORMS'] = len(val)
328
+ for i, item in enumerate(val):
329
+ for k, v in item.items():
330
+ new_data[f'{key}-{i}-{k}'] = v
331
+ else:
332
+ new_data[key] = val
333
+ return new_data
334
+
335
+ def validate(self, data):
336
+ if not self.instance:
337
+ try:
338
+ self.context['request'].META['HTTP_CONTROLLER']
339
+ except:
340
+ raise serializers.ValidationError(
341
+ ["Controller header is not supplied!"]
342
+ )
343
+ form = self.get_form(instance=self.instance)
344
+ a_data = self.accomodate_formsets(form, data)
345
+ form = self.get_form(
346
+ data=a_data, instance=self.instance
347
+ )
348
+ if not form.is_valid():
349
+ raise serializers.ValidationError(form.errors)
350
+ return data
351
+
352
+ def to_representation(self, instance):
353
+ return super(FormSerializerBase, self).to_representation(instance)
354
+
355
+ def update(self, instance, validated_data):
356
+ form = self.get_form(instance=instance)
357
+ a_data = self.accomodate_formsets(form, validated_data)
358
+ form = self.get_form(instance=instance, data=a_data)
359
+ if form.is_valid():
360
+ instance = form.save(commit=True)
361
+ return instance
362
+ raise serializers.ValidationError(form.errors)
363
+
364
+ def create(self, validated_data):
365
+ form = self.get_form()
366
+ a_data = self.accomodate_formsets(form, validated_data)
367
+ form = self.get_form(data=a_data)
368
+ if form.is_valid():
369
+ if form.controller.is_discoverable:
370
+ form.controller.init_discovery(form.cleaned_data)
371
+ return {'discovery': 'started'}
372
+ instance = form.save(commit=True)
373
+ return instance
374
+ raise serializers.ValidationError(form.errors)
66
375
 
67
376
  def get_controller_methods(self, obj):
68
377
  c_methods = [m[0] for m in inspect.getmembers(
@@ -92,13 +401,8 @@ class ComponentSerializer(serializers.ModelSerializer):
92
401
  return {}
93
402
  return {'type': app_widget.uid, 'size': app_widget.size}
94
403
 
95
- def get_subcomponents(self, obj):
96
- from simo.users.utils import get_system_user
97
- return ComponentSerializer(
98
- obj.subcomponents.all(), many=True, context={
99
- 'user': get_system_user()
100
- }
101
- ).data
404
+ def get_slaves(self, obj):
405
+ return [c['id'] for c in obj.slaves.all().values('id')]
102
406
 
103
407
 
104
408
  class ZoneSerializer(serializers.ModelSerializer):
@@ -1,29 +1,17 @@
1
1
  import json
2
- import time
3
- import pytz
4
2
  import asyncio
5
- import os
6
- import logging
7
3
  from ansi2html import Ansi2HTMLConverter
8
4
  from asgiref.sync import sync_to_async
9
- from django.urls import set_script_prefix
10
- from django.core.exceptions import ValidationError
11
5
  from django.template.loader import render_to_string
12
6
  from django.conf import settings
13
- from django.utils import timezone
14
7
  from django.contrib.contenttypes.models import ContentType
15
8
  from channels.generic.websocket import AsyncWebsocketConsumer, WebsocketConsumer
16
9
  from simo.core.events import ObjectChangeEvent, get_event_obj
17
10
  import paho.mqtt.client as mqtt
18
11
  from simo.users.middleware import introduce
19
- from simo.core.api import get_components_queryset
20
12
  from simo.core.models import Component, Gateway
21
- from simo.core.serializers import ComponentSerializer
22
- from simo.conf import dynamic_settings
23
- from simo.users.models import User, PermissionsRole
24
- from simo.users.serializers import UserSerializer
25
- from simo.users.api import UsersViewSet
26
13
  from simo.core.utils.model_helpers import get_log_file_path
14
+ from simo.core.middleware import introduce_instance
27
15
 
28
16
 
29
17
  class SIMOWebsocketConsumer(WebsocketConsumer):
@@ -220,6 +208,8 @@ class ComponentController(SIMOWebsocketConsumer):
220
208
  if not self.scope['user'].is_active:
221
209
  return self.close()
222
210
 
211
+ introduce_instance(self.component.zone.instance)
212
+
223
213
  self._mqtt_client = mqtt.Client()
224
214
  self._mqtt_client.username_pw_set('root', settings.SECRET_KEY)
225
215
  self._mqtt_client.on_connect = self._on_mqtt_connect
@@ -228,7 +218,6 @@ class ComponentController(SIMOWebsocketConsumer):
228
218
  port=settings.MQTT_PORT)
229
219
  self._mqtt_client.loop_start()
230
220
 
231
-
232
221
  def _on_mqtt_connect(self, mqtt_client, userdata, flags, rc):
233
222
  print("Subscribing to ComponentEvent's")
234
223
  event = ObjectChangeEvent(self.component.zone.instance, self.component)
@@ -238,6 +227,7 @@ class ComponentController(SIMOWebsocketConsumer):
238
227
  payload = json.loads(msg.payload)
239
228
  component = get_event_obj(payload, Component)
240
229
  if component == self.component:
230
+ introduce_instance(self.component.zone.instance)
241
231
  # print("Object changed [%s], %s" % (str(component), payload))
242
232
  self.component = component
243
233
  if self.send_value:
@@ -254,6 +244,7 @@ class ComponentController(SIMOWebsocketConsumer):
254
244
 
255
245
  def receive(self, text_data=None, bytes_data=None, **kwargs):
256
246
  introduce(self.scope['user'])
247
+ introduce_instance(self.component.zone.instance)
257
248
  json_data = json.loads(text_data)
258
249
  self.send_value = json_data.pop('send_value', False)
259
250
  for method, param in json_data.items():
simo/core/tasks.py CHANGED
@@ -192,7 +192,7 @@ def sync_with_remote():
192
192
  ).first()
193
193
  if weather_component:
194
194
  weather_component.track_history = False
195
- weather_component.set(data['weather_forecast'])
195
+ weather_component.controller.set(data['weather_forecast'])
196
196
 
197
197
  instance.save()
198
198
 
@@ -313,6 +313,16 @@ def drop_fingerprints_learn():
313
313
  )
314
314
 
315
315
 
316
+ @celery_app.task
317
+ def time_out_discoveries():
318
+ from .models import Gateway
319
+ for gw in Gateway.objects.filter(
320
+ discovery__has_key='start'
321
+ ).exclude(discovery__has_key='finished'):
322
+ if time.time() - gw.discovery['start'] > gw.discovery['timeout']:
323
+ gw.finish_discovery()
324
+
325
+
316
326
  @celery_app.on_after_finalize.connect
317
327
  def setup_periodic_tasks(sender, **kwargs):
318
328
  sender.add_periodic_task(1, watch_timers.s())
@@ -81,28 +81,50 @@
81
81
  <body class="{% if is_popup %}popup {% endif %}{% block bodyclass %}{% endblock %}"
82
82
  data-admin-utc-offset="{% now "Z" %}">
83
83
 
84
+
85
+ <style>
86
+ #instance-select{
87
+ background: none;
88
+ color: #f3ff4a;
89
+ font-family: 'Russo One';
90
+ font-size: 1rem;
91
+ border: none;
92
+ }
93
+ #instance-select option{
94
+ background-color: #08172a;
95
+ }
96
+ </style>
84
97
  <!-- Container -->
85
98
  <div id="container">
86
99
 
87
100
  {% if not is_popup %}
88
101
  <!-- Header -->
89
- <div id="header">
90
- <a href="/admin/">
91
- <div id="branding" style="background-image: url({% static 'logo/logo_color_bg_b.svg' %})"></div>
92
-
102
+ <div id="header" style="display:flex">
103
+ <div style="display:flex">
104
+ <a href="/admin/" >
105
+ <div id="branding" style="background-image: url({% static 'logo/logo_color_bg_b.svg' %})"></div>
106
+ </a>
93
107
  <div style="
94
108
  color: white;
95
109
  font-size: 16px;
96
- padding: 11px 0px;
110
+ padding: 7px 0px;
97
111
  margin-top: 4px;
98
- display: inline-block;
99
112
  ">
100
113
  <span style="display: inline-block; margin-right: 6px">|</span>
101
- <span style="display: inline-block; font-family: 'Russo One'; color: #f3ff4a;">
102
- {% if instances|length == 1 %}{{ instances.0.name }}{% else %}Hub Admin{% endif %}
103
- </span>
114
+ <select id="instance-select">
115
+ {% for instance in instances %}
116
+ <option
117
+ data-set_url="{% url 'set-instance' instance_slug=instance.slug %}"
118
+ {% if instance == current_instance %} selected {% endif %}
119
+ >{{ instance }}</option>
120
+ {% endfor %}
121
+ </select>
122
+ <!-- <span style="display: inline-block; font-family: 'Russo One'; color: #f3ff4a;">-->
123
+ <!-- {% if instances|length == 1 %}{{ instances.0.name }}{% else %}Hub Admin{% endif %}-->
124
+ <!-- </span>-->
104
125
  </div>
105
- </a>
126
+ </div>
127
+
106
128
  {% block usertools %}
107
129
  {% include 'admin/user_tools.html' %}
108
130
  {% endblock %}
@@ -146,5 +168,10 @@
146
168
  <script src="{% static 'third_party/precision-inputs/precision-inputs.fl-controls.js' %}"></script>
147
169
  <script src="{% static 'admin/js/admin_scripts.js' %}"></script>
148
170
 
171
+ <script>
172
+ $('#instance-select').on('change', function(){
173
+ window.location = $(this).find('option:selected').data('set_url');
174
+ })
175
+ </script>
149
176
  </body>
150
177
  </html>