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
@@ -0,0 +1,188 @@
1
+ {% extends "admin/base_site.html" %}
2
+ {% load i18n admin_urls static admin_modify %}
3
+
4
+ {% block extrahead %}{{ block.super }}
5
+ <script type="text/javascript" src="{% url 'admin:jsi18n' %}"></script>
6
+ {{ form.media }}
7
+ {% endblock %}
8
+
9
+ {% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% static "admin/css/forms.css" %}">{% endblock %}
10
+
11
+ {% block coltype %}colM{% endblock %}
12
+
13
+ {% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} change-form{% endblock %}
14
+
15
+ {% if not is_popup %}
16
+ {% block breadcrumbs %}
17
+ <div class="breadcrumbs">
18
+ <a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
19
+ &rsaquo; <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ opts.app_config.verbose_name }}</a>
20
+ &rsaquo; {% if has_view_permission %}<a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>{% else %}{{ opts.verbose_name_plural|capfirst }}{% endif %}
21
+ &rsaquo; {% if add %}{% blocktrans with name=opts.verbose_name %}Discover {{ name }}{% endblocktrans %}{% else %}{{ original|truncatewords:"18" }}{% endif %}
22
+ </div>
23
+ {% endblock %}
24
+ {% endif %}
25
+
26
+ {% block content %}<div id="content-main">
27
+ {% block object-tools %}
28
+ {% if change %}{% if not is_popup %}
29
+ <ul class="object-tools">
30
+ {% block object-tools-items %}
31
+ {% change_form_object_tools %}
32
+ {% endblock %}
33
+ </ul>
34
+ {% endif %}{% endif %}
35
+ {% endblock %}
36
+ <form {% if has_file_field %}enctype="multipart/form-data" {% endif %}action="{{ form_url }}" method="post" id="{{ opts.model_name }}_form" novalidate>{% csrf_token %}{% block form_top %}{% endblock %}
37
+ <div>
38
+ {% if is_popup %}<input type="hidden" name="{{ is_popup_var }}" value="1">{% endif %}
39
+ {% if to_field %}<input type="hidden" name="{{ to_field_var }}" value="{{ to_field }}">{% endif %}
40
+ {% if save_on_top %}{% block submit_buttons_top %}{% submit_row %}{% endblock %}{% endif %}
41
+ {% if form.non_field_errors %}
42
+ <p class="errornote">
43
+ {% if form.non_field_errors|length == 1 %}{% trans "Please correct the error below." %}{% else %}{% trans "Please correct the errors below." %}{% endif %}
44
+ </p>
45
+ {{ form.non_field_errors }}
46
+ {% endif %}
47
+
48
+ {% block field_sets %}
49
+ <h3 style="margin-bottom: 20px; text-align: center">Step {{ current_step }} of {{ total_steps }}</h3>
50
+
51
+ <fieldset class="module aligned">
52
+ {% if selected_gateway %}
53
+ <div class="form-row ">
54
+ <label class="required"><label for="id_gateway">Gateway:</label></label>
55
+ <div class="readonly" style="font-weight:bold;">{{ selected_gateway }}</div>
56
+ </div>
57
+ {% endif %}
58
+ {% if selected_type %}
59
+ <div class="form-row ">
60
+ <label class="required"><label for="id_base_type">Base type:</label></label>
61
+ <div class="readonly" style="font-weight:bold;">{{ selected_type }}</div>
62
+ </div>
63
+ {% endif %}
64
+
65
+ <div id="running-discovery">
66
+ <p style="margin: 30px 15px">
67
+ <i class="fas fa-spinner fa-spin fa-lg" style="margin-right: 10px"></i> DISCOVERY MODE ACTIVATED! <br>
68
+ Your new components will appear down bellow once discovered.
69
+ </p>
70
+ {% if discovery_msg %}
71
+ <p style="margin: 30px 15px">{{ discovery_msg }}</p>
72
+ {% endif %}
73
+ </div>
74
+ <div id="discovery-finished" style="display:none;">
75
+ <p style="margin: 30px 15px">Discovery process finished!</p>
76
+ <p style="margin: 30px 15px; font-weight: bold;">
77
+ <a href="#" id="retry-btn" style="padding: 10px 20px; border: 1px solid;">
78
+ <i class="fas fa-redo"></i> Retry!
79
+ </a>
80
+ </p>
81
+ </div>
82
+
83
+ <style>
84
+ #components-discovered{
85
+ padding: 0 20px;
86
+ }
87
+ .discovered-component{
88
+ padding: 10px 30px;
89
+ margin-bottom: 30px;
90
+ font-weight: bold;
91
+ }
92
+ .discovered-component.error{
93
+ color: red;
94
+ }
95
+ </style>
96
+ <div id="components-discovered">
97
+
98
+ </div>
99
+
100
+
101
+ </fieldset>
102
+
103
+ {% endblock %}
104
+
105
+
106
+
107
+ {% block submit_buttons_bottom %}
108
+
109
+ <div class="submit-row" style="text-align: left;">
110
+
111
+ <a href="{% url 'admin:core_component_changelist' %}" class="cancel-link" style="display: block; padding:9px 15px">
112
+ <i class="fa fa-check"></i> Done
113
+ </a>
114
+
115
+ </div>
116
+
117
+ {% endblock %}
118
+
119
+ {% block admin_change_form_document_ready %}
120
+ <script type="text/javascript"
121
+ id="django-admin-form-add-constants"
122
+ src="{% static 'admin/js/change_form.js' %}"
123
+ {% if adminform and add %}
124
+ data-model-name="{{ opts.model_name }}"
125
+ {% endif %}>
126
+ </script>
127
+ {% endblock %}
128
+
129
+ {# JavaScript for prepopulated fields #}
130
+ {% prepopulated_fields_js %}
131
+
132
+ <script>
133
+ (function() {
134
+ var api_check_url = '{{ api_check_url }}';
135
+ var api_retry_url = '{{ api_retry_url }}';
136
+ var api_components_url = '{{ api_components_url }}';
137
+ console.log("API CHECK URL: ", api_check_url);
138
+
139
+ function checkDiscoveryStatus(){
140
+ $.ajax({
141
+ url: api_check_url,
142
+ success: function(response){
143
+ for (var i=0; i<response.length; i++) {
144
+ var discovery = response[i];
145
+ if (discovery.finished > 0){
146
+ $('#running-discovery').hide();
147
+ $('#discovery-finished').show();
148
+ } else {
149
+ $('#running-discovery').show();
150
+ $('#discovery-finished').hide();
151
+ }
152
+ for (var j=0; j<discovery.result.length; j++){
153
+ if (discovery.result[j].error === undefined){
154
+ var comp_id = discovery.result[j];
155
+ if ($('#discovered-component-' + comp_id).length < 1){
156
+ $.ajax({
157
+ url: api_components_url + comp_id + '/',
158
+ success: function(component){
159
+ $('#components-discovered').append(
160
+ '<p class="discovered-component" id="discovered-component-' + comp_id +'"><a href="/admin/core/component/' + comp_id + '">' +
161
+ component.name + '</a></p>'
162
+ );
163
+ }
164
+ })
165
+ }
166
+ } else{
167
+ if ($('#discovery-error-' + j).length < 1) {
168
+ $('#components-discovered').append('<p class="discovered-component error" id="discovery-error-' + j + '">' + discovery.result[j].error + '</p>');
169
+ }
170
+ }
171
+ }
172
+ }
173
+ }
174
+ });
175
+ }
176
+ setInterval(checkDiscoveryStatus, 1000);
177
+
178
+ $('#retry-btn').on('click', function(e){
179
+ e.preventDefault();
180
+ $.get(api_retry_url);
181
+ });
182
+ })();
183
+ </script>
184
+
185
+ </div>
186
+ </form></div>
187
+
188
+ {% endblock %}
@@ -80,11 +80,11 @@
80
80
  <div class="submit-row" style="text-align: left;">
81
81
 
82
82
  <button class="button" name="prev" style="padding: 10px 15px" type="submit" value="1" {% if is_first %}disabled{% endif %}>{% trans "< Prev" %}</button>
83
- <div style="display:inline-block; margin-left: 15px;">
84
- <a href="{% url 'admin:core_component_changelist' %}" class="cancel-link" style="padding:9px 15px">
85
- <i class="fa fa-times"></i> Cancel
86
- </a>
87
- </div>
83
+
84
+ <a href="{% url 'admin:core_component_changelist' %}" class="cancel-link" style="display:block; margin-left: 15px; padding:9px 15px">
85
+ <i class="fa fa-times"></i> Cancel
86
+ </a>
87
+
88
88
 
89
89
  <input type="submit" value="{% if is_last %}Finish!{% else %}Next >{% endif %}" class="default" name="_save">
90
90
 
simo/core/utils/admin.py CHANGED
@@ -1,9 +1,16 @@
1
- from django.contrib import admin
1
+ from django import forms
2
2
  from django.shortcuts import render, redirect
3
3
  from django.contrib.admin.helpers import Fieldset
4
- from adminsortable2.admin import SortableAdminMixin
5
4
 
6
5
 
6
+ class AdminFormActionForm(forms.Form):
7
+
8
+ def __init__(self, modeladmin, request, queryset, *args, **kwargs):
9
+ self.modeladmin = modeladmin
10
+ self.request = request
11
+ self.queryset = queryset
12
+ super().__init__(*args, **kwargs)
13
+
7
14
 
8
15
  class FormAction:
9
16
 
@@ -27,19 +27,21 @@ class FormsetWidget(forms.Widget):
27
27
  use_cached = False
28
28
 
29
29
  class Media:
30
- css = {
31
- 'all': ['adminsortable2/css/sortable.css']
32
- }
30
+ pass
31
+ # No longer works with adminsortable2-2
32
+ # css = {
33
+ # 'all': ['adminsortable2/css/sortable.css']
34
+ # }
33
35
  js = (
34
36
  'admin/js/inlines.js',
35
- 'adminsortable2/js/plugins/admincompat.js',
36
- 'adminsortable2/js/libs/jquery.ui.core-1.11.4.js',
37
- 'adminsortable2/js/libs/jquery.ui.widget-1.11.4.js',
38
- 'adminsortable2/js/libs/jquery.ui.mouse-1.11.4.js',
39
- 'adminsortable2/js/libs/jquery.ui.touch-punch-0.2.3.js',
40
- 'adminsortable2/js/libs/jquery.ui.sortable-1.11.4.js',
41
- 'adminsortable2/js/inline-tabular.js',
42
- 'adminsortable2/js/inline-sortable.js',
37
+ # 'adminsortable2/js/plugins/admincompat.js',
38
+ # 'adminsortable2/js/libs/jquery.ui.core-1.11.4.js',
39
+ # 'adminsortable2/js/libs/jquery.ui.widget-1.11.4.js',
40
+ # 'adminsortable2/js/libs/jquery.ui.mouse-1.11.4.js',
41
+ # 'adminsortable2/js/libs/jquery.ui.touch-punch-0.2.3.js',
42
+ # 'adminsortable2/js/libs/jquery.ui.sortable-1.11.4.js',
43
+ # 'adminsortable2/js/inline-tabular.js',
44
+ # 'adminsortable2/js/inline-sortable.js',
43
45
  )
44
46
 
45
47
  def render(self, name, value, attrs=None, renderer=None):
@@ -115,7 +117,6 @@ class FormsetWidget(forms.Widget):
115
117
  return formset_data
116
118
 
117
119
 
118
-
119
120
  class FormsetField(forms.Field):
120
121
  widget = FormsetWidget
121
122
  formset_cls = None
@@ -127,7 +128,6 @@ class FormsetField(forms.Field):
127
128
  self.widget.formset_cls = formset_cls
128
129
  self.widget.prefix = self.prefix
129
130
 
130
-
131
131
  def clean(self, formset_data):
132
132
 
133
133
  try:
@@ -149,12 +149,14 @@ class FormsetField(forms.Field):
149
149
  continue
150
150
  form_data = {}
151
151
  for field_name, field in self.formset_cls().form.declared_fields.items():
152
- print(field)
153
152
  form_data[field_name] = formset_data.get(
154
153
  '%s-%d-%s' % (prefix, i, field_name)
155
154
  )
156
155
  if isinstance(field, forms.models.ModelChoiceField):
157
- form_data[field_name] = int(form_data[field_name])
156
+ if isinstance(form_data[field_name], models.Model):
157
+ form_data[field_name] = form_data[field_name].pk
158
+ else:
159
+ form_data[field_name] = int(form_data[field_name])
158
160
  elif isinstance(field, forms.models.ModelMultipleChoiceField):
159
161
  form_data[field_name] = [
160
162
  obj.pk for obj in form_data[field_name]
@@ -174,7 +176,6 @@ class FormsetField(forms.Field):
174
176
  for i in range(len(cleaned_value)):
175
177
  cleaned_value[i].pop('order')
176
178
 
177
- print("CLEANED VALUE: ", cleaned_value)
178
179
  return cleaned_value
179
180
 
180
181
 
@@ -14,6 +14,7 @@ class classproperty(property):
14
14
  def __get__(self, cls, owner):
15
15
  return self.fget.__get__(None, owner)()
16
16
 
17
+
17
18
  def get_random_string(
18
19
  size=20, chars=string.ascii_uppercase + string.ascii_lowercase + string.digits
19
20
  ):
@@ -0,0 +1,56 @@
1
+ import json
2
+ from django.core import serializers as model_serializers
3
+ from django.db import models
4
+ from collections.abc import Iterable
5
+
6
+
7
+
8
+ def serialize_form_data(data):
9
+ serialized_data = {}
10
+ for field_name, val in data.items():
11
+ is_model = False
12
+ if isinstance(val, Iterable):
13
+ for v in val:
14
+ if isinstance(v, models.Model):
15
+ is_model = True
16
+ break
17
+ elif isinstance(val, models.Model):
18
+ is_model = True
19
+ if is_model:
20
+ if isinstance(val, Iterable):
21
+ serialized_data[field_name] = {
22
+ 'model': 'many',
23
+ 'val': json.loads(model_serializers.serialize(
24
+ 'json', val
25
+ ))
26
+ }
27
+ else:
28
+ serialized_data[field_name] = {
29
+ 'model': 'single',
30
+ 'val': json.loads(model_serializers.serialize(
31
+ 'json', [val]
32
+ ))
33
+ }
34
+ else:
35
+ serialized_data[field_name] = val
36
+ return serialized_data
37
+
38
+
39
+ def deserialize_form_data(data):
40
+ deserialized_data = {}
41
+ for field_name, val in data.items():
42
+ if isinstance(val, dict) and val.get('model'):
43
+ deserializer_generator = model_serializers.deserialize(
44
+ 'json', json.dumps(val['val'])
45
+ )
46
+ if val['model'] == 'single':
47
+ for item in deserializer_generator:
48
+ deserialized_data[field_name] = item.object
49
+ break
50
+ else:
51
+ deserialized_data[field_name] = []
52
+ for item in deserializer_generator:
53
+ deserialized_data[field_name].append(item.object)
54
+ else:
55
+ deserialized_data[field_name] = val
56
+ return deserialized_data
@@ -40,7 +40,7 @@ def get_controller_types_choices(gateway=None):
40
40
  return choices
41
41
 
42
42
 
43
- # ALL_CONTROLLER_TYPES = get_controller_types_map()
43
+ #ALL_CONTROLLER_TYPES = get_controller_types_map()
44
44
  # CONTROLLER_TYPE_CHOICES = [
45
45
  # (slug, cls.name) for slug, cls in ALL_CONTROLLER_TYPES.items()
46
46
  # ]
@@ -1,4 +1,5 @@
1
1
  import xml.etree.cElementTree as et
2
+ from django import forms
2
3
  from django.utils.translation import gettext_lazy as _
3
4
  from django.core.exceptions import ValidationError
4
5
 
@@ -28,4 +29,16 @@ def validate_flr(f):
28
29
  return f
29
30
  # if not f.endswith('.flr'):
30
31
  # raise ValidationError("Only .flr files accepted")
31
- # return f
32
+ # return f
33
+
34
+
35
+ def validate_slaves(slaves, component, master_component=None):
36
+ for slave in slaves:
37
+ if slave == component:
38
+ raise forms.ValidationError(_(f"{component} is already a slave of {slave}"))
39
+ if slave == master_component:
40
+ raise forms.ValidationError(_(f"{master_component} is already a slave of {slave}"))
41
+ subslaves = slave.slaves.all()
42
+ if subslaves.count():
43
+ validate_slaves(subslaves, slave, master_component=None)
44
+ return slaves
simo/core/views.py CHANGED
@@ -9,10 +9,12 @@ from django.contrib.gis.geos import Point
9
9
  from django.contrib import messages
10
10
  from simo.users.models import User
11
11
  from simo.conf import dynamic_settings
12
+ from .models import Instance
12
13
  from .tasks import update as update_task, supervisor_restart
13
14
  from .forms import (
14
15
  HubConfigForm, CoordinatesForm, TermsAndConditionsForm
15
16
  )
17
+ from .middleware import introduce_instance
16
18
 
17
19
 
18
20
  def get_timestamp(request):
@@ -139,3 +141,14 @@ def reboot(request):
139
141
  if request.META.get('HTTP_REFERER'):
140
142
  return redirect(request.META.get('HTTP_REFERER'))
141
143
  return redirect(reverse('admin:index'))
144
+
145
+
146
+ @login_required
147
+ def set_instance(request, instance_slug):
148
+ instance = Instance.objects.filter(slug=instance_slug).first()
149
+ if instance:
150
+ introduce_instance(instance, request)
151
+
152
+ if request.META.get('HTTP_REFERER'):
153
+ return redirect(request.META.get('HTTP_REFERER'))
154
+ return redirect(reverse('admin:index'))
Binary file
Binary file
Binary file
Binary file
Binary file
simo/fleet/admin.py CHANGED
@@ -4,7 +4,7 @@ from django.template.loader import render_to_string
4
4
  from django.templatetags.static import static
5
5
  from simo.core.models import Component
6
6
  from simo.core.utils.admin import FormAction
7
- from .models import Colonel, I2CInterface
7
+ from .models import Colonel, I2CInterface, ColonelPin
8
8
  from .forms import ColonelAdminForm, MoveColonelForm, I2CInterfaceAdminForm
9
9
 
10
10
 
@@ -14,29 +14,63 @@ class I2CInterfaceInline(admin.TabularInline):
14
14
  form = I2CInterfaceAdminForm
15
15
 
16
16
 
17
+ class ColonelPinsInline(admin.TabularInline):
18
+ model = ColonelPin
19
+ extra = 0
20
+ fields = 'id_display', 'label', 'occupied_by_display',
21
+ readonly_fields = fields
22
+
23
+ def occupied_by_display(self, obj):
24
+ if not obj.occupied_by:
25
+ return
26
+ try:
27
+ admin_url = obj.occupied_by.get_admin_url()
28
+ except:
29
+ admin_url = None
30
+ txt = f'{obj.occupied_by_content_type}: {obj.occupied_by}'
31
+ if admin_url:
32
+ return mark_safe(f'<a href="{admin_url}">{txt}</a>')
33
+ return txt
34
+
35
+ occupied_by_display.short_description = "Occupied By"
36
+
37
+
38
+ def id_display(self, obj):
39
+ return obj.id
40
+ id_display.short_description = "ID"
41
+
42
+ def has_add_permission(self, request, obj):
43
+ return False
44
+
45
+ def has_delete_permission(self, request, obj=None):
46
+ return False
47
+
48
+ def has_change_permission(self, request, obj=None):
49
+ return False
50
+
51
+
17
52
  @admin.register(Colonel)
18
53
  class ColonelAdmin(admin.ModelAdmin):
19
54
  form = ColonelAdminForm
20
55
  list_display = (
21
56
  '__str__', 'instance', 'type', 'connected', 'last_seen', 'firmware_version',
22
- 'newer_firmware_available', 'is_authorized'
57
+ 'newer_firmware_available',
23
58
  )
24
59
  readonly_fields = (
25
60
  'type', 'uid', 'connected', 'last_seen',
26
- 'firmware_version', 'newer_firmware_available', 'occupied_pins',
27
- 'components_display', 'is_authorized'
61
+ 'firmware_version', 'newer_firmware_available',
28
62
  )
29
- # inlines = ColonelComponentInline, ColonelBLEDeviceInline
30
63
  fields = (
31
64
  'name', 'instance', 'enabled', 'firmware_auto_update'
32
65
  ) + readonly_fields + ('pwm_frequency', 'logs_stream', 'log', )
33
66
 
34
67
  actions = (
35
- 'update_firmware', 'check_for_upgrade', 'update_config', 'restart',
36
- FormAction(MoveColonelForm, 'move_colonel_to', "Move to other Colonel")
68
+ 'check_for_upgrade', 'update_firmware', 'update_config', 'restart',
69
+ FormAction(MoveColonelForm, 'move_colonel_to', "Move to other Colonel"),
70
+ 'rebuild_occupied_pins'
37
71
  )
38
72
 
39
- inlines = I2CInterfaceInline,
73
+ inlines = I2CInterfaceInline, ColonelPinsInline
40
74
 
41
75
  def get_queryset(self, request):
42
76
  qs = super().get_queryset(request)
@@ -44,26 +78,12 @@ class ColonelAdmin(admin.ModelAdmin):
44
78
  return qs
45
79
  return qs.filter(instance__in=request.user.instances)
46
80
 
47
-
48
- def components_display(self, obj=None):
49
- resp = ''
50
- for pin_no, item in obj.occupied_pins.items():
51
- try:
52
- component = Component.objects.get(pk=item)
53
- except:
54
- continue
55
- resp += '<a href="%s" target=_blank>%s</a><br>' % (
56
- component.get_admin_url(), str(component)
57
- )
58
- return mark_safe(resp)
59
- components_display.short_description = 'Components'
60
-
61
81
  def has_add_permission(self, request):
62
82
  return False
63
83
 
64
84
  def save_model(self, request, obj, form, change):
65
85
  res = super().save_model(request, obj, form, change)
66
- obj.restart()
86
+ obj.update_config()
67
87
  return res
68
88
 
69
89
  def update_firmware(self, request, queryset):
@@ -83,7 +103,7 @@ class ColonelAdmin(admin.ModelAdmin):
83
103
  )
84
104
 
85
105
  def move_colonel_to(self, request, queryset, form):
86
- if form.cleaned_data['colonel'] not in request.user.instances:
106
+ if form.cleaned_data['colonel'].instance not in request.user.instances:
87
107
  return
88
108
  moved = 0
89
109
  for colonel in queryset:
@@ -96,7 +116,6 @@ class ColonelAdmin(admin.ModelAdmin):
96
116
  request, "%d colonels were moved." % moved
97
117
  )
98
118
 
99
-
100
119
  def restart(self, request, queryset):
101
120
  restarted = 0
102
121
  for colonel in queryset:
@@ -128,6 +147,16 @@ class ColonelAdmin(admin.ModelAdmin):
128
147
  request, "%d colonels checked." % queryset.count()
129
148
  )
130
149
 
150
+ def rebuild_occupied_pins(self, request, queryset):
151
+ affected = 0
152
+ for obj in queryset:
153
+ affected += 1
154
+ obj.rebuild_occupied_pins()
155
+
156
+ self.message_user(
157
+ request, f"Occupied pins where rebuilt on {affected} colonels."
158
+ )
159
+
131
160
  def connected(self, obj):
132
161
  if obj.is_connected:
133
162
  return mark_safe('<img src="%s" alt="True">' % static('admin/img/icon-yes.svg'))
simo/fleet/api.py CHANGED
@@ -1,13 +1,69 @@
1
+ from django.db.models import Count
2
+ from django.utils.translation import gettext_lazy as _
1
3
  from rest_framework import viewsets
4
+ from rest_framework.decorators import action
5
+ from rest_framework.exceptions import ValidationError as APIValidationError
2
6
  from simo.core.api import InstanceMixin
3
- from .models import InstanceOptions
4
- from .serializers import InstanceOptionsSerializer
7
+ from simo.core.permissions import IsInstanceSuperuser
8
+ from .models import InstanceOptions, Colonel
9
+ from .serializers import InstanceOptionsSerializer, ColonelSerializer
5
10
 
6
11
 
7
12
  class InstanceOptionsViewSet(InstanceMixin, viewsets.ReadOnlyModelViewSet):
8
13
  url = 'fleet/options'
9
- basename = 'fleet-options'
14
+ basename = 'options'
10
15
  serializer_class = InstanceOptionsSerializer
11
16
 
12
17
  def get_queryset(self):
13
18
  return InstanceOptions.objects.filter(instance=self.instance)
19
+
20
+
21
+ class ColonelsViewSet(InstanceMixin, viewsets.ModelViewSet):
22
+ url = 'fleet/colonels'
23
+ basename = 'colonels'
24
+ serializer_class = ColonelSerializer
25
+
26
+ def get_permissions(self):
27
+ permissions = super().get_permissions()
28
+ permissions.append(IsInstanceSuperuser())
29
+ return permissions
30
+
31
+ def get_queryset(self):
32
+ return Colonel.objects.filter(instance=self.instance)
33
+
34
+ @action(detail=True, methods=['post'])
35
+ def check_for_upgrade(self, request, pk=None, *args, **kwargs):
36
+ colonel = self.get_object()
37
+ colonel.check_for_upgrade()
38
+
39
+ @action(detail=True, methods=['post'])
40
+ def upgrade(self, request, pk=None, *args, **kwargs):
41
+ colonel = self.get_object()
42
+ if colonel.major_upgrade_available:
43
+ colonel.update_firmware(colonel.major_upgrade_available)
44
+ elif colonel.minor_upgrade_available:
45
+ colonel.update_firmware(colonel.minor_upgrade_available)
46
+
47
+ @action(detail=True, methods=['post'])
48
+ def restart(self, request, pk=None, *args, **kwargs):
49
+ colonel = self.get_object()
50
+ colonel.restart()
51
+
52
+ @action(detail=True, methods=['post'])
53
+ def update_config(self, request, pk=None, *args, **kwargs):
54
+ colonel = self.get_object()
55
+ colonel.update_config()
56
+
57
+
58
+ @action(detail=True, methods=['post'])
59
+ def move_to(self, request, pk, *args, **kwargs):
60
+ colonel = self.get_object()
61
+ target = Colonel.objects.annotate(
62
+ components_count=Count('components')
63
+ ).filter(
64
+ pk=request.POST.get('target'),
65
+ components_count=0, type=colonel.type
66
+ )
67
+ if not target:
68
+ raise APIValidationError(_('Invalid target.'), code=400)
69
+ colonel.move_to(target)