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

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

Potentially problematic release.


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

Files changed (267) hide show
  1. simo/__pycache__/asgi.cpython-38.pyc +0 -0
  2. simo/__pycache__/settings.cpython-38.pyc +0 -0
  3. simo/__pycache__/urls.cpython-38.pyc +0 -0
  4. simo/__pycache__/wsgi.cpython-38.pyc +0 -0
  5. simo/core/__pycache__/admin.cpython-38.pyc +0 -0
  6. simo/core/__pycache__/api.cpython-38.pyc +0 -0
  7. simo/core/__pycache__/api_meta.cpython-38.pyc +0 -0
  8. simo/core/__pycache__/auto_urls.cpython-38.pyc +0 -0
  9. simo/core/__pycache__/autocomplete_views.cpython-38.pyc +0 -0
  10. simo/core/__pycache__/base_types.cpython-38.pyc +0 -0
  11. simo/core/__pycache__/context.cpython-38.pyc +0 -0
  12. simo/core/__pycache__/controllers.cpython-38.pyc +0 -0
  13. simo/core/__pycache__/events.cpython-38.pyc +0 -0
  14. simo/core/__pycache__/forms.cpython-38.pyc +0 -0
  15. simo/core/__pycache__/gateways.cpython-38.pyc +0 -0
  16. simo/core/__pycache__/managers.cpython-38.pyc +0 -0
  17. simo/core/__pycache__/middleware.cpython-38.pyc +0 -0
  18. simo/core/__pycache__/models.cpython-38.pyc +0 -0
  19. simo/core/__pycache__/permissions.cpython-38.pyc +0 -0
  20. simo/core/__pycache__/serializers.cpython-38.pyc +0 -0
  21. simo/core/__pycache__/signal_receivers.cpython-38.pyc +0 -0
  22. simo/core/__pycache__/socket_consumers.cpython-38.pyc +0 -0
  23. simo/core/__pycache__/tasks.cpython-38.pyc +0 -0
  24. simo/core/__pycache__/views.cpython-38.pyc +0 -0
  25. simo/core/admin.py +28 -18
  26. simo/core/api.py +157 -16
  27. simo/core/api_meta.py +87 -0
  28. simo/core/auto_urls.py +4 -1
  29. simo/core/autocomplete_views.py +8 -4
  30. simo/core/base_types.py +1 -0
  31. simo/core/context.py +3 -1
  32. simo/core/controllers.py +112 -32
  33. simo/core/db_backend/base.py +7 -22
  34. simo/core/drf_braces/README +3 -0
  35. simo/core/drf_braces/__init__.py +7 -0
  36. simo/core/drf_braces/__pycache__/__init__.cpython-38.pyc +0 -0
  37. simo/core/drf_braces/__pycache__/utils.cpython-38.pyc +0 -0
  38. simo/core/drf_braces/fields/__init__.py +5 -0
  39. simo/core/drf_braces/fields/__pycache__/__init__.cpython-38.pyc +0 -0
  40. simo/core/drf_braces/fields/__pycache__/_fields.cpython-38.pyc +0 -0
  41. simo/core/drf_braces/fields/__pycache__/custom.cpython-38.pyc +0 -0
  42. simo/core/drf_braces/fields/__pycache__/mixins.cpython-38.pyc +0 -0
  43. simo/core/drf_braces/fields/__pycache__/modified.cpython-38.pyc +0 -0
  44. simo/core/drf_braces/fields/_fields.py +48 -0
  45. simo/core/drf_braces/fields/custom.py +107 -0
  46. simo/core/drf_braces/fields/mixins.py +58 -0
  47. simo/core/drf_braces/fields/modified.py +41 -0
  48. simo/core/drf_braces/forms/__init__.py +0 -0
  49. simo/core/drf_braces/forms/fields.py +20 -0
  50. simo/core/drf_braces/forms/serializer_form.py +156 -0
  51. simo/core/drf_braces/mixins.py +52 -0
  52. simo/core/drf_braces/models.py +0 -0
  53. simo/core/drf_braces/parsers.py +72 -0
  54. simo/core/drf_braces/renderers.py +37 -0
  55. simo/core/drf_braces/serializers/__init__.py +0 -0
  56. simo/core/drf_braces/serializers/__pycache__/__init__.cpython-38.pyc +0 -0
  57. simo/core/drf_braces/serializers/__pycache__/form_serializer.cpython-38.pyc +0 -0
  58. simo/core/drf_braces/serializers/enforce_validation_serializer.py +214 -0
  59. simo/core/drf_braces/serializers/form_serializer.py +391 -0
  60. simo/core/drf_braces/serializers/swapping.py +48 -0
  61. simo/core/drf_braces/tests/__init__.py +0 -0
  62. simo/core/drf_braces/tests/fields/__init__.py +0 -0
  63. simo/core/drf_braces/tests/fields/test_custom.py +94 -0
  64. simo/core/drf_braces/tests/fields/test_fields.py +13 -0
  65. simo/core/drf_braces/tests/fields/test_mixins.py +96 -0
  66. simo/core/drf_braces/tests/fields/test_modified.py +40 -0
  67. simo/core/drf_braces/tests/forms/__init__.py +0 -0
  68. simo/core/drf_braces/tests/forms/test_fields.py +46 -0
  69. simo/core/drf_braces/tests/forms/test_serializer_form.py +256 -0
  70. simo/core/drf_braces/tests/serializers/__init__.py +0 -0
  71. simo/core/drf_braces/tests/serializers/test_enforce_validation_serializer.py +169 -0
  72. simo/core/drf_braces/tests/serializers/test_form_serializer.py +387 -0
  73. simo/core/drf_braces/tests/serializers/test_swapping.py +40 -0
  74. simo/core/drf_braces/tests/test_mixins.py +111 -0
  75. simo/core/drf_braces/tests/test_parsers.py +73 -0
  76. simo/core/drf_braces/tests/test_renderers.py +23 -0
  77. simo/core/drf_braces/tests/test_utils.py +73 -0
  78. simo/core/drf_braces/utils.py +209 -0
  79. simo/core/events.py +3 -3
  80. simo/core/forms.py +79 -37
  81. simo/core/gateways.py +31 -14
  82. simo/core/management/commands/gateways_manager.py +0 -1
  83. simo/core/managers.py +81 -0
  84. simo/core/middleware.py +25 -0
  85. simo/core/migrations/0026_category_instance.py +20 -0
  86. simo/core/migrations/0027_remove_component_tags.py +17 -0
  87. simo/core/migrations/0028_rename_subcomponents_component_slaves.py +18 -0
  88. simo/core/migrations/0029_auto_20240229_1331.py +33 -0
  89. simo/core/migrations/__pycache__/0026_category_instance.cpython-38.pyc +0 -0
  90. simo/core/migrations/__pycache__/0027_remove_component_tags.cpython-38.pyc +0 -0
  91. simo/core/migrations/__pycache__/0028_rename_subcomponents_component_slaves.cpython-38.pyc +0 -0
  92. simo/core/migrations/__pycache__/0029_auto_20240229_1331.cpython-38.pyc +0 -0
  93. simo/core/models.py +103 -66
  94. simo/core/permissions.py +28 -2
  95. simo/core/serializers.py +330 -26
  96. simo/core/socket_consumers.py +5 -14
  97. simo/core/tasks.py +11 -1
  98. simo/core/templates/admin/base.html +37 -10
  99. simo/core/templates/admin/wizard/discovery.html +188 -0
  100. simo/core/templates/admin/wizard/wizard_add.html +5 -5
  101. simo/core/utils/__pycache__/serialization.cpython-38.pyc +0 -0
  102. simo/core/utils/admin.py +9 -2
  103. simo/core/utils/formsets.py +17 -16
  104. simo/core/utils/helpers.py +1 -0
  105. simo/core/utils/serialization.py +56 -0
  106. simo/core/utils/type_constants.py +1 -1
  107. simo/core/utils/validators.py +14 -1
  108. simo/core/views.py +13 -0
  109. simo/fleet/__pycache__/admin.cpython-38.pyc +0 -0
  110. simo/fleet/__pycache__/api.cpython-38.pyc +0 -0
  111. simo/fleet/__pycache__/auto_urls.cpython-38.pyc +0 -0
  112. simo/fleet/__pycache__/controllers.cpython-38.pyc +0 -0
  113. simo/fleet/__pycache__/forms.cpython-38.pyc +0 -0
  114. simo/fleet/__pycache__/gateways.cpython-38.pyc +0 -0
  115. simo/fleet/__pycache__/managers.cpython-38.pyc +0 -0
  116. simo/fleet/__pycache__/models.cpython-38.pyc +0 -0
  117. simo/fleet/__pycache__/serializers.cpython-38.pyc +0 -0
  118. simo/fleet/__pycache__/socket_consumers.cpython-38.pyc +0 -0
  119. simo/fleet/__pycache__/utils.cpython-38.pyc +0 -0
  120. simo/fleet/__pycache__/views.cpython-38.pyc +0 -0
  121. simo/fleet/admin.py +54 -25
  122. simo/fleet/api.py +59 -3
  123. simo/fleet/auto_urls.py +2 -3
  124. simo/fleet/controllers.py +199 -16
  125. simo/fleet/forms.py +325 -483
  126. simo/fleet/gateways.py +44 -2
  127. simo/fleet/managers.py +32 -0
  128. simo/fleet/migrations/0025_auto_20240130_1334.py +27 -0
  129. simo/fleet/migrations/0026_rename_i2cinterface_scl_pin_and_more.py +64 -0
  130. simo/fleet/migrations/0027_auto_20240306_0802.py +170 -0
  131. simo/fleet/migrations/0028_remove_i2cinterface_scl_pin_no_and_more.py +21 -0
  132. simo/fleet/migrations/0029_alter_i2cinterface_scl_pin_and_more.py +24 -0
  133. simo/fleet/migrations/0030_colonelpin_label_alter_colonel_type.py +24 -0
  134. simo/fleet/migrations/0031_alter_colonel_type.py +18 -0
  135. simo/fleet/migrations/__pycache__/0025_auto_20240130_1334.cpython-38.pyc +0 -0
  136. simo/fleet/migrations/__pycache__/0026_rename_i2cinterface_scl_pin_and_more.cpython-38.pyc +0 -0
  137. simo/fleet/migrations/__pycache__/0027_auto_20240306_0802.cpython-38.pyc +0 -0
  138. simo/fleet/migrations/__pycache__/0028_remove_i2cinterface_scl_pin_no_and_more.cpython-38.pyc +0 -0
  139. simo/fleet/migrations/__pycache__/0029_alter_i2cinterface_scl_pin_and_more.cpython-38.pyc +0 -0
  140. simo/fleet/migrations/__pycache__/0030_colonelpin_label_alter_colonel_type.cpython-38.pyc +0 -0
  141. simo/fleet/migrations/__pycache__/0031_alter_colonel_type.cpython-38.pyc +0 -0
  142. simo/fleet/models.py +134 -82
  143. simo/fleet/serializers.py +35 -1
  144. simo/fleet/socket_consumers.py +239 -76
  145. simo/fleet/utils.py +15 -53
  146. simo/fleet/views.py +28 -14
  147. simo/generic/controllers.py +13 -89
  148. simo/generic/forms.py +29 -18
  149. simo/generic/gateways.py +73 -2
  150. simo/generic/models.py +3 -3
  151. simo/multimedia/controllers.py +9 -8
  152. simo/settings.py +7 -4
  153. simo/urls.py +4 -8
  154. simo/users/__pycache__/admin.cpython-38.pyc +0 -0
  155. simo/users/__pycache__/api.cpython-38.pyc +0 -0
  156. simo/users/__pycache__/auto_urls.cpython-38.pyc +0 -0
  157. simo/users/__pycache__/models.cpython-38.pyc +0 -0
  158. simo/users/__pycache__/serializers.cpython-38.pyc +0 -0
  159. simo/users/__pycache__/sso_urls.cpython-38.pyc +0 -0
  160. simo/users/admin.py +8 -1
  161. simo/users/api.py +38 -2
  162. simo/users/auto_urls.py +2 -2
  163. simo/users/migrations/0025_rename_name_fingerprint_type_and_more.py +22 -0
  164. simo/users/migrations/__pycache__/0025_rename_name_fingerprint_type_and_more.cpython-38.pyc +0 -0
  165. simo/users/models.py +2 -3
  166. simo/users/serializers.py +15 -1
  167. simo/users/sso_urls.py +3 -3
  168. simo/wsgi.py +7 -0
  169. {simo-1.7.20.dist-info → simo-2.0.0.dist-info}/METADATA +8 -9
  170. {simo-1.7.20.dist-info → simo-2.0.0.dist-info}/RECORD +173 -189
  171. {simo-1.7.20.dist-info → simo-2.0.0.dist-info}/WHEEL +1 -1
  172. simo/core/db_backend/__pycache__/__init__.cpython-38.pyc +0 -0
  173. simo/core/db_backend/__pycache__/base.cpython-38.pyc +0 -0
  174. simo/core/management/commands/__pycache__/gateways_manager.cpython-38.pyc +0 -0
  175. simo/core/migrations/__pycache__/0001_initial.cpython-38.pyc +0 -0
  176. simo/core/migrations/__pycache__/0002_load_icons.cpython-38.pyc +0 -0
  177. simo/core/migrations/__pycache__/0003_create_default_zones_and_categories.cpython-38.pyc +0 -0
  178. simo/core/migrations/__pycache__/0004_create_generic.cpython-38.pyc +0 -0
  179. simo/core/migrations/__pycache__/0005_component_subcomponents.cpython-38.pyc +0 -0
  180. simo/core/migrations/__pycache__/0006_alter_component_subcomponents.cpython-38.pyc +0 -0
  181. simo/core/migrations/__pycache__/0007_component_change_init_to.cpython-38.pyc +0 -0
  182. simo/core/migrations/__pycache__/0008_alter_component_change_init_to.cpython-38.pyc +0 -0
  183. simo/core/migrations/__pycache__/0009_auto_20220707_1404.cpython-38.pyc +0 -0
  184. simo/core/migrations/__pycache__/0010_historyaggregate.cpython-38.pyc +0 -0
  185. simo/core/migrations/__pycache__/0011_component_last_change.cpython-38.pyc +0 -0
  186. simo/core/migrations/__pycache__/0012_instance.cpython-38.pyc +0 -0
  187. simo/core/migrations/__pycache__/0013_auto_20231003_0754.cpython-38.pyc +0 -0
  188. simo/core/migrations/__pycache__/0014_zone_instance.cpython-38.pyc +0 -0
  189. simo/core/migrations/__pycache__/0015_auto_20231004_1113.cpython-38.pyc +0 -0
  190. simo/core/migrations/__pycache__/0016_auto_20231004_1113.cpython-38.pyc +0 -0
  191. simo/core/migrations/__pycache__/0017_auto_20231004_1313.cpython-38.pyc +0 -0
  192. simo/core/migrations/__pycache__/0018_auto_20231005_0622.cpython-38.pyc +0 -0
  193. simo/core/migrations/__pycache__/0019_alter_gateway_type.cpython-38.pyc +0 -0
  194. simo/core/migrations/__pycache__/0020_component_meta.cpython-38.pyc +0 -0
  195. simo/core/migrations/__pycache__/0021_auto_20231020_1041.cpython-38.pyc +0 -0
  196. simo/core/migrations/__pycache__/__init__.cpython-38.pyc +0 -0
  197. simo/core/templatetags/__pycache__/__init__.cpython-38.pyc +0 -0
  198. simo/core/templatetags/__pycache__/components_list.cpython-38.pyc +0 -0
  199. simo/core/utils/__pycache__/__init__.cpython-38.pyc +0 -0
  200. simo/core/utils/__pycache__/admin.cpython-38.pyc +0 -0
  201. simo/core/utils/__pycache__/config_values.cpython-38.pyc +0 -0
  202. simo/core/utils/__pycache__/easing.cpython-38.pyc +0 -0
  203. simo/core/utils/__pycache__/form_fields.cpython-38.pyc +0 -0
  204. simo/core/utils/__pycache__/form_widgets.cpython-38.pyc +0 -0
  205. simo/core/utils/__pycache__/formsets.cpython-38.pyc +0 -0
  206. simo/core/utils/__pycache__/helpers.cpython-38.pyc +0 -0
  207. simo/core/utils/__pycache__/logs.cpython-38.pyc +0 -0
  208. simo/core/utils/__pycache__/mixins.cpython-38.pyc +0 -0
  209. simo/core/utils/__pycache__/model_helpers.cpython-38.pyc +0 -0
  210. simo/core/utils/__pycache__/relay.cpython-38.pyc +0 -0
  211. simo/core/utils/__pycache__/type_constants.cpython-38.pyc +0 -0
  212. simo/core/utils/__pycache__/validators.cpython-38.pyc +0 -0
  213. simo/fleet/tasks.py +0 -25
  214. simo/generic/__pycache__/__init__.cpython-37.pyc +0 -0
  215. simo/generic/__pycache__/__init__.cpython-38.pyc +0 -0
  216. simo/generic/__pycache__/app_widgets.cpython-38.pyc +0 -0
  217. simo/generic/__pycache__/base_types.cpython-38.pyc +0 -0
  218. simo/generic/__pycache__/controllers.cpython-37.pyc +0 -0
  219. simo/generic/__pycache__/controllers.cpython-38.pyc +0 -0
  220. simo/generic/__pycache__/forms.cpython-38.pyc +0 -0
  221. simo/generic/__pycache__/gateways.cpython-38.pyc +0 -0
  222. simo/generic/__pycache__/models.cpython-38.pyc +0 -0
  223. simo/generic/__pycache__/routing.cpython-38.pyc +0 -0
  224. simo/generic/__pycache__/socket_consumers.cpython-38.pyc +0 -0
  225. simo/generic/__pycache__/widgets.cpython-37.pyc +0 -0
  226. simo/multimedia/__pycache__/__init__.cpython-38.pyc +0 -0
  227. simo/multimedia/__pycache__/admin.cpython-38.pyc +0 -0
  228. simo/multimedia/__pycache__/api.cpython-38.pyc +0 -0
  229. simo/multimedia/__pycache__/app_widgets.cpython-38.pyc +0 -0
  230. simo/multimedia/__pycache__/base_types.cpython-38.pyc +0 -0
  231. simo/multimedia/__pycache__/controllers.cpython-38.pyc +0 -0
  232. simo/multimedia/__pycache__/forms.cpython-38.pyc +0 -0
  233. simo/multimedia/__pycache__/models.cpython-38.pyc +0 -0
  234. simo/multimedia/__pycache__/serializers.cpython-38.pyc +0 -0
  235. simo/multimedia/migrations/__pycache__/0001_initial.cpython-38.pyc +0 -0
  236. simo/multimedia/migrations/__pycache__/0002_sound_length.cpython-38.pyc +0 -0
  237. simo/multimedia/migrations/__pycache__/0003_alter_sound_length.cpython-38.pyc +0 -0
  238. simo/multimedia/migrations/__pycache__/0004_auto_20231023_1055.cpython-38.pyc +0 -0
  239. simo/multimedia/migrations/__pycache__/__init__.cpython-38.pyc +0 -0
  240. simo/notifications/__pycache__/__init__.cpython-38.pyc +0 -0
  241. simo/notifications/__pycache__/admin.cpython-38.pyc +0 -0
  242. simo/notifications/__pycache__/api.cpython-38.pyc +0 -0
  243. simo/notifications/__pycache__/models.cpython-38.pyc +0 -0
  244. simo/notifications/__pycache__/serializers.cpython-38.pyc +0 -0
  245. simo/notifications/__pycache__/utils.cpython-38.pyc +0 -0
  246. simo/notifications/migrations/__pycache__/0001_initial.cpython-38.pyc +0 -0
  247. simo/notifications/migrations/__pycache__/0002_notification_instance.cpython-38.pyc +0 -0
  248. simo/notifications/migrations/__pycache__/__init__.cpython-38.pyc +0 -0
  249. simo/users/migrations/__pycache__/0001_initial.cpython-38.pyc +0 -0
  250. simo/users/migrations/__pycache__/0002_componentpermission.cpython-38.pyc +0 -0
  251. simo/users/migrations/__pycache__/0003_create_roles_and_system_user.cpython-38.pyc +0 -0
  252. simo/users/migrations/__pycache__/0004_user_secret_key.cpython-38.pyc +0 -0
  253. simo/users/migrations/__pycache__/0005_permissionsrole_instance.cpython-38.pyc +0 -0
  254. simo/users/migrations/__pycache__/0006_auto_20231003_0850.cpython-38.pyc +0 -0
  255. simo/users/migrations/__pycache__/0007_auto_20231003_1228.cpython-38.pyc +0 -0
  256. simo/users/migrations/__pycache__/0008_auto_20231003_1229.cpython-38.pyc +0 -0
  257. simo/users/migrations/__pycache__/0009_remove_user_role.cpython-38.pyc +0 -0
  258. simo/users/migrations/__pycache__/0010_auto_20231004_1313.cpython-38.pyc +0 -0
  259. simo/users/migrations/__pycache__/0011_auto_20231004_1313.cpython-38.pyc +0 -0
  260. simo/users/migrations/__pycache__/0012_alter_userinstancerole_unique_together.cpython-38.pyc +0 -0
  261. simo/users/migrations/__pycache__/0013_remove_user_roles.cpython-38.pyc +0 -0
  262. simo/users/migrations/__pycache__/0014_user_roles.cpython-38.pyc +0 -0
  263. simo/users/migrations/__pycache__/0015_remove_user_at_home.cpython-38.pyc +0 -0
  264. simo/users/migrations/__pycache__/0016_auto_20231005_1050.cpython-38.pyc +0 -0
  265. simo/users/migrations/__pycache__/__init__.cpython-38.pyc +0 -0
  266. {simo-1.7.20.dist-info → simo-2.0.0.dist-info}/LICENSE.md +0 -0
  267. {simo-1.7.20.dist-info → simo-2.0.0.dist-info}/top_level.txt +0 -0
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
simo/core/admin.py CHANGED
@@ -1,5 +1,6 @@
1
1
  from django.utils.translation import gettext_lazy as _
2
2
  from django.contrib import admin
3
+ from django.urls import reverse
3
4
  from easy_thumbnails.fields import ThumbnailerField
4
5
  from adminsortable2.admin import SortableAdminMixin
5
6
  from django.template.loader import render_to_string
@@ -21,6 +22,7 @@ from .forms import (
21
22
  )
22
23
  from .filters import ZonesFilter
23
24
  from .widgets import AdminImageWidget
25
+ from .middleware import get_current_instance
24
26
  from simo.conf import dynamic_settings
25
27
 
26
28
  csrf_protect_m = method_decorator(csrf_protect)
@@ -79,12 +81,6 @@ class ZoneAdmin(SortableAdminMixin, admin.ModelAdmin):
79
81
  search_fields = 'name',
80
82
  list_filter = 'instance',
81
83
 
82
- def get_queryset(self, request):
83
- qs = super().get_queryset(request)
84
- if request.user.is_master:
85
- return qs
86
- return qs.filter(instance__in=request.user.instances)
87
-
88
84
  def get_fields(self, request, obj=None):
89
85
  if request.user.is_master:
90
86
  return super().get_fields(request, obj)
@@ -271,7 +267,7 @@ class ComponentAdmin(admin.ModelAdmin):
271
267
  'control', 'value', 'arm_status', 'history', 'meta'
272
268
  )
273
269
  list_filter = (
274
- 'gateway', 'base_type', 'tags', ('zone', ZonesFilter), 'category', 'alive',
270
+ 'gateway', 'base_type', ('zone', ZonesFilter), 'category', 'alive',
275
271
  'alarm_category', 'arm_status'
276
272
  )
277
273
 
@@ -280,12 +276,6 @@ class ComponentAdmin(admin.ModelAdmin):
280
276
  change_list_template = 'admin/component_change_list.html'
281
277
  inlines = ComponentPermissionInline,
282
278
 
283
- def get_queryset(self, request):
284
- qs = super().get_queryset(request)
285
- if request.user.is_master:
286
- return qs
287
- return qs.filter(zone__instance__in=request.user.instances)
288
-
289
279
  def get_fieldsets(self, request, obj=None):
290
280
  form = self._get_form_for_get_fields(request, obj)
291
281
  fieldsets = form.get_admin_fieldsets(request, obj)
@@ -338,7 +328,6 @@ class ComponentAdmin(admin.ModelAdmin):
338
328
  )[request.session['add_comp_type']]
339
329
  except:
340
330
  request.session.pop('add_comp_type')
341
- print("No such controller type!")
342
331
  return redirect(request.path)
343
332
 
344
333
  add_form = controller_cls.add_form
@@ -350,6 +339,8 @@ class ComponentAdmin(admin.ModelAdmin):
350
339
  ):
351
340
  if field_neme in form.fields:
352
341
  form.fields.pop(field_neme)
342
+ if 'slaves' not in form.declared_fields:
343
+ form.fields.pop('slaves')
353
344
 
354
345
  ctx['is_last'] = True
355
346
  ctx['current_step'] = 3
@@ -359,13 +350,33 @@ class ComponentAdmin(admin.ModelAdmin):
359
350
  if request.method == 'POST':
360
351
  ctx['form'] = add_form(
361
352
  request=request,
362
- gateway=gateway,
363
- controller_cls=controller_cls,
353
+ controller_uid=controller_cls.uid,
364
354
  data=request.POST, files=request.FILES,
365
355
  initial=request.session.get('c_add_init'),
366
356
  )
367
357
  pop_fields_from_form(ctx['form'])
368
358
  if ctx['form'].is_valid():
359
+ if ctx['form'].controller.is_discoverable:
360
+ ctx['form'].controller.init_discovery(
361
+ ctx['form'].cleaned_data
362
+ )
363
+ ctx['discovery_msg'] = ctx['form'].controller.discovery_msg
364
+ instance = ctx['form'].cleaned_data['zone'].instance
365
+ ctx['api_check_url'] = reverse(
366
+ 'discoveries-list',
367
+ kwargs={'instance_slug': instance.slug}
368
+ ) + f"?controller_uid={ctx['form'].controller.uid}"
369
+ ctx['api_retry_url'] = reverse(
370
+ 'discoveries-list',
371
+ kwargs={'instance_slug': instance.slug}
372
+ ) + f"retry/?controller_uid={ctx['form'].controller.uid}"
373
+ ctx['api_components_url'] = reverse(
374
+ 'components-list',
375
+ kwargs={'instance_slug': instance.slug}
376
+ )
377
+ return render(
378
+ request, 'admin/wizard/discovery.html', ctx
379
+ )
369
380
  new_comp = ctx['form'].save()
370
381
  request.session.pop('add_comp_gateway')
371
382
  request.session.pop('add_comp_type')
@@ -373,8 +384,7 @@ class ComponentAdmin(admin.ModelAdmin):
373
384
  else:
374
385
  ctx['form'] = add_form(
375
386
  request=request,
376
- gateway=gateway,
377
- controller_cls=controller_cls,
387
+ controller_uid=controller_cls.uid,
378
388
  initial=request.session.get('c_add_init'),
379
389
  )
380
390
  pop_fields_from_form(ctx['form'])
simo/core/api.py CHANGED
@@ -1,38 +1,43 @@
1
1
  import datetime
2
2
  from calendar import monthrange
3
3
  import pytz
4
- import logging
4
+ import time
5
5
  from django.db.models import Q, Prefetch
6
6
  from django.utils.translation import gettext_lazy as _
7
7
  from django.utils import timezone
8
8
  from django.shortcuts import get_object_or_404
9
- from easy_thumbnails.files import get_thumbnailer
10
- from simo.core.utils.helpers import get_self_ip
9
+ from simo.core.utils.helpers import get_self_ip, search_queryset
11
10
  from rest_framework.pagination import PageNumberPagination
12
11
  from rest_framework import viewsets
13
12
  from django_filters.rest_framework import DjangoFilterBackend
14
13
  from rest_framework.decorators import action
15
14
  from rest_framework.response import Response as RESTResponse
16
- from django.core.exceptions import ValidationError
17
15
  from rest_framework.exceptions import ValidationError as APIValidationError
18
- from simo.conf import dynamic_settings
19
16
  from simo.core.utils.config_values import ConfigException
20
17
  from .models import (
21
18
  Instance, Category, Zone, Component, Icon, ComponentHistory,
22
- HistoryAggregate
19
+ HistoryAggregate, Gateway
23
20
  )
24
21
  from .serializers import (
25
22
  IconSerializer, CategorySerializer, ZoneSerializer,
26
23
  ComponentSerializer, ComponentHistorySerializer
27
24
  )
25
+ from .permissions import IsInstanceSuperuser, InstanceSuperuserCanEdit
28
26
 
29
27
 
30
28
  class InstanceMixin:
31
29
 
32
30
  def dispatch(self, request, *args, **kwargs):
33
- self.instance = Instance.objects.get(
34
- slug=self.request.resolver_match.kwargs.get('instance_slug')
35
- )
31
+ try:
32
+ self.instance = Instance.objects.get(
33
+ slug=self.request.resolver_match.kwargs.get('instance_slug')
34
+ )
35
+ except Instance.DoesNotExist:
36
+ raise APIValidationError(
37
+ f"Instance {self.request.resolver_match.kwargs.get('instance_slug')} "
38
+ "is not found on this SIMO.io hub!",
39
+ code=400
40
+ )
36
41
  return super().dispatch(request, *args, **kwargs)
37
42
 
38
43
  def get_serializer_context(self):
@@ -50,18 +55,51 @@ class IconViewSet(viewsets.ReadOnlyModelViewSet):
50
55
  def get_queryset(self):
51
56
  queryset = super().get_queryset()
52
57
  if 'slugs' in self.request.GET:
53
- queryset = queryset.filter(slug__in=self.request.GET['slugs'].split(','))
58
+ queryset = queryset.filter(
59
+ slug__in=self.request.GET['slugs'].split(',')
60
+ )
61
+ if 'q' in self.request.GET:
62
+ queryset = search_queryset(
63
+ queryset, self.request.GET['q'], ['slug', 'keywords']
64
+ )[:10]
54
65
  return queryset
55
66
 
67
+ def get_view_name(self):
68
+ singular = "Icon"
69
+ plural = "Icons"
70
+ suffix = getattr(self, 'suffix', None)
71
+ if suffix and suffix.lower() == 'list':
72
+ return plural
73
+ return singular
56
74
 
57
- class CategoryViewSet(viewsets.ReadOnlyModelViewSet):
75
+
76
+ class CategoryViewSet(InstanceMixin, viewsets.ModelViewSet):
58
77
  url = 'core/categories'
59
78
  basename = 'categories'
60
- queryset = Category.objects.all()
61
79
  serializer_class = CategorySerializer
62
80
 
81
+ def get_permissions(self):
82
+ permissions = super().get_permissions()
83
+ permissions.append(InstanceSuperuserCanEdit())
84
+ return permissions
85
+
86
+ def get_queryset(self):
87
+ return Category.objects.filter(instance=self.instance)
88
+
89
+ def get_view_name(self):
90
+ singular = "Category"
91
+ plural = "Categories"
92
+ suffix = getattr(self, 'suffix', None)
93
+ if suffix and suffix.lower() == 'list':
94
+ return plural
95
+ return singular
63
96
 
64
- class ZoneViewSet(InstanceMixin, viewsets.ReadOnlyModelViewSet):
97
+ def perform_create(self, serializer):
98
+ serializer.validated_data['instance'] = self.instance
99
+ serializer.save()
100
+
101
+
102
+ class ZoneViewSet(InstanceMixin, viewsets.ModelViewSet):
65
103
  url = 'core/zones'
66
104
  basename = 'zones'
67
105
  serializer_class = ZoneSerializer
@@ -69,6 +107,23 @@ class ZoneViewSet(InstanceMixin, viewsets.ReadOnlyModelViewSet):
69
107
  def get_queryset(self):
70
108
  return Zone.objects.filter(instance=self.instance)
71
109
 
110
+ def get_permissions(self):
111
+ permissions = super().get_permissions()
112
+ permissions.append(InstanceSuperuserCanEdit())
113
+ return permissions
114
+
115
+ def get_view_name(self):
116
+ singular = "Zone"
117
+ plural = "Zones"
118
+ suffix = getattr(self, 'suffix', None)
119
+ if suffix and suffix.lower() == 'list':
120
+ return plural
121
+ return singular
122
+
123
+ def perform_create(self, serializer):
124
+ serializer.validated_data['instance'] = self.instance
125
+ serializer.save()
126
+
72
127
 
73
128
  def get_components_queryset(instance, user):
74
129
  qs = Component.objects.filter(zone__instance=instance)
@@ -102,17 +157,31 @@ def get_components_queryset(instance, user):
102
157
  return qs
103
158
 
104
159
 
105
- class ComponentViewSet(InstanceMixin, viewsets.ReadOnlyModelViewSet):
160
+ class ComponentViewSet(InstanceMixin, viewsets.ModelViewSet):
106
161
  url = 'core/components'
107
162
  basename = 'components'
108
163
  serializer_class = ComponentSerializer
109
164
 
165
+ def get_permissions(self):
166
+ permissions = super().get_permissions()
167
+ permissions.append(InstanceSuperuserCanEdit())
168
+ return permissions
169
+
110
170
  def get_queryset(self):
111
171
  return get_components_queryset(self.instance, self.request.user).filter(
112
172
  zone__instance=self.instance
113
173
  )
114
174
 
175
+ def get_view_name(self):
176
+ singular = "Component"
177
+ plural = "Components"
178
+ suffix = getattr(self, 'suffix', None)
179
+ if suffix and suffix.lower() == 'list':
180
+ return plural
181
+ return singular
182
+
115
183
  def perform_controller_method(self, json_data, component):
184
+ component.prepare_controller()
116
185
  for method_name, param in json_data.items():
117
186
  if not hasattr(component, method_name):
118
187
  raise APIValidationError(
@@ -152,7 +221,7 @@ class ComponentViewSet(InstanceMixin, viewsets.ReadOnlyModelViewSet):
152
221
  json_data = request.data
153
222
  subcomponent_id = json_data.pop('id', -1)
154
223
  try:
155
- subcomponent = component.subcomponents.get(pk=subcomponent_id)
224
+ subcomponent = component.slaves.get(pk=subcomponent_id)
156
225
  except Component.DoesNotExist:
157
226
  raise APIValidationError(
158
227
  _('Subcomponent with id %d does not exist!' % str(subcomponent_id)),
@@ -463,7 +532,6 @@ class InfoViewSet(InstanceMixin, viewsets.GenericViewSet):
463
532
  permission_classes = []
464
533
 
465
534
  def list(self, request, format=None, *args, **kwargs):
466
- from simo.conf import dynamic_settings
467
535
  resp = RESTResponse({'uid': self.instance.uid})
468
536
  resp["Access-Control-Allow-Origin"] = "*"
469
537
  return resp
@@ -502,3 +570,76 @@ class StatesViewSet(InstanceMixin, viewsets.GenericViewSet):
502
570
  }
503
571
  ).data
504
572
  })
573
+
574
+
575
+ class ControllerTypes(InstanceMixin, viewsets.GenericViewSet):
576
+ url = 'core/controller-types'
577
+ basename = 'controller-types'
578
+ queryset = []
579
+
580
+ def get_permissions(self):
581
+ permissions = super().get_permissions()
582
+ permissions.append(IsInstanceSuperuser())
583
+ return permissions
584
+
585
+ def list(self, request, *args, **kwargs):
586
+ from .utils.type_constants import get_controller_types_map
587
+ data = {}
588
+
589
+ for uid, cls in get_controller_types_map().items():
590
+ if cls.gateway_class.name not in data:
591
+ data[cls.gateway_class.name] = []
592
+ data[cls.gateway_class.name].append({
593
+ 'uid': uid,
594
+ 'name': cls.name,
595
+ 'is_discoverable': cls.is_discoverable
596
+ })
597
+
598
+ return RESTResponse(data)
599
+
600
+
601
+ class RunningDiscoveries(InstanceMixin, viewsets.GenericViewSet):
602
+ url = 'core/discoveries'
603
+ basename = 'discoveries'
604
+ queryset = []
605
+
606
+
607
+ def get_permissions(self):
608
+ permissions = super().get_permissions()
609
+ permissions.append(IsInstanceSuperuser())
610
+ return permissions
611
+
612
+ def get_data(self, gateways):
613
+ data = []
614
+ for gateway in gateways:
615
+ data.append({
616
+ 'gateway': gateway.id,
617
+ 'start': gateway.discovery['start'],
618
+ 'controller_uid': gateway.discovery['controller_uid'],
619
+ 'result': gateway.discovery['result'],
620
+ 'finished': gateway.discovery.get('finished'),
621
+ })
622
+ return data
623
+
624
+ def get_gateways(self, request):
625
+ gateways = Gateway.objects.filter(
626
+ discovery__start__gt=time.time() - 60 * 60 # no more than an hour
627
+ )
628
+ if 'controller_uid' in request.GET:
629
+ gateways = gateways.filter(
630
+ discovery__controller_uid=request.GET['controller_uid']
631
+ )
632
+ return gateways
633
+
634
+ def list(self, request, *args, **kwargs):
635
+ return RESTResponse(self.get_data(self.get_gateways(request)))
636
+
637
+ @action(detail=False, methods=['get'])
638
+ def retry(self, request, *args, **kwargs):
639
+ gateways = self.get_gateways(request)
640
+ if 'controller_uid' in request.GET:
641
+ for gateway in gateways:
642
+ gateway.retry_discovery()
643
+ return RESTResponse(self.get_data(gateways))
644
+
645
+
simo/core/api_meta.py ADDED
@@ -0,0 +1,87 @@
1
+ from collections import OrderedDict
2
+ from django.utils.encoding import force_str
3
+ from rest_framework import serializers
4
+ from rest_framework.metadata import SimpleMetadata
5
+ from rest_framework.utils.field_mapping import ClassLookupDict
6
+ from .serializers import ComponentManyToManyRelatedField
7
+
8
+
9
+ class SIMOAPIMetadata(SimpleMetadata):
10
+
11
+ label_lookup = ClassLookupDict({
12
+ serializers.Field: 'field',
13
+ serializers.BooleanField: 'boolean',
14
+ serializers.CharField: 'string',
15
+ serializers.UUIDField: 'string',
16
+ serializers.URLField: 'url',
17
+ serializers.EmailField: 'email',
18
+ serializers.RegexField: 'regex',
19
+ serializers.SlugField: 'slug',
20
+ serializers.IntegerField: 'integer',
21
+ serializers.FloatField: 'float',
22
+ serializers.DecimalField: 'decimal',
23
+ serializers.DateField: 'date',
24
+ serializers.DateTimeField: 'datetime',
25
+ serializers.TimeField: 'time',
26
+ serializers.ChoiceField: 'choice',
27
+ serializers.MultipleChoiceField: 'multiple choice',
28
+ serializers.FileField: 'file upload',
29
+ serializers.ImageField: 'image upload',
30
+ serializers.ListField: 'list',
31
+ serializers.DictField: 'nested object',
32
+ serializers.Serializer: 'nested object',
33
+ serializers.RelatedField: 'related object',
34
+ serializers.ManyRelatedField: 'many related objects',
35
+ ComponentManyToManyRelatedField: 'many related objects'
36
+ })
37
+
38
+ def get_field_info(self, field):
39
+ """
40
+ Given an instance of a serializer field, return a dictionary
41
+ of metadata about it.
42
+ """
43
+ field_info = OrderedDict()
44
+ field_info['type'] = self.label_lookup[field]
45
+ field_info['required'] = getattr(field, 'required', False)
46
+
47
+ form_field = field.style.get('form_field')
48
+ if form_field:
49
+ if hasattr(form_field, 'queryset'):
50
+ model = form_field.queryset.model
51
+ field_info['related_object'] = ".".join(
52
+ [model.__module__, model.__name__]
53
+ )
54
+ if hasattr(form_field, 'filter_by'):
55
+ field_info['filter_by'] = form_field.filter_by
56
+
57
+ attrs = [
58
+ 'read_only', 'label', 'help_text',
59
+ 'min_length', 'max_length',
60
+ 'min_value', 'max_value',
61
+ 'initial'
62
+ ]
63
+
64
+ for attr in attrs:
65
+ value = getattr(field, attr, None)
66
+ if value is not None and value != '':
67
+ field_info[attr] = force_str(value, strings_only=True)
68
+
69
+ if getattr(field, 'child', None):
70
+ field_info['child'] = self.get_field_info(field.child)
71
+ elif getattr(field, 'fields', None):
72
+ field_info['children'] = self.get_serializer_info(field)
73
+
74
+ if not field_info.get('read_only') and hasattr(field, 'choices'):
75
+ add_choices = True
76
+ queryset = getattr(field, 'queryset', None)
77
+ if queryset and queryset.count() > 1000:
78
+ add_choices = False
79
+ if add_choices:
80
+ field_info['choices'] = [
81
+ {
82
+ 'value': choice_value,
83
+ 'display_name': force_str(choice_name, strings_only=True)
84
+ }
85
+ for choice_value, choice_name in field.choices.items()
86
+ ]
87
+ return field_info
simo/core/auto_urls.py CHANGED
@@ -1,5 +1,7 @@
1
1
  from django.urls import path
2
- from .views import get_timestamp, setup_wizard, update, restart, reboot
2
+ from .views import (
3
+ get_timestamp, setup_wizard, update, restart, reboot, set_instance
4
+ )
3
5
  from .autocomplete_views import (
4
6
  IconModelAutocomplete, #IconSlugAutocomplete,
5
7
  CategoryAutocomplete, ZoneAutocomplete,
@@ -28,6 +30,7 @@ urlpatterns = [
28
30
  'autocomplete-component',
29
31
  ComponentAutocomplete.as_view(), name='autocomplete-component'
30
32
  ),
33
+ path('set-instance/<slug:instance_slug>/', set_instance, name='set-instance'),
31
34
  path('setup/', setup_wizard, name='setup-wizard'),
32
35
  path('update/', update, name='update'),
33
36
  path('restart/', restart, name='restart'),
@@ -18,7 +18,7 @@ class IconModelAutocomplete(autocomplete.Select2QuerySetView):
18
18
 
19
19
  if self.q:
20
20
  qs = search_queryset(qs, self.q, ('slug', 'keywords'))
21
- return qs
21
+ return qs.distinct()
22
22
 
23
23
  def get_result_label(self, item):
24
24
  return render_to_string(
@@ -55,6 +55,7 @@ class CategoryAutocomplete(autocomplete.Select2QuerySetView):
55
55
  def get_queryset(self):
56
56
  qs = Category.objects.all()
57
57
 
58
+
58
59
  if not self.request.user.is_staff:
59
60
  return qs.none()
60
61
 
@@ -64,7 +65,7 @@ class CategoryAutocomplete(autocomplete.Select2QuerySetView):
64
65
  qs = qs.filter(all=False)
65
66
  if self.q:
66
67
  qs = search_queryset(qs, self.q, ('name'))
67
- return qs
68
+ return qs.distinct()
68
69
 
69
70
  def get_result_label(self, item):
70
71
  return render_to_string(
@@ -90,7 +91,7 @@ class ZoneAutocomplete(autocomplete.Select2QuerySetView):
90
91
 
91
92
  if self.q:
92
93
  qs = search_queryset(qs, self.q, ('name',))
93
- return qs
94
+ return qs.distinct()
94
95
 
95
96
  def get_result_label(self, item):
96
97
  return render_to_string(
@@ -119,6 +120,9 @@ class ComponentAutocomplete(autocomplete.Select2QuerySetView):
119
120
  if 'base_type' in self.forwarded:
120
121
  qs = qs.filter(base_type__in=self.forwarded['base_type'])
121
122
 
123
+ if 'controller_uid' in self.forwarded:
124
+ qs = qs.filter(controller_uid__in=self.forwarded['controller_uid'])
125
+
122
126
  if 'alarm_category' in self.forwarded:
123
127
  qs = qs.filter(
124
128
  Q(base_type='alarm-group') |
@@ -127,7 +131,7 @@ class ComponentAutocomplete(autocomplete.Select2QuerySetView):
127
131
 
128
132
  if self.q:
129
133
  qs = search_queryset(qs, self.q, ('name',))
130
- return qs
134
+ return qs.distinct()
131
135
 
132
136
  def get_result_label(self, item):
133
137
  return render_to_string(
simo/core/base_types.py CHANGED
@@ -9,6 +9,7 @@ BASE_TYPES = {
9
9
  'multi-sensor': _("Multi sensor"),
10
10
  'binary-sensor': _("Binary sensor"),
11
11
  'dimmer': _("Dimmer"),
12
+ 'dimmer-plus': _("Dimmer Plus"),
12
13
  'rgbw-light': _('RGB(W) light'),
13
14
  'switch': _("Switch"),
14
15
  'switch-double': _("Switch Double"),
simo/core/context.py CHANGED
@@ -6,6 +6,7 @@ from django.apps import apps
6
6
  from simo.core.models import Instance
7
7
  from simo.conf import dynamic_settings
8
8
  from simo.core.utils.helpers import is_update_available
9
+ from simo.core.middleware import get_current_instance
9
10
 
10
11
 
11
12
  def additional_templates_context(request):
@@ -14,7 +15,8 @@ def additional_templates_context(request):
14
15
  'dynamic_settings': dynamic_settings,
15
16
  'current_version': pkg_resources.get_distribution('simo').version,
16
17
  'update_available': is_update_available(True),
17
- 'instances': Instance.objects.all()
18
+ 'instances': Instance.objects.all(),
19
+ 'current_instance': get_current_instance()
18
20
  }
19
21
 
20
22
  if request.path.endswith('/admin/'):