simo 1.7.20__py3-none-any.whl → 2.0.1__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.
- simo/__pycache__/asgi.cpython-38.pyc +0 -0
- simo/__pycache__/settings.cpython-38.pyc +0 -0
- simo/__pycache__/urls.cpython-38.pyc +0 -0
- simo/__pycache__/wsgi.cpython-38.pyc +0 -0
- simo/core/__pycache__/admin.cpython-38.pyc +0 -0
- simo/core/__pycache__/api.cpython-38.pyc +0 -0
- simo/core/__pycache__/api_meta.cpython-38.pyc +0 -0
- simo/core/__pycache__/auto_urls.cpython-38.pyc +0 -0
- simo/core/__pycache__/autocomplete_views.cpython-38.pyc +0 -0
- simo/core/__pycache__/base_types.cpython-38.pyc +0 -0
- simo/core/__pycache__/context.cpython-38.pyc +0 -0
- simo/core/__pycache__/controllers.cpython-38.pyc +0 -0
- simo/core/__pycache__/events.cpython-38.pyc +0 -0
- simo/core/__pycache__/forms.cpython-38.pyc +0 -0
- simo/core/__pycache__/gateways.cpython-38.pyc +0 -0
- simo/core/__pycache__/managers.cpython-38.pyc +0 -0
- simo/core/__pycache__/middleware.cpython-38.pyc +0 -0
- simo/core/__pycache__/models.cpython-38.pyc +0 -0
- simo/core/__pycache__/permissions.cpython-38.pyc +0 -0
- simo/core/__pycache__/serializers.cpython-38.pyc +0 -0
- simo/core/__pycache__/signal_receivers.cpython-38.pyc +0 -0
- simo/core/__pycache__/socket_consumers.cpython-38.pyc +0 -0
- simo/core/__pycache__/tasks.cpython-38.pyc +0 -0
- simo/core/__pycache__/views.cpython-38.pyc +0 -0
- simo/core/admin.py +28 -18
- simo/core/api.py +157 -16
- simo/core/api_meta.py +87 -0
- simo/core/auto_urls.py +4 -1
- simo/core/autocomplete_views.py +8 -4
- simo/core/base_types.py +1 -0
- simo/core/context.py +3 -1
- simo/core/controllers.py +134 -36
- simo/core/db_backend/base.py +7 -22
- simo/core/drf_braces/README +3 -0
- simo/core/drf_braces/__init__.py +7 -0
- simo/core/drf_braces/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/core/drf_braces/__pycache__/utils.cpython-38.pyc +0 -0
- simo/core/drf_braces/fields/__init__.py +5 -0
- simo/core/drf_braces/fields/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/core/drf_braces/fields/__pycache__/_fields.cpython-38.pyc +0 -0
- simo/core/drf_braces/fields/__pycache__/custom.cpython-38.pyc +0 -0
- simo/core/drf_braces/fields/__pycache__/mixins.cpython-38.pyc +0 -0
- simo/core/drf_braces/fields/__pycache__/modified.cpython-38.pyc +0 -0
- simo/core/drf_braces/fields/_fields.py +48 -0
- simo/core/drf_braces/fields/custom.py +107 -0
- simo/core/drf_braces/fields/mixins.py +58 -0
- simo/core/drf_braces/fields/modified.py +41 -0
- simo/core/drf_braces/forms/__init__.py +0 -0
- simo/core/drf_braces/forms/fields.py +20 -0
- simo/core/drf_braces/forms/serializer_form.py +156 -0
- simo/core/drf_braces/mixins.py +52 -0
- simo/core/drf_braces/models.py +0 -0
- simo/core/drf_braces/parsers.py +72 -0
- simo/core/drf_braces/renderers.py +37 -0
- simo/core/drf_braces/serializers/__init__.py +0 -0
- simo/core/drf_braces/serializers/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/core/drf_braces/serializers/__pycache__/form_serializer.cpython-38.pyc +0 -0
- simo/core/drf_braces/serializers/enforce_validation_serializer.py +214 -0
- simo/core/drf_braces/serializers/form_serializer.py +391 -0
- simo/core/drf_braces/serializers/swapping.py +48 -0
- simo/core/drf_braces/tests/__init__.py +0 -0
- simo/core/drf_braces/tests/fields/__init__.py +0 -0
- simo/core/drf_braces/tests/fields/test_custom.py +94 -0
- simo/core/drf_braces/tests/fields/test_fields.py +13 -0
- simo/core/drf_braces/tests/fields/test_mixins.py +96 -0
- simo/core/drf_braces/tests/fields/test_modified.py +40 -0
- simo/core/drf_braces/tests/forms/__init__.py +0 -0
- simo/core/drf_braces/tests/forms/test_fields.py +46 -0
- simo/core/drf_braces/tests/forms/test_serializer_form.py +256 -0
- simo/core/drf_braces/tests/serializers/__init__.py +0 -0
- simo/core/drf_braces/tests/serializers/test_enforce_validation_serializer.py +169 -0
- simo/core/drf_braces/tests/serializers/test_form_serializer.py +387 -0
- simo/core/drf_braces/tests/serializers/test_swapping.py +40 -0
- simo/core/drf_braces/tests/test_mixins.py +111 -0
- simo/core/drf_braces/tests/test_parsers.py +73 -0
- simo/core/drf_braces/tests/test_renderers.py +23 -0
- simo/core/drf_braces/tests/test_utils.py +73 -0
- simo/core/drf_braces/utils.py +209 -0
- simo/core/events.py +3 -3
- simo/core/forms.py +79 -37
- simo/core/gateways.py +31 -14
- simo/core/management/commands/gateways_manager.py +0 -1
- simo/core/managers.py +81 -0
- simo/core/middleware.py +25 -0
- simo/core/migrations/0026_category_instance.py +20 -0
- simo/core/migrations/0027_remove_component_tags.py +17 -0
- simo/core/migrations/0028_rename_subcomponents_component_slaves.py +18 -0
- simo/core/migrations/0029_auto_20240229_1331.py +33 -0
- simo/core/migrations/__pycache__/0026_category_instance.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0027_remove_component_tags.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0028_rename_subcomponents_component_slaves.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0029_auto_20240229_1331.cpython-38.pyc +0 -0
- simo/core/models.py +103 -66
- simo/core/permissions.py +28 -2
- simo/core/serializers.py +330 -26
- simo/core/socket_consumers.py +5 -14
- simo/core/tasks.py +11 -1
- simo/core/templates/admin/base.html +37 -10
- simo/core/templates/admin/wizard/discovery.html +188 -0
- simo/core/templates/admin/wizard/wizard_add.html +5 -5
- simo/core/utils/__pycache__/serialization.cpython-38.pyc +0 -0
- simo/core/utils/admin.py +9 -2
- simo/core/utils/formsets.py +17 -16
- simo/core/utils/helpers.py +1 -0
- simo/core/utils/serialization.py +56 -0
- simo/core/utils/type_constants.py +1 -1
- simo/core/utils/validators.py +14 -1
- simo/core/views.py +13 -0
- simo/fleet/__pycache__/admin.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/api.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/auto_urls.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/controllers.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/forms.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/gateways.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/managers.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/models.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/serializers.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/socket_consumers.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/utils.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/views.cpython-38.pyc +0 -0
- simo/fleet/admin.py +53 -29
- simo/fleet/api.py +59 -3
- simo/fleet/auto_urls.py +2 -3
- simo/fleet/controllers.py +199 -16
- simo/fleet/forms.py +325 -483
- simo/fleet/gateways.py +44 -2
- simo/fleet/managers.py +32 -0
- simo/fleet/migrations/0025_auto_20240130_1334.py +27 -0
- simo/fleet/migrations/0026_rename_i2cinterface_scl_pin_and_more.py +64 -0
- simo/fleet/migrations/0027_auto_20240306_0802.py +170 -0
- simo/fleet/migrations/0028_remove_i2cinterface_scl_pin_no_and_more.py +21 -0
- simo/fleet/migrations/0029_alter_i2cinterface_scl_pin_and_more.py +24 -0
- simo/fleet/migrations/0030_colonelpin_label_alter_colonel_type.py +24 -0
- simo/fleet/migrations/0031_alter_colonel_type.py +18 -0
- simo/fleet/migrations/__pycache__/0025_auto_20240130_1334.cpython-38.pyc +0 -0
- simo/fleet/migrations/__pycache__/0026_rename_i2cinterface_scl_pin_and_more.cpython-38.pyc +0 -0
- simo/fleet/migrations/__pycache__/0027_auto_20240306_0802.cpython-38.pyc +0 -0
- simo/fleet/migrations/__pycache__/0028_remove_i2cinterface_scl_pin_no_and_more.cpython-38.pyc +0 -0
- simo/fleet/migrations/__pycache__/0029_alter_i2cinterface_scl_pin_and_more.cpython-38.pyc +0 -0
- simo/fleet/migrations/__pycache__/0030_colonelpin_label_alter_colonel_type.cpython-38.pyc +0 -0
- simo/fleet/migrations/__pycache__/0031_alter_colonel_type.cpython-38.pyc +0 -0
- simo/fleet/models.py +140 -82
- simo/fleet/serializers.py +35 -1
- simo/fleet/socket_consumers.py +239 -76
- simo/fleet/utils.py +15 -53
- simo/fleet/views.py +28 -14
- simo/generic/controllers.py +13 -89
- simo/generic/forms.py +29 -18
- simo/generic/gateways.py +73 -2
- simo/generic/models.py +3 -3
- simo/multimedia/controllers.py +9 -8
- simo/settings.py +7 -4
- simo/urls.py +4 -8
- simo/users/__pycache__/admin.cpython-38.pyc +0 -0
- simo/users/__pycache__/api.cpython-38.pyc +0 -0
- simo/users/__pycache__/auto_urls.cpython-38.pyc +0 -0
- simo/users/__pycache__/models.cpython-38.pyc +0 -0
- simo/users/__pycache__/serializers.cpython-38.pyc +0 -0
- simo/users/__pycache__/sso_urls.cpython-38.pyc +0 -0
- simo/users/admin.py +8 -1
- simo/users/api.py +38 -2
- simo/users/auto_urls.py +2 -2
- simo/users/migrations/0025_rename_name_fingerprint_type_and_more.py +22 -0
- simo/users/migrations/__pycache__/0025_rename_name_fingerprint_type_and_more.cpython-38.pyc +0 -0
- simo/users/models.py +2 -3
- simo/users/serializers.py +15 -1
- simo/users/sso_urls.py +3 -3
- simo/wsgi.py +7 -0
- {simo-1.7.20.dist-info → simo-2.0.1.dist-info}/METADATA +8 -9
- {simo-1.7.20.dist-info → simo-2.0.1.dist-info}/RECORD +173 -189
- {simo-1.7.20.dist-info → simo-2.0.1.dist-info}/WHEEL +1 -1
- simo/core/db_backend/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/core/db_backend/__pycache__/base.cpython-38.pyc +0 -0
- simo/core/management/commands/__pycache__/gateways_manager.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0001_initial.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0002_load_icons.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0003_create_default_zones_and_categories.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0004_create_generic.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0005_component_subcomponents.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0006_alter_component_subcomponents.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0007_component_change_init_to.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0008_alter_component_change_init_to.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0009_auto_20220707_1404.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0010_historyaggregate.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0011_component_last_change.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0012_instance.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0013_auto_20231003_0754.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0014_zone_instance.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0015_auto_20231004_1113.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0016_auto_20231004_1113.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0017_auto_20231004_1313.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0018_auto_20231005_0622.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0019_alter_gateway_type.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0020_component_meta.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0021_auto_20231020_1041.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/core/templatetags/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/core/templatetags/__pycache__/components_list.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/admin.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/config_values.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/easing.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/form_fields.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/form_widgets.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/formsets.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/helpers.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/logs.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/mixins.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/model_helpers.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/relay.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/type_constants.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/validators.cpython-38.pyc +0 -0
- simo/fleet/tasks.py +0 -25
- simo/generic/__pycache__/__init__.cpython-37.pyc +0 -0
- simo/generic/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/generic/__pycache__/app_widgets.cpython-38.pyc +0 -0
- simo/generic/__pycache__/base_types.cpython-38.pyc +0 -0
- simo/generic/__pycache__/controllers.cpython-37.pyc +0 -0
- simo/generic/__pycache__/controllers.cpython-38.pyc +0 -0
- simo/generic/__pycache__/forms.cpython-38.pyc +0 -0
- simo/generic/__pycache__/gateways.cpython-38.pyc +0 -0
- simo/generic/__pycache__/models.cpython-38.pyc +0 -0
- simo/generic/__pycache__/routing.cpython-38.pyc +0 -0
- simo/generic/__pycache__/socket_consumers.cpython-38.pyc +0 -0
- simo/generic/__pycache__/widgets.cpython-37.pyc +0 -0
- simo/multimedia/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/multimedia/__pycache__/admin.cpython-38.pyc +0 -0
- simo/multimedia/__pycache__/api.cpython-38.pyc +0 -0
- simo/multimedia/__pycache__/app_widgets.cpython-38.pyc +0 -0
- simo/multimedia/__pycache__/base_types.cpython-38.pyc +0 -0
- simo/multimedia/__pycache__/controllers.cpython-38.pyc +0 -0
- simo/multimedia/__pycache__/forms.cpython-38.pyc +0 -0
- simo/multimedia/__pycache__/models.cpython-38.pyc +0 -0
- simo/multimedia/__pycache__/serializers.cpython-38.pyc +0 -0
- simo/multimedia/migrations/__pycache__/0001_initial.cpython-38.pyc +0 -0
- simo/multimedia/migrations/__pycache__/0002_sound_length.cpython-38.pyc +0 -0
- simo/multimedia/migrations/__pycache__/0003_alter_sound_length.cpython-38.pyc +0 -0
- simo/multimedia/migrations/__pycache__/0004_auto_20231023_1055.cpython-38.pyc +0 -0
- simo/multimedia/migrations/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/notifications/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/notifications/__pycache__/admin.cpython-38.pyc +0 -0
- simo/notifications/__pycache__/api.cpython-38.pyc +0 -0
- simo/notifications/__pycache__/models.cpython-38.pyc +0 -0
- simo/notifications/__pycache__/serializers.cpython-38.pyc +0 -0
- simo/notifications/__pycache__/utils.cpython-38.pyc +0 -0
- simo/notifications/migrations/__pycache__/0001_initial.cpython-38.pyc +0 -0
- simo/notifications/migrations/__pycache__/0002_notification_instance.cpython-38.pyc +0 -0
- simo/notifications/migrations/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0001_initial.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0002_componentpermission.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0003_create_roles_and_system_user.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0004_user_secret_key.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0005_permissionsrole_instance.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0006_auto_20231003_0850.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0007_auto_20231003_1228.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0008_auto_20231003_1229.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0009_remove_user_role.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0010_auto_20231004_1313.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0011_auto_20231004_1313.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0012_alter_userinstancerole_unique_together.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0013_remove_user_roles.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0014_user_roles.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0015_remove_user_at_home.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0016_auto_20231005_1050.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/__init__.cpython-38.pyc +0 -0
- {simo-1.7.20.dist-info → simo-2.0.1.dist-info}/LICENSE.md +0 -0
- {simo-1.7.20.dist-info → simo-2.0.1.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
|
+
› <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ opts.app_config.verbose_name }}</a>
|
|
20
|
+
› {% 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
|
+
› {% 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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
|
|
Binary file
|
simo/core/utils/admin.py
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
from django
|
|
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
|
|
simo/core/utils/formsets.py
CHANGED
|
@@ -27,19 +27,21 @@ class FormsetWidget(forms.Widget):
|
|
|
27
27
|
use_cached = False
|
|
28
28
|
|
|
29
29
|
class Media:
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
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
|
|
simo/core/utils/helpers.py
CHANGED
|
@@ -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
|
-
#
|
|
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
|
# ]
|
simo/core/utils/validators.py
CHANGED
|
@@ -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
|
|
Binary file
|
|
Binary file
|
|
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',
|
|
57
|
+
'newer_firmware_available',
|
|
23
58
|
)
|
|
24
59
|
readonly_fields = (
|
|
25
60
|
'type', 'uid', 'connected', 'last_seen',
|
|
26
|
-
'firmware_version', 'newer_firmware_available',
|
|
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
|
-
'
|
|
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,28 +78,9 @@ 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
|
-
def save_model(self, request, obj, form, change):
|
|
65
|
-
res = super().save_model(request, obj, form, change)
|
|
66
|
-
obj.restart()
|
|
67
|
-
return res
|
|
68
|
-
|
|
69
84
|
def update_firmware(self, request, queryset):
|
|
70
85
|
count = 0
|
|
71
86
|
for colonel in queryset:
|
|
@@ -83,7 +98,7 @@ class ColonelAdmin(admin.ModelAdmin):
|
|
|
83
98
|
)
|
|
84
99
|
|
|
85
100
|
def move_colonel_to(self, request, queryset, form):
|
|
86
|
-
if form.cleaned_data['colonel'] not in request.user.instances:
|
|
101
|
+
if form.cleaned_data['colonel'].instance not in request.user.instances:
|
|
87
102
|
return
|
|
88
103
|
moved = 0
|
|
89
104
|
for colonel in queryset:
|
|
@@ -96,7 +111,6 @@ class ColonelAdmin(admin.ModelAdmin):
|
|
|
96
111
|
request, "%d colonels were moved." % moved
|
|
97
112
|
)
|
|
98
113
|
|
|
99
|
-
|
|
100
114
|
def restart(self, request, queryset):
|
|
101
115
|
restarted = 0
|
|
102
116
|
for colonel in queryset:
|
|
@@ -128,6 +142,16 @@ class ColonelAdmin(admin.ModelAdmin):
|
|
|
128
142
|
request, "%d colonels checked." % queryset.count()
|
|
129
143
|
)
|
|
130
144
|
|
|
145
|
+
def rebuild_occupied_pins(self, request, queryset):
|
|
146
|
+
affected = 0
|
|
147
|
+
for obj in queryset:
|
|
148
|
+
affected += 1
|
|
149
|
+
obj.rebuild_occupied_pins()
|
|
150
|
+
|
|
151
|
+
self.message_user(
|
|
152
|
+
request, f"Occupied pins where rebuilt on {affected} colonels."
|
|
153
|
+
)
|
|
154
|
+
|
|
131
155
|
def connected(self, obj):
|
|
132
156
|
if obj.is_connected:
|
|
133
157
|
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 .
|
|
4
|
-
from .
|
|
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 = '
|
|
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)
|