openwisp-controller 1.2.1__tar.gz → 1.2.3__tar.gz

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.
Files changed (333) hide show
  1. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/CHANGES.rst +24 -0
  2. {openwisp_controller-1.2.1/openwisp_controller.egg-info → openwisp_controller-1.2.3}/PKG-INFO +1 -1
  3. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/__init__.py +1 -1
  4. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/base/config.py +4 -4
  5. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/base/device.py +3 -3
  6. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/base/device_group.py +4 -4
  7. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/base/multitenancy.py +2 -1
  8. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/base/template.py +3 -4
  9. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0023_update_context.py +4 -3
  10. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0028_template_default_values.py +3 -3
  11. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0036_device_group.py +5 -3
  12. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0049_devicegroup_context.py +2 -2
  13. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0051_organizationconfigsettings_context.py +2 -2
  14. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/static/config/js/relevant_templates.js +1 -1
  15. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/tests/test_device.py +14 -0
  16. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/tests/test_selenium.py +98 -2
  17. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/tests/test_vpn.py +3 -2
  18. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/tasks.py +10 -5
  19. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/tests/test_models.py +56 -12
  20. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/tests/test_tasks.py +50 -0
  21. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/geo/tests/test_admin.py +6 -0
  22. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/geo/tests/test_selenium.py +5 -1
  23. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/rule_types/base.py +28 -8
  24. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/rule_types/vpn.py +3 -2
  25. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/tests/test_admin.py +137 -3
  26. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3/openwisp_controller.egg-info}/PKG-INFO +1 -1
  27. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/LICENSE +0 -0
  28. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/MANIFEST.in +0 -0
  29. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/README.rst +0 -0
  30. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/admin.py +0 -0
  31. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/base.py +0 -0
  32. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/checks.py +0 -0
  33. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/__init__.py +0 -0
  34. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/admin.py +0 -0
  35. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/api/__init__.py +0 -0
  36. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/api/download_views.py +0 -0
  37. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/api/filters.py +0 -0
  38. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/api/serializers.py +0 -0
  39. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/api/urls.py +0 -0
  40. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/api/views.py +0 -0
  41. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/api/zerotier_service.py +0 -0
  42. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/apps.py +0 -0
  43. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/base/__init__.py +0 -0
  44. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/base/base.py +0 -0
  45. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/base/channels_consumer.py +0 -0
  46. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/base/tag.py +0 -0
  47. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/base/vpn.py +0 -0
  48. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/controller/__init__.py +0 -0
  49. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/controller/urls.py +0 -0
  50. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/controller/views.py +0 -0
  51. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/crypto.py +0 -0
  52. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/exceptions.py +0 -0
  53. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/exportable.py +0 -0
  54. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/filters.py +0 -0
  55. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/fixtures/test_templates.json +0 -0
  56. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/handlers.py +0 -0
  57. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0001_squashed_0002_config_settings_uuid.py +0 -0
  58. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0003_template_tags.py +0 -0
  59. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0004_add_device_model.py +0 -0
  60. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0005_populate_device.py +0 -0
  61. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0006_config_device_not_null.py +0 -0
  62. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0007_simplify_config.py +0 -0
  63. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0008_update_indexes.py +0 -0
  64. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0009_device_system.py +0 -0
  65. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0010_auto_20180106_1814.py +0 -0
  66. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0011_update_device_mac_address.py +0 -0
  67. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0012_auto_20180219_1501.py +0 -0
  68. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0013_last_ip_management_ip_and_status_applied.py +0 -0
  69. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0014_device_hardware_id.py +0 -0
  70. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0015_default_groups_permissions.py +0 -0
  71. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0016_default_organization_config_settings.py +0 -0
  72. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0017_template_name_organization_unique_together.py +0 -0
  73. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0018_config_context.py +0 -0
  74. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0019_organization_mac_add_hardware_id_name_unique_together.py +0 -0
  75. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0020_remove_config_organization.py +0 -0
  76. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0021_vpn_key.py +0 -0
  77. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0022_vpn_format_dh.py +0 -0
  78. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0024_update_context_data.py +0 -0
  79. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0025_update_device_key.py +0 -0
  80. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0026_hardware_id_not_unique.py +0 -0
  81. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0027_add_indexes_on_ip_fields.py +0 -0
  82. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0029_merge_django_netjsonconfig.py +0 -0
  83. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0030_django_taggit_update.py +0 -0
  84. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0031_update_vpn_dh_param.py +0 -0
  85. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0032_update_legacy_vpn_backend.py +0 -0
  86. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0033_name_unique_per_organization.py +0 -0
  87. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0034_template_required.py +0 -0
  88. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0035_device_name_unique_optional.py +0 -0
  89. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0037_alter_taggedtemplate.py +0 -0
  90. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0038_vpn_subnet.py +0 -0
  91. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0039_wireguard_vxlan_ipam.py +0 -0
  92. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0040_vpnclient_ip_setnull.py +0 -0
  93. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0041_default_groups_organizationconfigsettings_permission.py +0 -0
  94. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0042_multiple_wireguard_tunnels.py +0 -0
  95. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0043_devicegroup_templates.py +0 -0
  96. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0044_config_error_reason.py +0 -0
  97. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0045_alter_vpn_webhook_endpoint.py +0 -0
  98. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0046_organizationlimits.py +0 -0
  99. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0047_add_organizationlimits.py +0 -0
  100. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0048_wifi_radio_band_migration.py +0 -0
  101. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0050_alter_vpnclient_unique_together.py +0 -0
  102. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0052_vpn_node_network_id.py +0 -0
  103. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0053_vpnclient_secret.py +0 -0
  104. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0054_device__is_deactivated.py +0 -0
  105. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0055_alter_config_status.py +0 -0
  106. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0056_vpnclient_template.py +0 -0
  107. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0057_populate_vpnclient_template.py +0 -0
  108. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0058_alter_vpnclient_template.py +0 -0
  109. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0059_zerotier_templates_ow_zt_to_global.py +0 -0
  110. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0060_cleanup_api_task_notification_types.py +0 -0
  111. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0061_config_checksum_db.py +0 -0
  112. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/__init__.py +0 -0
  113. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/models.py +0 -0
  114. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/settings.py +0 -0
  115. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/signals.py +0 -0
  116. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/sortedm2m/__init__.py +0 -0
  117. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/sortedm2m/fields.py +0 -0
  118. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/sortedm2m/forms.py +0 -0
  119. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/static/config/css/admin.css +0 -0
  120. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/static/config/css/device-delete-confirmation.css +0 -0
  121. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/static/config/css/devicegroup.css +0 -0
  122. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/static/config/css/lib/advanced-mode.css +0 -0
  123. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/static/config/css/lib/img/jsoneditor-icons.svg +0 -0
  124. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/static/config/css/lib/jsonschema-ui.css +0 -0
  125. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/static/config/js/device-delete-confirmation.js +0 -0
  126. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/static/config/js/lib/advanced-mode.js +0 -0
  127. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/static/config/js/lib/jsonschema-ui.js +0 -0
  128. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/static/config/js/lib/tomorrow_night_bright.js +0 -0
  129. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/static/config/js/management_ip.js +0 -0
  130. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/static/config/js/preview.js +0 -0
  131. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/static/config/js/switcher.js +0 -0
  132. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/static/config/js/tabs.js +0 -0
  133. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/static/config/js/unsaved_changes.js +0 -0
  134. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/static/config/js/utils.js +0 -0
  135. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/static/config/js/vpn.js +0 -0
  136. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/static/config/js/widget.js +0 -0
  137. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/static/import_export/import-openwisp.css +0 -0
  138. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/static/sortedm2m/patch_sortedm2m.js +0 -0
  139. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/static/support.css +0 -0
  140. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/tasks.py +0 -0
  141. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/tasks_zerotier.py +0 -0
  142. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/templates/admin/config/change_device_group.html +0 -0
  143. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/templates/admin/config/change_form.html +0 -0
  144. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/templates/admin/config/change_list_device.html +0 -0
  145. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/templates/admin/config/clone_template_form.html +0 -0
  146. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/templates/admin/config/device/change_form.html +0 -0
  147. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/templates/admin/config/device/delete_confirmation.html +0 -0
  148. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/templates/admin/config/device/delete_selected_confirmation.html +0 -0
  149. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/templates/admin/config/device_recover_form.html +0 -0
  150. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/templates/admin/config/jsonschema-widget.html +0 -0
  151. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/templates/admin/config/preview.html +0 -0
  152. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/templates/admin/config/system_context.html +0 -0
  153. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/templates/admin/device_group/change_form.html +0 -0
  154. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/templates/admin/import_export/import.html +0 -0
  155. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/templates/reversion/config/revision_form.html +0 -0
  156. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/tests/__init__.py +0 -0
  157. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/tests/pytest.py +0 -0
  158. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/tests/test_admin.py +0 -0
  159. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/tests/test_api.py +0 -0
  160. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/tests/test_apps.py +0 -0
  161. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/tests/test_config.py +0 -0
  162. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/tests/test_controller.py +0 -0
  163. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/tests/test_device_group.py +0 -0
  164. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/tests/test_handlers.py +0 -0
  165. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/tests/test_notifications.py +0 -0
  166. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/tests/test_tag.py +0 -0
  167. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/tests/test_template.py +0 -0
  168. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/tests/test_views.py +0 -0
  169. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/tests/utils.py +0 -0
  170. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/urls.py +0 -0
  171. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/utils.py +0 -0
  172. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/validators.py +0 -0
  173. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/views.py +0 -0
  174. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/config/widgets.py +0 -0
  175. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/__init__.py +0 -0
  176. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/admin.py +0 -0
  177. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/api/serializers.py +0 -0
  178. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/api/urls.py +0 -0
  179. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/api/views.py +0 -0
  180. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/apps.py +0 -0
  181. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/base/__init__.py +0 -0
  182. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/base/models.py +0 -0
  183. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/channels/consumers.py +0 -0
  184. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/channels/routing.py +0 -0
  185. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/commands.py +0 -0
  186. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/connectors/__init__.py +0 -0
  187. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/connectors/airos/snmp.py +0 -0
  188. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/connectors/exceptions.py +0 -0
  189. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/connectors/openwrt/__init__.py +0 -0
  190. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/connectors/openwrt/snmp.py +0 -0
  191. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/connectors/openwrt/ssh.py +0 -0
  192. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/connectors/snmp.py +0 -0
  193. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/connectors/ssh.py +0 -0
  194. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/exceptions.py +0 -0
  195. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/migrations/0001_initial.py +0 -0
  196. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/migrations/0002_credentials_auto_add.py +0 -0
  197. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/migrations/0003_default_group_permissions.py +0 -0
  198. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/migrations/0004_django3_1_upgrade.py +0 -0
  199. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/migrations/0005_device_connection_failure_reason.py +0 -0
  200. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/migrations/0006_name_unique_per_organization.py +0 -0
  201. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/migrations/0007_command.py +0 -0
  202. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/migrations/0008_remove_conflicting_deviceconnections.py +0 -0
  203. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/migrations/0009_alter_deviceconnection_unique_together.py +0 -0
  204. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/migrations/__init__.py +0 -0
  205. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/models.py +0 -0
  206. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/schema.py +0 -0
  207. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/settings.py +0 -0
  208. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/signals.py +0 -0
  209. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/static/connection/css/command-inline.css +0 -0
  210. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/static/connection/css/credentials.css +0 -0
  211. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/static/connection/js/commands.js +0 -0
  212. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/static/connection/js/credentials.js +0 -0
  213. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/static/connection/js/lib/reconnecting-websocket.min.js +0 -0
  214. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/tests/__init__.py +0 -0
  215. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/tests/base.py +0 -0
  216. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/tests/pytest.py +0 -0
  217. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/tests/test-key.ed25519 +0 -0
  218. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/tests/test-key.rsa +0 -0
  219. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/tests/test_admin.py +0 -0
  220. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/tests/test_api.py +0 -0
  221. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/tests/test_command_utilities.py +0 -0
  222. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/tests/test_notifications.py +0 -0
  223. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/tests/test_selenium.py +0 -0
  224. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/tests/test_snmp.py +0 -0
  225. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/tests/test_ssh.py +0 -0
  226. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/tests/utils.py +0 -0
  227. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/utils.py +0 -0
  228. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/connection/widgets.py +0 -0
  229. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/context_processors.py +0 -0
  230. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/geo/__init__.py +0 -0
  231. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/geo/admin.py +0 -0
  232. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/geo/api/__init__.py +0 -0
  233. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/geo/api/filters.py +0 -0
  234. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/geo/api/serializers.py +0 -0
  235. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/geo/api/urls.py +0 -0
  236. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/geo/api/views.py +0 -0
  237. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/geo/apps.py +0 -0
  238. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/geo/base/__init__.py +0 -0
  239. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/geo/base/models.py +0 -0
  240. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/geo/channels/__init__.py +0 -0
  241. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/geo/channels/consumers.py +0 -0
  242. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/geo/channels/routing.py +0 -0
  243. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/geo/exportable.py +0 -0
  244. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/geo/migrations/0001_initial.py +0 -0
  245. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/geo/migrations/0002_default_groups_permissions.py +0 -0
  246. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/geo/migrations/0003_alter_devicelocation_floorplan_location.py +0 -0
  247. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/geo/migrations/__init__.py +0 -0
  248. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/geo/models.py +0 -0
  249. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/geo/templates/admin/widgets/foreign_key_raw_id.html +0 -0
  250. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/geo/tests/__init__.py +0 -0
  251. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/geo/tests/pytest.py +0 -0
  252. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/geo/tests/test_admin_inline.py +0 -0
  253. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/geo/tests/test_api.py +0 -0
  254. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/geo/tests/test_apps.py +0 -0
  255. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/geo/tests/test_models.py +0 -0
  256. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/geo/tests/utils.py +0 -0
  257. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/geo/utils.py +0 -0
  258. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/migrations.py +0 -0
  259. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/mixins.py +0 -0
  260. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/pki/__init__.py +0 -0
  261. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/pki/admin.py +0 -0
  262. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/pki/api/serializers.py +0 -0
  263. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/pki/api/urls.py +0 -0
  264. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/pki/api/views.py +0 -0
  265. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/pki/apps.py +0 -0
  266. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/pki/base/__init__.py +0 -0
  267. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/pki/base/models.py +0 -0
  268. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/pki/migrations/0001_initial.py +0 -0
  269. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/pki/migrations/0002_add_organization_name.py +0 -0
  270. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/pki/migrations/0003_fill_organization_name.py +0 -0
  271. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/pki/migrations/0004_auto_20180106_1814.py +0 -0
  272. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/pki/migrations/0005_organizational_unit_name.py +0 -0
  273. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/pki/migrations/0006_add_x509_passphrase_field.py +0 -0
  274. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/pki/migrations/0007_default_groups_permissions.py +0 -0
  275. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/pki/migrations/0008_serial_number_length.py +0 -0
  276. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/pki/migrations/0009_common_name_maxlength_64.py +0 -0
  277. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/pki/migrations/0010_common_name_organization_unique.py +0 -0
  278. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/pki/migrations/0011_disallowed_blank_key_length_or_digest.py +0 -0
  279. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/pki/migrations/__init__.py +0 -0
  280. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/pki/models.py +0 -0
  281. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/pki/static/admin/pki/js/show-org-field.js +0 -0
  282. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/pki/templates/admin/pki/change_form.html +0 -0
  283. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/pki/tests/__init__.py +0 -0
  284. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/pki/tests/test_admin.py +0 -0
  285. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/pki/tests/test_api.py +0 -0
  286. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/pki/tests/test_models.py +0 -0
  287. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/pki/tests/utils.py +0 -0
  288. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/pki/urls.py +0 -0
  289. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/pki/utils.py +0 -0
  290. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/routing.py +0 -0
  291. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/serializers.py +0 -0
  292. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/settings.py +0 -0
  293. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/__init__.py +0 -0
  294. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/admin.py +0 -0
  295. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/apps.py +0 -0
  296. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/base/models.py +0 -0
  297. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/filters.py +0 -0
  298. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/migrations/0001_initial.py +0 -0
  299. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/migrations/0002_default_group_migration.py +0 -0
  300. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/migrations/0003_related_field_allow_blank.py +0 -0
  301. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/migrations/0004_index_rule_on_delete.py +0 -0
  302. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/migrations/0005_number_of_subnets_and_ips.py +0 -0
  303. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/migrations/__init__.py +0 -0
  304. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/models.py +0 -0
  305. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/rule_types/__init__.py +0 -0
  306. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/rule_types/device.py +0 -0
  307. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/settings.py +0 -0
  308. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/signals.py +0 -0
  309. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/static/subnet-division/css/subnet-division.css +0 -0
  310. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/static/subnet-division/js/subnet-division.js +0 -0
  311. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/tasks.py +0 -0
  312. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/tests/__init__.py +0 -0
  313. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/tests/helpers.py +0 -0
  314. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/tests/test_models.py +0 -0
  315. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/tests/test_rule.py +0 -0
  316. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/utils.py +0 -0
  317. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/tests/__init__.py +0 -0
  318. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/tests/mixins.py +0 -0
  319. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/tests/test_selenium.py +0 -0
  320. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/tests/test_users_integration.py +0 -0
  321. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/tests/test_utilities.py +0 -0
  322. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/tests/utils.py +0 -0
  323. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/urls.py +0 -0
  324. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller/vpn_backends.py +0 -0
  325. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller.egg-info/SOURCES.txt +0 -0
  326. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller.egg-info/dependency_links.txt +0 -0
  327. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller.egg-info/not-zip-safe +0 -0
  328. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller.egg-info/requires.txt +0 -0
  329. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/openwisp_controller.egg-info/top_level.txt +0 -0
  330. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/pyproject.toml +0 -0
  331. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/requirements.txt +0 -0
  332. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/setup.cfg +0 -0
  333. {openwisp_controller-1.2.1 → openwisp_controller-1.2.3}/setup.py +0 -0
@@ -1,6 +1,30 @@
1
1
  Changelog
2
2
  =========
3
3
 
4
+ Version 1.2.3 [2026-04-09]
5
+ --------------------------
6
+
7
+ Bugfixes
8
+ ~~~~~~~~
9
+
10
+ - Corrected initial field value assignment in ``AbstractDevice`` class
11
+ - Fixed duplicate template entries in Device admin
12
+ - Invalidate Config checksum after subnet provisioning
13
+ - Fixed concurrent ``update_config`` detection `#1204
14
+ <https://github.com/openwisp/openwisp-controller/issues/1204>`_
15
+
16
+ Version 1.2.2 [2026-03-06]
17
+ --------------------------
18
+
19
+ Changes
20
+ ~~~~~~~
21
+
22
+ Other changes
23
+ +++++++++++++
24
+
25
+ - Improved help text of configuration variable fields
26
+ - Minor fixes in the test suite
27
+
4
28
  Version 1.2.1 [2026-03-04]
5
29
  --------------------------
6
30
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openwisp-controller
3
- Version: 1.2.1
3
+ Version: 1.2.3
4
4
  Summary: OpenWISP Controller
5
5
  Home-page: http://openwisp.org
6
6
  Download-URL: https://github.com/openwisp/openwisp-controller/releases
@@ -1,4 +1,4 @@
1
- VERSION = (1, 2, 1, "final")
1
+ VERSION = (1, 2, 3, "final")
2
2
  __version__ = VERSION # alias
3
3
 
4
4
 
@@ -94,10 +94,10 @@ class AbstractConfig(ChecksumCacheMixin, BaseConfig):
94
94
  blank=True,
95
95
  default=dict,
96
96
  help_text=_(
97
- "Additional "
98
- '<a href="http://netjsonconfig.openwisp.org/'
99
- 'en/stable/general/basics.html#context" target="_blank">'
100
- "context (configuration variables)</a> in JSON format"
97
+ "allows overriding "
98
+ '<a href="https://openwisp.io/docs/stable/controller/user/variables.html'
99
+ '" target="_blank">'
100
+ "configuration variables</a>"
101
101
  ),
102
102
  load_kwargs={"object_pairs_hook": collections.OrderedDict},
103
103
  dump_kwargs={"indent": 4},
@@ -325,9 +325,9 @@ class AbstractDevice(OrgMixin, BaseModel):
325
325
  if not present_values:
326
326
  return
327
327
  self.refresh_from_db(fields=present_values.keys())
328
- for field in self._changed_checked_fields:
329
- setattr(self, f"_initial_{field}", field)
330
- setattr(self, field, present_values[field])
328
+ for field, value in present_values.items():
329
+ setattr(self, f"_initial_{field}", getattr(self, field))
330
+ setattr(self, field, value)
331
331
 
332
332
  def _check_name_changed(self):
333
333
  if self._initial_name == models.DEFERRED:
@@ -42,8 +42,9 @@ class AbstractDeviceGroup(OrgMixin, TimeStampedEditableModel):
42
42
  load_kwargs={"object_pairs_hook": collections.OrderedDict},
43
43
  dump_kwargs={"indent": 4},
44
44
  help_text=_(
45
- "Group meta data, use this field to store data which is related"
46
- " to this group and can be retrieved via the REST API."
45
+ "Store custom metadata related to this group. This field is intended "
46
+ "for arbitrary data that does not affect device configuration and can "
47
+ "be retrieved via the REST API for integrations or external tools."
47
48
  ),
48
49
  verbose_name=_("Metadata"),
49
50
  )
@@ -53,8 +54,7 @@ class AbstractDeviceGroup(OrgMixin, TimeStampedEditableModel):
53
54
  load_kwargs={"object_pairs_hook": collections.OrderedDict},
54
55
  dump_kwargs={"indent": 4},
55
56
  help_text=_(
56
- "This field can be used to add meta data for the group"
57
- ' or to add "Configuration Variables" to the devices.'
57
+ "Define configuration variables available to all devices in this group"
58
58
  ),
59
59
  verbose_name=_("Configuration Variables"),
60
60
  )
@@ -37,7 +37,8 @@ class AbstractOrganizationConfigSettings(UUIDModel):
37
37
  load_kwargs={"object_pairs_hook": collections.OrderedDict},
38
38
  dump_kwargs={"indent": 4},
39
39
  help_text=_(
40
- 'This field can be used to add "Configuration Variables"' " to the devices."
40
+ "Define reusable configuration variables available "
41
+ "to all devices in this organization"
41
42
  ),
42
43
  verbose_name=_("Configuration Variables"),
43
44
  )
@@ -97,10 +97,9 @@ class AbstractTemplate(ShareableOrgMixinUniqueName, BaseConfig):
97
97
  default=dict,
98
98
  blank=True,
99
99
  help_text=_(
100
- "A dictionary containing the default "
101
- "values for the variables used by this "
102
- "template; these default variables will "
103
- "be used during schema validation."
100
+ "Define default values for the variables used in this template. "
101
+ "These values are used during validation and when a variable is "
102
+ "not provided by the device, group, or organization."
104
103
  ),
105
104
  load_kwargs={"object_pairs_hook": OrderedDict},
106
105
  dump_kwargs={"indent": 4},
@@ -18,9 +18,10 @@ class Migration(migrations.Migration):
18
18
  default=dict,
19
19
  dump_kwargs={"ensure_ascii": False, "indent": 4},
20
20
  help_text=(
21
- 'Additional <a href="http://netjsonconfig.openwisp.org'
22
- '/en/stable/general/basics.html#context" target="_blank">'
23
- "context (configuration variables)</a> in JSON format"
21
+ "allows overriding "
22
+ '<a href="https://openwisp.io/docs/stable/controller/user/variables.html' # noqa: E501
23
+ '" target="_blank">'
24
+ "configuration variables</a>"
24
25
  ),
25
26
  load_kwargs={"object_pairs_hook": collections.OrderedDict},
26
27
  ),
@@ -18,9 +18,9 @@ class Migration(migrations.Migration):
18
18
  default=dict,
19
19
  dump_kwargs={"ensure_ascii": False, "indent": 4},
20
20
  help_text=(
21
- "A dictionary containing the default values for "
22
- "the variables used by this template; these default "
23
- "variables will be used during schema validation."
21
+ "Define default values for the variables used in this template. "
22
+ "These values are used during validation and when a variable is "
23
+ "not provided by the device, group, or organization."
24
24
  ),
25
25
  load_kwargs={"object_pairs_hook": collections.OrderedDict},
26
26
  verbose_name="Default Values",
@@ -62,9 +62,11 @@ class Migration(migrations.Migration):
62
62
  dump_kwargs={"ensure_ascii": False, "indent": 4},
63
63
  load_kwargs={"object_pairs_hook": collections.OrderedDict},
64
64
  help_text=(
65
- "Group meta data, use this field to store data which is"
66
- " related to this group and can be retrieved via the"
67
- " REST API."
65
+ "Store custom metadata related to this group. "
66
+ "This field is intended for arbitrary data that "
67
+ "does not affect device configuration and can "
68
+ "be retrieved via the REST API for integrations "
69
+ "or external tools."
68
70
  ),
69
71
  verbose_name="Metadata",
70
72
  ),
@@ -20,8 +20,8 @@ class Migration(migrations.Migration):
20
20
  default=dict,
21
21
  dump_kwargs={"ensure_ascii": False, "indent": 4},
22
22
  help_text=(
23
- "This field can be used to add meta data for the group"
24
- ' or to add "Configuration Variables" to the devices.'
23
+ "Define configuration variables available "
24
+ "to all devices in this group"
25
25
  ),
26
26
  load_kwargs={"object_pairs_hook": collections.OrderedDict},
27
27
  verbose_name="Configuration Variables",
@@ -20,8 +20,8 @@ class Migration(migrations.Migration):
20
20
  default=dict,
21
21
  dump_kwargs={"indent": 4},
22
22
  help_text=(
23
- 'This field can be used to add "Configuration Variables"'
24
- " to the devices."
23
+ "Define reusable configuration variables available "
24
+ "to all devices in this organization"
25
25
  ),
26
26
  load_kwargs={"object_pairs_hook": collections.OrderedDict},
27
27
  verbose_name="Configuration Variables",
@@ -138,7 +138,7 @@ django.jQuery(function ($) {
138
138
  resetTemplateOptions();
139
139
  var enabledTemplates = [],
140
140
  sortedm2mUl = $("ul.sortedm2m-items:first"),
141
- sortedm2mPrefixUl = $("ul.sortedm2m-items:last");
141
+ sortedm2mPrefixUl = $("#config-empty ul.sortedm2m-items");
142
142
 
143
143
  // Adds "li" elements for templates
144
144
  Object.keys(data).forEach(function (templateId, index) {
@@ -529,6 +529,20 @@ class TestDevice(
529
529
  with self.assertNumQueries(3):
530
530
  device._check_changed_fields()
531
531
 
532
+ def test_deferred_fields_populated_correctly(self):
533
+ device = self._create_device(
534
+ name="deferred-test",
535
+ management_ip="10.0.0.1",
536
+ )
537
+ # Load the instance with deferred fields omitted
538
+ device = Device.objects.only("id").get(pk=device.pk)
539
+ device.management_ip = "10.0.0.55"
540
+ # Saving the device object will populate the deferred fields
541
+ device.save()
542
+ # Ensure `_initial_<field>` contains the actual value, not the field name
543
+ self.assertEqual(getattr(device, "_initial_management_ip"), "10.0.0.55")
544
+ self.assertNotEqual(getattr(device, "_initial_management_ip"), "management_ip")
545
+
532
546
  def test_exceed_organization_device_limit(self):
533
547
  org = self._get_org()
534
548
  org.config_limits.device_limit = 1
@@ -1,5 +1,6 @@
1
1
  import time
2
2
 
3
+ from django.contrib.auth.models import Group, Permission
3
4
  from django.contrib.staticfiles.testing import StaticLiveServerTestCase
4
5
  from django.test import tag
5
6
  from django.urls.base import reverse
@@ -40,9 +41,17 @@ class SeleniumTestMixin(BaseSeleniumTestMixin):
40
41
  hidden = hidden or []
41
42
  visible = visible or []
42
43
  for template in hidden:
43
- self.wait_for_invisibility(By.XPATH, f'//*[@value="{template.id}"]')
44
+ self.wait_for_invisibility(
45
+ By.XPATH,
46
+ f'//ul[contains(@class,"sortedm2m-items")]'
47
+ f'//input[@value="{template.id}"]',
48
+ )
44
49
  for template in visible:
45
- self.wait_for_visibility(By.XPATH, f'//*[@value="{template.id}"]')
50
+ self.wait_for_visibility(
51
+ By.XPATH,
52
+ f'//ul[contains(@class,"sortedm2m-items")]'
53
+ f'//input[@value="{template.id}"]',
54
+ )
46
55
 
47
56
 
48
57
  @tag("selenium_tests")
@@ -373,6 +382,93 @@ class TestDeviceAdmin(
373
382
  self.assertEqual(config.templates.count(), 0)
374
383
  self.assertEqual(config.status, "modified")
375
384
 
385
+ def test_relevant_templates_duplicates(self):
386
+ """
387
+ Test that a user with specific permissions can see shared templates
388
+ properly. Verifies that:
389
+ 1. User with custom group permissions can access the admin
390
+ 2. Multiple shared templates are displayed correctly
391
+ 3. Each template appears only once in the sortedm2m list
392
+ """
393
+ # Define permission codenames for the custom group
394
+ permission_codenames = [
395
+ "view_group",
396
+ "change_config",
397
+ "view_config",
398
+ "add_device",
399
+ "change_device",
400
+ "delete_device",
401
+ "view_device",
402
+ "view_devicegroup",
403
+ "view_template",
404
+ ]
405
+ # Create a custom group with the specified permissions
406
+ permissions = Permission.objects.filter(codename__in=permission_codenames)
407
+ custom_group, _ = Group.objects.get_or_create(name="Custom Operator")
408
+ custom_group.permissions.set(permissions)
409
+ # Create a user and assign the custom group
410
+ user = self._create_user(
411
+ username="limited_user",
412
+ password="testpass123",
413
+ email="limited@test.com",
414
+ is_staff=True,
415
+ )
416
+ user.groups.add(custom_group)
417
+ org = self._get_org()
418
+ self._create_org_user(user=user, organization=org, is_admin=True)
419
+ # Create multiple shared templates (organization=None)
420
+ template1 = self._create_template(
421
+ name="Shared Template 1", organization=None, default=True
422
+ )
423
+ template2 = self._create_template(name="Shared Template 2", organization=None)
424
+ device = self._create_config(organization=org).device
425
+ # Login as the limited user
426
+ self.login(username="limited_user", password="testpass123")
427
+ # Navigate using Selenium
428
+ self.open(
429
+ reverse("admin:config_device_change", args=[device.id]) + "#config-group"
430
+ )
431
+ self.hide_loading_overlay()
432
+ with self.subTest(
433
+ "Regression precondition: empty Config inline is not rendered"
434
+ ):
435
+ self.assertFalse(self.web_driver.find_elements(By.ID, "config-empty"))
436
+
437
+ with self.subTest("All shared templates should be visible"):
438
+ self._verify_templates_visibility(visible=[template1, template2])
439
+
440
+ with self.subTest("Verify sortedm2m list has exactly 2 template items"):
441
+ # Check that ul.sortedm2m-items.sortedm2m.ui-sortable has exactly 2 children
442
+ # with .sortedm2m-item class
443
+ sortedm2m_items = self.find_elements(
444
+ by=By.CSS_SELECTOR,
445
+ value="ul.sortedm2m-items.sortedm2m.ui-sortable > li.sortedm2m-item",
446
+ )
447
+ self.assertEqual(
448
+ len(sortedm2m_items),
449
+ 2,
450
+ (
451
+ "Expected exactly 2 template items in sortedm2m list,"
452
+ f" found {len(sortedm2m_items)}"
453
+ ),
454
+ )
455
+
456
+ with self.subTest(
457
+ "Verify checkbox inputs are rendered with expected attributes"
458
+ ):
459
+ for idx, template_id in enumerate([template1.id, template2.id]):
460
+ checkbox = self.find_element(
461
+ by=By.ID, value=f"id_config-templates_{idx}"
462
+ )
463
+ self.assertEqual(checkbox.get_attribute("value"), str(template_id))
464
+ self.assertEqual(checkbox.get_attribute("data-required"), "false")
465
+
466
+ with self.subTest("Save operation completes successfully"):
467
+ # Scroll to the top of the page to ensure the save button is visible
468
+ self.web_driver.execute_script("window.scrollTo(0, 0);")
469
+ self.find_element(by=By.NAME, value="_save").click()
470
+ self.wait_for_presence(By.CSS_SELECTOR, ".messagelist .success", timeout=5)
471
+
376
472
 
377
473
  @tag("selenium_tests")
378
474
  class TestDeviceGroupAdmin(
@@ -3,12 +3,13 @@ import uuid
3
3
  from subprocess import CalledProcessError, TimeoutExpired
4
4
  from unittest import mock
5
5
 
6
+ import requests
6
7
  from celery.exceptions import Retry, SoftTimeLimitExceeded
7
8
  from django.conf import settings
8
9
  from django.core.exceptions import ValidationError
9
10
  from django.db.models.signals import post_save
10
11
  from django.db.utils import IntegrityError
11
- from django.http.response import HttpResponse, HttpResponseNotFound
12
+ from django.http.response import HttpResponse
12
13
  from django.test import TestCase, TransactionTestCase
13
14
  from requests.exceptions import ConnectionError, RequestException, Timeout
14
15
  from swapper import load_model
@@ -855,7 +856,7 @@ class TestWireguardTransaction(BaseTestVpn, TestWireguardVpnMixin, TransactionTe
855
856
  fail_response.raise_for_status.side_effect = requests.exceptions.HTTPError(
856
857
  "Not Found"
857
858
  )
858
- with mock.patch("logging.Logger.warning") as mocked_logger, mock.patch(
859
+ with mock.patch("logging.Logger.error") as mocked_logger, mock.patch(
859
860
  "requests.post", return_value=fail_response
860
861
  ):
861
862
  post_save.send(
@@ -16,20 +16,25 @@ logger = logging.getLogger(__name__)
16
16
  _TASK_NAME = "openwisp_controller.connection.tasks.update_config"
17
17
 
18
18
 
19
- def _is_update_in_progress(device_id):
19
+ def _is_update_in_progress(device_id, current_task_id=None):
20
20
  active = current_app.control.inspect().active()
21
21
  if not active:
22
22
  return False
23
23
  # check if there's any other running task before adding it
24
+ # exclude the current task by comparing task IDs
24
25
  for task_list in active.values():
25
26
  for task in task_list:
26
- if task["name"] == _TASK_NAME and str(device_id) in task["args"]:
27
+ if (
28
+ task["name"] == _TASK_NAME
29
+ and str(device_id) in task["args"]
30
+ and task["id"] != current_task_id
31
+ ):
27
32
  return True
28
33
  return False
29
34
 
30
35
 
31
- @shared_task
32
- def update_config(device_id):
36
+ @shared_task(bind=True)
37
+ def update_config(self, device_id):
33
38
  """
34
39
  Launches the ``update_config()`` operation
35
40
  of a specific device in the background
@@ -48,7 +53,7 @@ def update_config(device_id):
48
53
  except ObjectDoesNotExist as e:
49
54
  logger.warning(f'update_config("{device_id}") failed: {e}')
50
55
  return
51
- if _is_update_in_progress(device_id):
56
+ if _is_update_in_progress(device_id, current_task_id=self.request.id):
52
57
  return
53
58
  try:
54
59
  device_conn = DeviceConnection.get_working_connection(device)
@@ -1,6 +1,7 @@
1
1
  import socket
2
2
  from unittest import mock
3
3
  from unittest.mock import PropertyMock
4
+ from uuid import uuid4
4
5
 
5
6
  import paramiko
6
7
  from django.contrib.auth.models import ContentType
@@ -1026,20 +1027,56 @@ class TestModelsTransaction(BaseTestModels, TransactionTestCase):
1026
1027
  @mock.patch.object(DeviceConnection, "update_config")
1027
1028
  @mock.patch.object(DeviceConnection, "get_working_connection")
1028
1029
  def test_device_update_config_in_progress(
1029
- self, mocked_get_working_connection, update_config, mocked_sleep
1030
+ self, mocked_get_working_connection, mocked_update_config, mocked_sleep
1030
1031
  ):
1031
1032
  conf = self._prepare_conf_object()
1032
1033
 
1033
- with mock.patch("celery.app.control.Inspect.active") as mocked_active:
1034
- mocked_active.return_value = {
1035
- "task": [{"name": _TASK_NAME, "args": [str(conf.device.pk)]}]
1036
- }
1037
- conf.config = {"general": {"timezone": "UTC"}}
1038
- conf.full_clean()
1039
- conf.save()
1040
- mocked_active.assert_called_once()
1041
- mocked_get_working_connection.assert_not_called()
1042
- update_config.assert_not_called()
1034
+ with self.subTest("More than one update_config task active for the device"):
1035
+ with mock.patch("celery.app.control.Inspect.active") as mocked_active:
1036
+ mocked_active.return_value = {
1037
+ "task": [
1038
+ {
1039
+ "name": _TASK_NAME,
1040
+ "args": [str(conf.device.pk)],
1041
+ "id": str(uuid4()),
1042
+ }
1043
+ ]
1044
+ }
1045
+ conf.config = {"general": {"timezone": "UTC"}}
1046
+ conf.full_clean()
1047
+ conf.save()
1048
+ mocked_active.assert_called_once()
1049
+ mocked_get_working_connection.assert_not_called()
1050
+ mocked_update_config.assert_not_called()
1051
+
1052
+ Config.objects.update(status="applied")
1053
+ mocked_get_working_connection.return_value = (
1054
+ conf.device.deviceconnection_set.first()
1055
+ )
1056
+ with self.subTest("Only one task is active for the device"):
1057
+ task_id = str(uuid4())
1058
+ with mock.patch(
1059
+ "celery.app.control.Inspect.active"
1060
+ ) as mocked_active, mock.patch(
1061
+ "celery.app.task.Context.id",
1062
+ new_callable=mock.PropertyMock,
1063
+ return_value=task_id,
1064
+ ):
1065
+ mocked_active.return_value = {
1066
+ "task": [
1067
+ {
1068
+ "name": _TASK_NAME,
1069
+ "args": [str(conf.device.pk)],
1070
+ "id": task_id,
1071
+ }
1072
+ ]
1073
+ }
1074
+ conf.config = {"general": {"timezone": "Asia/Kolkata"}}
1075
+ conf.full_clean()
1076
+ conf.save()
1077
+ mocked_active.assert_called_once()
1078
+ mocked_get_working_connection.assert_called_once()
1079
+ mocked_update_config.assert_called_once()
1043
1080
 
1044
1081
  @mock.patch("time.sleep")
1045
1082
  @mock.patch.object(DeviceConnection, "update_config")
@@ -1053,8 +1090,15 @@ class TestModelsTransaction(BaseTestModels, TransactionTestCase):
1053
1090
  )
1054
1091
 
1055
1092
  with mock.patch("celery.app.control.Inspect.active") as mocked_active:
1093
+ # Mock a task running for a different device (args is different)
1056
1094
  mocked_active.return_value = {
1057
- "task": [{"name": _TASK_NAME, "args": ["..."]}]
1095
+ "task": [
1096
+ {
1097
+ "name": _TASK_NAME,
1098
+ "args": ["another-device-id"], # Different device
1099
+ "id": "different-task-id",
1100
+ }
1101
+ ]
1058
1102
  }
1059
1103
  conf.config = {"general": {"timezone": "UTC"}}
1060
1104
  conf.full_clean()
@@ -19,6 +19,56 @@ class TestTasks(CreateConnectionsMixin, TestCase):
19
19
  "openwisp_controller.connection.base.models.AbstractDeviceConnection.connect"
20
20
  )
21
21
 
22
+ def _get_mocked_celery_active(self, device_id, task_id=None):
23
+ return {
24
+ "worker1": [
25
+ {
26
+ "name": tasks._TASK_NAME,
27
+ "args": [device_id],
28
+ "id": task_id or str(uuid.uuid4()),
29
+ }
30
+ ]
31
+ }
32
+
33
+ def test_is_update_in_progress_same_task(self):
34
+ device_id = str(uuid.uuid4())
35
+ task_id = str(uuid.uuid4())
36
+ with mock.patch(
37
+ "celery.app.control.Inspect.active",
38
+ return_value=self._get_mocked_celery_active(device_id, task_id),
39
+ ):
40
+ result = tasks._is_update_in_progress(device_id, current_task_id=task_id)
41
+ self.assertEqual(result, False)
42
+
43
+ def test_is_update_in_progress_different_task(self):
44
+ device_id = str(uuid.uuid4())
45
+ current_task_id = str(uuid.uuid4())
46
+ other_task_id = str(uuid.uuid4())
47
+ with mock.patch(
48
+ "celery.app.control.Inspect.active",
49
+ return_value=self._get_mocked_celery_active(device_id, other_task_id),
50
+ ):
51
+ result = tasks._is_update_in_progress(
52
+ device_id, current_task_id=current_task_id
53
+ )
54
+ self.assertEqual(result, True)
55
+
56
+ def test_is_update_in_progress_no_tasks(self):
57
+ device_id = str(uuid.uuid4())
58
+ with mock.patch("celery.app.control.Inspect.active", return_value={}):
59
+ result = tasks._is_update_in_progress(device_id)
60
+ self.assertEqual(result, False)
61
+
62
+ def test_is_update_in_progress_different_device(self):
63
+ device_id = str(uuid.uuid4())
64
+ other_device_id = str(uuid.uuid4())
65
+ with mock.patch(
66
+ "celery.app.control.Inspect.active",
67
+ return_value=self._get_mocked_celery_active(other_device_id),
68
+ ):
69
+ result = tasks._is_update_in_progress(device_id)
70
+ self.assertEqual(result, False)
71
+
22
72
  @mock.patch("logging.Logger.warning")
23
73
  @mock.patch("time.sleep")
24
74
  def test_update_config_missing_config(self, mocked_sleep, mocked_warning):
@@ -29,6 +29,12 @@ class TestAdmin(TestAdminMixin, TestGeoMixin, BaseTestAdmin, TestCase):
29
29
  """override TestAdminMixin.setUp"""
30
30
  pass
31
31
 
32
+ def _get_location_add_params(self, **kwargs):
33
+ params = super()._get_location_add_params(**kwargs)
34
+ if "organization" not in kwargs:
35
+ params["organization"] = self._get_org().id
36
+ return params
37
+
32
38
  def _create_multitenancy_test_env(self, vpn=False):
33
39
  org1 = self._create_organization(name="test1org")
34
40
  org2 = self._create_organization(name="test2org")
@@ -91,7 +91,11 @@ class TestDeviceAdminReadonly(
91
91
  def test_unsaved_changes_readonly(self):
92
92
  self.login()
93
93
  ol = self._create_object_location()
94
- path = reverse("admin:config_device_change", args=[ol.device.id])
94
+ path = reverse(
95
+ f"admin:{self.object_model._meta.app_label}_"
96
+ f"{self.object_model._meta.model_name}_change",
97
+ args=[ol.device.id],
98
+ )
95
99
 
96
100
  with self.subTest("Alert should not be displayed without any change"):
97
101
  self.open(path)
@@ -79,16 +79,36 @@ class BaseSubnetDivisionRuleType(object):
79
79
  def destroyer_receiver(cls, instance, **kwargs):
80
80
  cls.destroy_provisioned_subnets_ips(instance, **kwargs)
81
81
 
82
- @staticmethod
83
- def post_provision_handler(instance, provisioned, **kwargs):
82
+ @classmethod
83
+ def post_provision_handler(cls, instance, provisioned, **kwargs):
84
84
  """
85
- This method should be overridden in inherited rule types to
86
- perform any operation on provisioned subnets and IP addresses.
87
- :param instance: object that triggered provisioning
88
- :param provisioned: dictionary containing subnets and IP addresses
89
- provisioned, None if nothing is provisioned
85
+ Hook for post-provisioning actions on subnets and IP addresses.
86
+
87
+ This method is intended to be extended by subclasses of rule types
88
+ to perform custom operations after subnets and IPs are provisioned.
89
+
90
+ Subnet provisioning is executed asynchronously in Celery workers.
91
+ If the device configuration references variables provided by the
92
+ subnet division rule, the current checksum may have been computed
93
+ using variable names instead of their provisioned values. In such cases,
94
+ `Config.checksum_db` (which tracks persisted configuration changes)
95
+ must be updated to reflect the actual provisioned values, and the
96
+ checksum cache invalidated to avoid stale data.
97
+
98
+ :param instance: The object that triggered the provisioning.
99
+ :param provisioned: Dictionary containing provisioned subnets and IPs,
100
+ or None if no provisioning occurred.
90
101
  """
91
- pass
102
+ if not provisioned:
103
+ return
104
+ config = cls.get_config(instance)
105
+ config._invalidate_backend_instance_cache()
106
+ current_checksum = config.checksum
107
+ if current_checksum != config.checksum_db:
108
+ # Update checksum using the UPDATE query to avoid sending
109
+ # unnecessary signals that may be triggered by `save()` method.
110
+ config._update_checksum_db(current_checksum)
111
+ config.invalidate_checksum_cache()
92
112
 
93
113
  @staticmethod
94
114
  def subnet_provisioned_signal_emitter(instance, provisioned):