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/middleware.py CHANGED
@@ -14,6 +14,26 @@ def get_current_request():
14
14
  pass
15
15
 
16
16
 
17
+ def introduce_instance(instance, request=None):
18
+ _thread_locals.instance = instance
19
+ if request:
20
+ request.session['instance_id'] = instance.id
21
+
22
+
23
+ def get_current_instance(request=None):
24
+ instance = getattr(_thread_locals, 'instance', None)
25
+ if not instance and request and request.session.get('instance_id'):
26
+ from simo.core.models import Instance
27
+ instance = Instance.objects.filter(
28
+ id=request.session['instance_id']
29
+ ).first()
30
+ if not instance:
31
+ del request.session['instance_id']
32
+ else:
33
+ introduce_instance(instance, request)
34
+ return instance
35
+
36
+
17
37
  def simo_router_middleware(get_response):
18
38
 
19
39
  def middleware(request):
@@ -34,11 +54,15 @@ def instance_middleware(get_response):
34
54
  from simo.core.models import Instance
35
55
 
36
56
  instance = None
57
+ # API calls
37
58
  if request.resolver_match:
38
59
  instance = Instance.objects.filter(
39
60
  slug=request.resolver_match.kwargs.get('instance_slug')
40
61
  ).first()
41
62
 
63
+ if not instance:
64
+ instance = get_current_instance(request)
65
+
42
66
  if not instance:
43
67
  if request.user.is_authenticated:
44
68
  if len(request.user.instances) == 1:
@@ -46,6 +70,7 @@ def instance_middleware(get_response):
46
70
  instance = inst
47
71
 
48
72
  if instance:
73
+ introduce_instance(instance, request)
49
74
  tz = pytz.timezone(instance.timezone)
50
75
  timezone.activate(tz)
51
76
 
@@ -0,0 +1,20 @@
1
+ # Generated by Django 3.2.9 on 2024-01-24 11:39
2
+
3
+ from django.db import migrations, models
4
+ import django.db.models.deletion
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ dependencies = [
10
+ ('core', '0025_auto_20240122_1321'),
11
+ ]
12
+
13
+ operations = [
14
+ migrations.AddField(
15
+ model_name='category',
16
+ name='instance',
17
+ field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='core.instance'),
18
+ preserve_default=False,
19
+ ),
20
+ ]
@@ -0,0 +1,17 @@
1
+ # Generated by Django 3.2.9 on 2024-01-25 12:04
2
+
3
+ from django.db import migrations
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('core', '0026_category_instance'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.RemoveField(
14
+ model_name='component',
15
+ name='tags',
16
+ ),
17
+ ]
@@ -0,0 +1,18 @@
1
+ # Generated by Django 3.2.9 on 2024-02-07 10:14
2
+
3
+ from django.db import migrations
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('core', '0027_remove_component_tags'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.RenameField(
14
+ model_name='component',
15
+ old_name='subcomponents',
16
+ new_name='slaves',
17
+ ),
18
+ ]
@@ -0,0 +1,33 @@
1
+ # Generated by Django 3.2.9 on 2024-02-29 13:31
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('core', '0028_rename_subcomponents_component_slaves'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AddField(
14
+ model_name='gateway',
15
+ name='discovery',
16
+ field=models.JSONField(blank=True, editable=False, null=True),
17
+ ),
18
+ migrations.AlterField(
19
+ model_name='component',
20
+ name='instance_methods',
21
+ field=models.TextField(blank=True, default='\n\ndef is_in_alarm(self):\n return bool(self.value)\n\n', help_text='Add your own component methods or override existing ones'),
22
+ ),
23
+ migrations.AlterField(
24
+ model_name='component',
25
+ name='slaves',
26
+ field=models.ManyToManyField(blank=True, null=True, related_name='masters', to='core.Component'),
27
+ ),
28
+ migrations.AlterField(
29
+ model_name='gateway',
30
+ name='type',
31
+ field=models.CharField(choices=[], db_index=True, max_length=200, unique=True),
32
+ ),
33
+ ]
simo/core/models.py CHANGED
@@ -1,9 +1,11 @@
1
1
  import os
2
2
  import sys
3
3
  import inspect
4
-
4
+ import time
5
+ from collections.abc import Iterable
5
6
  from django.utils.text import slugify
6
7
  from django.core.cache import cache
8
+ from django.utils.functional import cached_property
7
9
  from django.urls import reverse_lazy
8
10
  from django.utils.translation import gettext_lazy as _
9
11
  from django.db import models
@@ -21,6 +23,7 @@ from taggit.managers import TaggableManager
21
23
  from simo.core.utils.mixins import SimoAdminMixin
22
24
  from simo.core.storage import OverwriteStorage
23
25
  from simo.core.utils.validators import validate_svg
26
+ from .managers import ZonesManager, CategoriesManager, ComponentsManager
24
27
  from .events import GatewayObjectCommand, OnChangeMixin
25
28
 
26
29
 
@@ -112,6 +115,7 @@ class Instance(DirtyFieldsMixin, models.Model, SimoAdminMixin):
112
115
  User, null=True, blank=True, on_delete=models.SET_NULL
113
116
  )
114
117
 
118
+
115
119
  def __str__(self):
116
120
  return self.name
117
121
 
@@ -130,6 +134,7 @@ class Zone(DirtyFieldsMixin, models.Model, SimoAdminMixin):
130
134
  order = models.PositiveIntegerField(
131
135
  default=0, blank=False, null=False, db_index=True
132
136
  )
137
+ objects = ZonesManager()
133
138
 
134
139
  # TODO: Admin ordering not working via remote!
135
140
 
@@ -143,6 +148,7 @@ class Zone(DirtyFieldsMixin, models.Model, SimoAdminMixin):
143
148
 
144
149
 
145
150
  class Category(DirtyFieldsMixin, models.Model, SimoAdminMixin):
151
+ instance = models.ForeignKey(Instance, on_delete=models.CASCADE)
146
152
  name = models.CharField(_('name'), max_length=40)
147
153
  icon = models.ForeignKey(Icon, on_delete=models.SET_NULL, null=True)
148
154
  header_image = ThumbnailerImageField(
@@ -159,14 +165,13 @@ class Category(DirtyFieldsMixin, models.Model, SimoAdminMixin):
159
165
  order = models.PositiveIntegerField(
160
166
  default=0, blank=False, null=False, db_index=True
161
167
  )
162
-
168
+ objects = CategoriesManager()
163
169
 
164
170
  class Meta:
165
171
  verbose_name = _("category")
166
172
  verbose_name_plural = _("categories")
167
173
  ordering = ('order', 'id')
168
174
 
169
-
170
175
  def __str__(self):
171
176
  return self.name
172
177
 
@@ -175,7 +180,9 @@ class Category(DirtyFieldsMixin, models.Model, SimoAdminMixin):
175
180
  dirty_fields = self.get_dirty_fields()
176
181
  if 'all' in dirty_fields:
177
182
  if self.all:
178
- Category.objects.all().update(all=False)
183
+ Category.objects.filter(
184
+ instance=self.instance
185
+ ).update(all=False)
179
186
  if 'header_image' in dirty_fields:
180
187
  self.header_image_last_change = timezone.now()
181
188
  return super().save(*args, **kwargs)
@@ -197,6 +204,11 @@ class Gateway(DirtyFieldsMixin, models.Model, SimoAdminMixin):
197
204
  (key, val) for key, val in RUN_STATUS_CHOICES_MAP.items()
198
205
  ),
199
206
  )
207
+ discovery = models.JSONField(
208
+ null=True, blank=True, editable=False
209
+ )
210
+
211
+
200
212
  handler = None
201
213
 
202
214
  def __str__(self):
@@ -243,6 +255,52 @@ class Gateway(DirtyFieldsMixin, models.Model, SimoAdminMixin):
243
255
  urlconf=settings.CHANNELS_URLCONF
244
256
  )
245
257
 
258
+ def start_discovery(self, controller_uid, init_data, timeout=None):
259
+ self.discovery = {
260
+ 'start': time.time(),
261
+ 'timeout': timeout if timeout else 60,
262
+ 'controller_uid': controller_uid,
263
+ 'init_data': init_data,
264
+ 'result': []
265
+ }
266
+ self.save()
267
+
268
+ def retry_discovery(self):
269
+ self.discovery['start'] = time.time()
270
+ self.discovery.pop('finished', None)
271
+ self.save()
272
+
273
+ def process_discovery(self, data):
274
+ self.refresh_from_db()
275
+ from .utils.type_constants import get_controller_types_map
276
+ ControllerClass = get_controller_types_map().get(
277
+ self.discovery['controller_uid']
278
+ )
279
+ if ControllerClass and hasattr(
280
+ ControllerClass, '_process_discovery'
281
+ ):
282
+ result = ControllerClass._process_discovery(
283
+ started_with=self.discovery['init_data'], data=data
284
+ )
285
+ if result:
286
+ if not isinstance(result, dict) and isinstance(result, Iterable):
287
+ for res in result:
288
+ if isinstance(res, models.Model):
289
+ self.discovery['result'].append(res.pk)
290
+ else:
291
+ self.discovery['result'].append(res)
292
+ else:
293
+ if isinstance(result, models.Model):
294
+ self.discovery['result'].append(result.pk)
295
+ else:
296
+ self.discovery['result'].append(result)
297
+
298
+ self.save(update_fields=['discovery'])
299
+
300
+ def finish_discovery(self):
301
+ self.discovery['finished'] = time.time()
302
+ self.save(update_fields=['discovery'])
303
+
246
304
 
247
305
  class Component(DirtyFieldsMixin, models.Model, SimoAdminMixin, OnChangeMixin):
248
306
  name = models.CharField(
@@ -258,19 +316,12 @@ class Component(DirtyFieldsMixin, models.Model, SimoAdminMixin, OnChangeMixin):
258
316
  Category, related_name='components', on_delete=models.CASCADE,
259
317
  null=True, blank=True
260
318
  )
261
- tags = TaggableManager(blank=True)
262
- # TODO: Remove gateway instance from component.
263
- # There can't be two instances of same type gateway, therefore its
264
- # instance is only required to deliver configuration and
265
- # background service responsible for components management.
266
- # Convert this to CharField for components filtering.
267
319
  gateway = models.ForeignKey(
268
320
  Gateway, on_delete=models.CASCADE, related_name='components'
269
321
  )
270
322
  base_type = models.CharField(
271
323
  _("base type"), max_length=200, db_index=True#, choices=BASE_TYPE_CHOICES
272
324
  )
273
- # Rename to controller_uid
274
325
  controller_uid = models.CharField(
275
326
  _("type"), max_length=200, choices=(), db_index=True,
276
327
  )
@@ -282,9 +333,8 @@ class Component(DirtyFieldsMixin, models.Model, SimoAdminMixin, OnChangeMixin):
282
333
  value_previous = models.JSONField(null=True, blank=True, editable=False)
283
334
  value_units = models.CharField(max_length=100, null=True, blank=True)
284
335
 
285
- subcomponents = models.ManyToManyField(
286
- 'Component', null=True, blank=True, editable=False,
287
- related_name='masters'
336
+ slaves = models.ManyToManyField(
337
+ 'Component', null=True, blank=True, related_name='masters'
288
338
  )
289
339
 
290
340
  change_init_by = models.ForeignKey(
@@ -318,15 +368,6 @@ class Component(DirtyFieldsMixin, models.Model, SimoAdminMixin, OnChangeMixin):
318
368
  def is_in_alarm(self):
319
369
  return bool(self.value)
320
370
 
321
- def translate_before_send(self, value):
322
- '''Perform value translation just before sending it to device.'''
323
- return value
324
-
325
- def translate_before_set(self, value):
326
- '''Perform value translation just before value is set to component.
327
- Must return a valid value for this component type.'''
328
- return value
329
-
330
371
  """)
331
372
 
332
373
  alarm_category = models.CharField(
@@ -343,12 +384,13 @@ def translate_before_set(self, value):
343
384
  )
344
385
  )
345
386
 
387
+ objects = ComponentsManager()
388
+
346
389
  tracker = FieldTracker(fields=('value', 'arm_status'))
347
390
  # change this to False before saving to not record changes to history
348
391
  track_history = True
349
392
 
350
393
  controller_cls = None
351
- controller = None
352
394
 
353
395
  _mqtt_client = None
354
396
  _on_change_function = None
@@ -359,41 +401,8 @@ def translate_before_set(self, value):
359
401
  verbose_name_plural = _("Components")
360
402
  ordering = 'zone', 'base_type', 'name'
361
403
 
362
-
363
404
  def __init__(self, *args, **kwargs):
364
- from .utils.type_constants import (
365
- get_controller_types_map,
366
- get_controller_types_choices
367
- )
368
- self._meta.get_field('controller_uid').choices = \
369
- get_controller_types_choices()
370
405
  super().__init__(*args, **kwargs)
371
- if self.controller_uid and not self.controller:
372
- if self.id and not 'test' in sys.argv:
373
- try:
374
- self.controller_cls = cache.get('c_%d_contr_cls' % self.id)
375
- except:
376
- pass
377
- if not self.controller_cls:
378
- self.controller_cls = get_controller_types_map(
379
- self.gateway
380
- ).get(self.controller_uid)
381
- if self.controller_cls and self.id and not 'test' in sys.argv:
382
- cache.set(
383
- 'c_%d_contr_cls' % self.id,
384
- self.controller_cls, None
385
- )
386
-
387
- if self.controller_cls:
388
- self.controller = self.controller_cls(self)
389
- controller_methods = [m for m in inspect.getmembers(
390
- self.controller, predicate=inspect.ismethod
391
- ) if not m[0].startswith('_')]
392
- for method in controller_methods:
393
- setattr(self, method[0], method[1])
394
- if not self.id:
395
- self.value = self.controller.default_value
396
-
397
406
  # Goes in zero seconds!
398
407
  if self.instance_methods:
399
408
  custom_methods = {}
@@ -412,6 +421,43 @@ def translate_before_set(self, value):
412
421
  return '%s | %s' % (self.zone.name, self.name)
413
422
  return self.name
414
423
 
424
+ @cached_property
425
+ def controller(self):
426
+ from .utils.type_constants import (
427
+ get_controller_types_map,
428
+ get_controller_types_choices
429
+ )
430
+ self._meta.get_field('controller_uid').choices = \
431
+ get_controller_types_choices()
432
+ if self.controller_uid:
433
+ controller_cls = None
434
+ if self.id and not 'test' in sys.argv:
435
+ try:
436
+ controller_cls = cache.get('c_%d_contr_cls' % self.id)
437
+ except:
438
+ pass
439
+ if not controller_cls:
440
+ controller_cls = get_controller_types_map(
441
+ self.gateway
442
+ ).get(self.controller_uid)
443
+ if controller_cls and self.id and not 'test' in sys.argv:
444
+ cache.set(
445
+ 'c_%d_contr_cls' % self.id,
446
+ controller_cls, None
447
+ )
448
+ if controller_cls:
449
+ return controller_cls(self)
450
+
451
+ def prepare_controller(self):
452
+ if not self.controller:
453
+ return
454
+ controller_methods = [m for m in inspect.getmembers(
455
+ self.controller, predicate=inspect.ismethod
456
+ ) if not m[0].startswith('_')]
457
+ for method in controller_methods:
458
+ setattr(self, method[0], method[1])
459
+ if not self.id:
460
+ self.value = self.controller.default_value
415
461
 
416
462
  def get_socket_url(self):
417
463
  return reverse_lazy(
@@ -472,15 +518,6 @@ def translate_before_set(self, value):
472
518
  def is_in_alarm(self):
473
519
  return bool(self.value)
474
520
 
475
- def translate_before_send(self, value):
476
- '''Perform value translation just before sending it to device.'''
477
- return value
478
-
479
- def translate_before_set(self, value):
480
- '''Perform value translation just before value is set to component.
481
- Must return a valid value for this component type.'''
482
- return value
483
-
484
521
  def can_read(self, user):
485
522
  if user.is_superuser:
486
523
  return True
simo/core/permissions.py CHANGED
@@ -1,5 +1,5 @@
1
- from rest_framework.permissions import BasePermission
2
- from .models import Instance
1
+ from rest_framework.permissions import BasePermission, SAFE_METHODS
2
+ from .models import Instance, Category, Zone
3
3
 
4
4
 
5
5
  class InstancePermission(BasePermission):
@@ -21,3 +21,29 @@ class InstancePermission(BasePermission):
21
21
  return False
22
22
 
23
23
  return True
24
+
25
+
26
+ class IsInstanceSuperuser(BasePermission):
27
+
28
+ def has_permission(self, request, view):
29
+ if request.user.is_master:
30
+ return True
31
+ user_role = request.user.get_role(view.instance)
32
+ return user_role.is_superuser
33
+
34
+
35
+ class InstanceSuperuserCanEdit(BasePermission):
36
+
37
+ def has_object_permission(self, request, view, obj):
38
+
39
+ # allow deleting only empty categories and zones
40
+ if type(obj) in (Zone, Category) and request.method == 'DELETE'\
41
+ and obj.components.all().count():
42
+ return False
43
+
44
+ if request.user.is_master:
45
+ return True
46
+ user_role = request.user.get_role(view.instance)
47
+ if user_role.is_superuser:
48
+ return True
49
+ return request.method in SAFE_METHODS