openwisp-controller 1.2.2__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.2 → openwisp_controller-1.2.3}/CHANGES.rst +12 -0
  2. {openwisp_controller-1.2.2/openwisp_controller.egg-info → openwisp_controller-1.2.3}/PKG-INFO +1 -1
  3. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/__init__.py +1 -1
  4. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/base/device.py +3 -3
  5. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/static/config/js/relevant_templates.js +1 -1
  6. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/tests/test_device.py +14 -0
  7. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/tests/test_selenium.py +98 -2
  8. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/tasks.py +10 -5
  9. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/tests/test_models.py +56 -12
  10. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/tests/test_tasks.py +50 -0
  11. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/geo/tests/test_admin.py +6 -0
  12. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/geo/tests/test_selenium.py +5 -1
  13. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/rule_types/base.py +28 -8
  14. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/rule_types/vpn.py +3 -2
  15. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/tests/test_admin.py +137 -3
  16. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3/openwisp_controller.egg-info}/PKG-INFO +1 -1
  17. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/LICENSE +0 -0
  18. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/MANIFEST.in +0 -0
  19. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/README.rst +0 -0
  20. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/admin.py +0 -0
  21. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/base.py +0 -0
  22. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/checks.py +0 -0
  23. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/__init__.py +0 -0
  24. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/admin.py +0 -0
  25. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/api/__init__.py +0 -0
  26. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/api/download_views.py +0 -0
  27. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/api/filters.py +0 -0
  28. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/api/serializers.py +0 -0
  29. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/api/urls.py +0 -0
  30. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/api/views.py +0 -0
  31. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/api/zerotier_service.py +0 -0
  32. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/apps.py +0 -0
  33. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/base/__init__.py +0 -0
  34. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/base/base.py +0 -0
  35. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/base/channels_consumer.py +0 -0
  36. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/base/config.py +0 -0
  37. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/base/device_group.py +0 -0
  38. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/base/multitenancy.py +0 -0
  39. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/base/tag.py +0 -0
  40. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/base/template.py +0 -0
  41. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/base/vpn.py +0 -0
  42. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/controller/__init__.py +0 -0
  43. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/controller/urls.py +0 -0
  44. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/controller/views.py +0 -0
  45. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/crypto.py +0 -0
  46. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/exceptions.py +0 -0
  47. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/exportable.py +0 -0
  48. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/filters.py +0 -0
  49. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/fixtures/test_templates.json +0 -0
  50. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/handlers.py +0 -0
  51. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0001_squashed_0002_config_settings_uuid.py +0 -0
  52. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0003_template_tags.py +0 -0
  53. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0004_add_device_model.py +0 -0
  54. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0005_populate_device.py +0 -0
  55. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0006_config_device_not_null.py +0 -0
  56. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0007_simplify_config.py +0 -0
  57. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0008_update_indexes.py +0 -0
  58. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0009_device_system.py +0 -0
  59. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0010_auto_20180106_1814.py +0 -0
  60. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0011_update_device_mac_address.py +0 -0
  61. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0012_auto_20180219_1501.py +0 -0
  62. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0013_last_ip_management_ip_and_status_applied.py +0 -0
  63. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0014_device_hardware_id.py +0 -0
  64. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0015_default_groups_permissions.py +0 -0
  65. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0016_default_organization_config_settings.py +0 -0
  66. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0017_template_name_organization_unique_together.py +0 -0
  67. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0018_config_context.py +0 -0
  68. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0019_organization_mac_add_hardware_id_name_unique_together.py +0 -0
  69. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0020_remove_config_organization.py +0 -0
  70. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0021_vpn_key.py +0 -0
  71. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0022_vpn_format_dh.py +0 -0
  72. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0023_update_context.py +0 -0
  73. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0024_update_context_data.py +0 -0
  74. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0025_update_device_key.py +0 -0
  75. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0026_hardware_id_not_unique.py +0 -0
  76. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0027_add_indexes_on_ip_fields.py +0 -0
  77. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0028_template_default_values.py +0 -0
  78. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0029_merge_django_netjsonconfig.py +0 -0
  79. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0030_django_taggit_update.py +0 -0
  80. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0031_update_vpn_dh_param.py +0 -0
  81. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0032_update_legacy_vpn_backend.py +0 -0
  82. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0033_name_unique_per_organization.py +0 -0
  83. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0034_template_required.py +0 -0
  84. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0035_device_name_unique_optional.py +0 -0
  85. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0036_device_group.py +0 -0
  86. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0037_alter_taggedtemplate.py +0 -0
  87. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0038_vpn_subnet.py +0 -0
  88. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0039_wireguard_vxlan_ipam.py +0 -0
  89. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0040_vpnclient_ip_setnull.py +0 -0
  90. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0041_default_groups_organizationconfigsettings_permission.py +0 -0
  91. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0042_multiple_wireguard_tunnels.py +0 -0
  92. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0043_devicegroup_templates.py +0 -0
  93. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0044_config_error_reason.py +0 -0
  94. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0045_alter_vpn_webhook_endpoint.py +0 -0
  95. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0046_organizationlimits.py +0 -0
  96. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0047_add_organizationlimits.py +0 -0
  97. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0048_wifi_radio_band_migration.py +0 -0
  98. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0049_devicegroup_context.py +0 -0
  99. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0050_alter_vpnclient_unique_together.py +0 -0
  100. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0051_organizationconfigsettings_context.py +0 -0
  101. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0052_vpn_node_network_id.py +0 -0
  102. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0053_vpnclient_secret.py +0 -0
  103. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0054_device__is_deactivated.py +0 -0
  104. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0055_alter_config_status.py +0 -0
  105. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0056_vpnclient_template.py +0 -0
  106. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0057_populate_vpnclient_template.py +0 -0
  107. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0058_alter_vpnclient_template.py +0 -0
  108. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0059_zerotier_templates_ow_zt_to_global.py +0 -0
  109. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0060_cleanup_api_task_notification_types.py +0 -0
  110. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/0061_config_checksum_db.py +0 -0
  111. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/migrations/__init__.py +0 -0
  112. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/models.py +0 -0
  113. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/settings.py +0 -0
  114. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/signals.py +0 -0
  115. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/sortedm2m/__init__.py +0 -0
  116. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/sortedm2m/fields.py +0 -0
  117. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/sortedm2m/forms.py +0 -0
  118. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/static/config/css/admin.css +0 -0
  119. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/static/config/css/device-delete-confirmation.css +0 -0
  120. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/static/config/css/devicegroup.css +0 -0
  121. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/static/config/css/lib/advanced-mode.css +0 -0
  122. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/static/config/css/lib/img/jsoneditor-icons.svg +0 -0
  123. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/static/config/css/lib/jsonschema-ui.css +0 -0
  124. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/static/config/js/device-delete-confirmation.js +0 -0
  125. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/static/config/js/lib/advanced-mode.js +0 -0
  126. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/static/config/js/lib/jsonschema-ui.js +0 -0
  127. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/static/config/js/lib/tomorrow_night_bright.js +0 -0
  128. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/static/config/js/management_ip.js +0 -0
  129. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/static/config/js/preview.js +0 -0
  130. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/static/config/js/switcher.js +0 -0
  131. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/static/config/js/tabs.js +0 -0
  132. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/static/config/js/unsaved_changes.js +0 -0
  133. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/static/config/js/utils.js +0 -0
  134. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/static/config/js/vpn.js +0 -0
  135. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/static/config/js/widget.js +0 -0
  136. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/static/import_export/import-openwisp.css +0 -0
  137. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/static/sortedm2m/patch_sortedm2m.js +0 -0
  138. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/static/support.css +0 -0
  139. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/tasks.py +0 -0
  140. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/tasks_zerotier.py +0 -0
  141. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/templates/admin/config/change_device_group.html +0 -0
  142. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/templates/admin/config/change_form.html +0 -0
  143. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/templates/admin/config/change_list_device.html +0 -0
  144. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/templates/admin/config/clone_template_form.html +0 -0
  145. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/templates/admin/config/device/change_form.html +0 -0
  146. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/templates/admin/config/device/delete_confirmation.html +0 -0
  147. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/templates/admin/config/device/delete_selected_confirmation.html +0 -0
  148. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/templates/admin/config/device_recover_form.html +0 -0
  149. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/templates/admin/config/jsonschema-widget.html +0 -0
  150. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/templates/admin/config/preview.html +0 -0
  151. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/templates/admin/config/system_context.html +0 -0
  152. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/templates/admin/device_group/change_form.html +0 -0
  153. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/templates/admin/import_export/import.html +0 -0
  154. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/templates/reversion/config/revision_form.html +0 -0
  155. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/tests/__init__.py +0 -0
  156. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/tests/pytest.py +0 -0
  157. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/tests/test_admin.py +0 -0
  158. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/tests/test_api.py +0 -0
  159. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/tests/test_apps.py +0 -0
  160. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/tests/test_config.py +0 -0
  161. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/tests/test_controller.py +0 -0
  162. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/tests/test_device_group.py +0 -0
  163. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/tests/test_handlers.py +0 -0
  164. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/tests/test_notifications.py +0 -0
  165. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/tests/test_tag.py +0 -0
  166. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/tests/test_template.py +0 -0
  167. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/tests/test_views.py +0 -0
  168. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/tests/test_vpn.py +0 -0
  169. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/tests/utils.py +0 -0
  170. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/urls.py +0 -0
  171. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/utils.py +0 -0
  172. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/validators.py +0 -0
  173. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/views.py +0 -0
  174. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/config/widgets.py +0 -0
  175. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/__init__.py +0 -0
  176. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/admin.py +0 -0
  177. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/api/serializers.py +0 -0
  178. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/api/urls.py +0 -0
  179. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/api/views.py +0 -0
  180. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/apps.py +0 -0
  181. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/base/__init__.py +0 -0
  182. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/base/models.py +0 -0
  183. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/channels/consumers.py +0 -0
  184. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/channels/routing.py +0 -0
  185. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/commands.py +0 -0
  186. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/connectors/__init__.py +0 -0
  187. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/connectors/airos/snmp.py +0 -0
  188. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/connectors/exceptions.py +0 -0
  189. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/connectors/openwrt/__init__.py +0 -0
  190. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/connectors/openwrt/snmp.py +0 -0
  191. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/connectors/openwrt/ssh.py +0 -0
  192. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/connectors/snmp.py +0 -0
  193. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/connectors/ssh.py +0 -0
  194. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/exceptions.py +0 -0
  195. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/migrations/0001_initial.py +0 -0
  196. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/migrations/0002_credentials_auto_add.py +0 -0
  197. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/migrations/0003_default_group_permissions.py +0 -0
  198. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/migrations/0004_django3_1_upgrade.py +0 -0
  199. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/migrations/0005_device_connection_failure_reason.py +0 -0
  200. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/migrations/0006_name_unique_per_organization.py +0 -0
  201. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/migrations/0007_command.py +0 -0
  202. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/migrations/0008_remove_conflicting_deviceconnections.py +0 -0
  203. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/migrations/0009_alter_deviceconnection_unique_together.py +0 -0
  204. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/migrations/__init__.py +0 -0
  205. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/models.py +0 -0
  206. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/schema.py +0 -0
  207. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/settings.py +0 -0
  208. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/signals.py +0 -0
  209. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/static/connection/css/command-inline.css +0 -0
  210. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/static/connection/css/credentials.css +0 -0
  211. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/static/connection/js/commands.js +0 -0
  212. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/static/connection/js/credentials.js +0 -0
  213. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/static/connection/js/lib/reconnecting-websocket.min.js +0 -0
  214. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/tests/__init__.py +0 -0
  215. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/tests/base.py +0 -0
  216. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/tests/pytest.py +0 -0
  217. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/tests/test-key.ed25519 +0 -0
  218. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/tests/test-key.rsa +0 -0
  219. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/tests/test_admin.py +0 -0
  220. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/tests/test_api.py +0 -0
  221. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/tests/test_command_utilities.py +0 -0
  222. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/tests/test_notifications.py +0 -0
  223. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/tests/test_selenium.py +0 -0
  224. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/tests/test_snmp.py +0 -0
  225. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/tests/test_ssh.py +0 -0
  226. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/tests/utils.py +0 -0
  227. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/utils.py +0 -0
  228. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/connection/widgets.py +0 -0
  229. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/context_processors.py +0 -0
  230. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/geo/__init__.py +0 -0
  231. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/geo/admin.py +0 -0
  232. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/geo/api/__init__.py +0 -0
  233. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/geo/api/filters.py +0 -0
  234. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/geo/api/serializers.py +0 -0
  235. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/geo/api/urls.py +0 -0
  236. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/geo/api/views.py +0 -0
  237. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/geo/apps.py +0 -0
  238. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/geo/base/__init__.py +0 -0
  239. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/geo/base/models.py +0 -0
  240. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/geo/channels/__init__.py +0 -0
  241. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/geo/channels/consumers.py +0 -0
  242. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/geo/channels/routing.py +0 -0
  243. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/geo/exportable.py +0 -0
  244. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/geo/migrations/0001_initial.py +0 -0
  245. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/geo/migrations/0002_default_groups_permissions.py +0 -0
  246. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/geo/migrations/0003_alter_devicelocation_floorplan_location.py +0 -0
  247. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/geo/migrations/__init__.py +0 -0
  248. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/geo/models.py +0 -0
  249. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/geo/templates/admin/widgets/foreign_key_raw_id.html +0 -0
  250. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/geo/tests/__init__.py +0 -0
  251. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/geo/tests/pytest.py +0 -0
  252. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/geo/tests/test_admin_inline.py +0 -0
  253. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/geo/tests/test_api.py +0 -0
  254. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/geo/tests/test_apps.py +0 -0
  255. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/geo/tests/test_models.py +0 -0
  256. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/geo/tests/utils.py +0 -0
  257. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/geo/utils.py +0 -0
  258. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/migrations.py +0 -0
  259. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/mixins.py +0 -0
  260. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/pki/__init__.py +0 -0
  261. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/pki/admin.py +0 -0
  262. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/pki/api/serializers.py +0 -0
  263. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/pki/api/urls.py +0 -0
  264. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/pki/api/views.py +0 -0
  265. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/pki/apps.py +0 -0
  266. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/pki/base/__init__.py +0 -0
  267. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/pki/base/models.py +0 -0
  268. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/pki/migrations/0001_initial.py +0 -0
  269. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/pki/migrations/0002_add_organization_name.py +0 -0
  270. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/pki/migrations/0003_fill_organization_name.py +0 -0
  271. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/pki/migrations/0004_auto_20180106_1814.py +0 -0
  272. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/pki/migrations/0005_organizational_unit_name.py +0 -0
  273. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/pki/migrations/0006_add_x509_passphrase_field.py +0 -0
  274. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/pki/migrations/0007_default_groups_permissions.py +0 -0
  275. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/pki/migrations/0008_serial_number_length.py +0 -0
  276. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/pki/migrations/0009_common_name_maxlength_64.py +0 -0
  277. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/pki/migrations/0010_common_name_organization_unique.py +0 -0
  278. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/pki/migrations/0011_disallowed_blank_key_length_or_digest.py +0 -0
  279. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/pki/migrations/__init__.py +0 -0
  280. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/pki/models.py +0 -0
  281. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/pki/static/admin/pki/js/show-org-field.js +0 -0
  282. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/pki/templates/admin/pki/change_form.html +0 -0
  283. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/pki/tests/__init__.py +0 -0
  284. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/pki/tests/test_admin.py +0 -0
  285. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/pki/tests/test_api.py +0 -0
  286. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/pki/tests/test_models.py +0 -0
  287. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/pki/tests/utils.py +0 -0
  288. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/pki/urls.py +0 -0
  289. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/pki/utils.py +0 -0
  290. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/routing.py +0 -0
  291. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/serializers.py +0 -0
  292. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/settings.py +0 -0
  293. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/__init__.py +0 -0
  294. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/admin.py +0 -0
  295. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/apps.py +0 -0
  296. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/base/models.py +0 -0
  297. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/filters.py +0 -0
  298. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/migrations/0001_initial.py +0 -0
  299. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/migrations/0002_default_group_migration.py +0 -0
  300. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/migrations/0003_related_field_allow_blank.py +0 -0
  301. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/migrations/0004_index_rule_on_delete.py +0 -0
  302. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/migrations/0005_number_of_subnets_and_ips.py +0 -0
  303. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/migrations/__init__.py +0 -0
  304. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/models.py +0 -0
  305. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/rule_types/__init__.py +0 -0
  306. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/rule_types/device.py +0 -0
  307. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/settings.py +0 -0
  308. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/signals.py +0 -0
  309. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/static/subnet-division/css/subnet-division.css +0 -0
  310. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/static/subnet-division/js/subnet-division.js +0 -0
  311. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/tasks.py +0 -0
  312. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/tests/__init__.py +0 -0
  313. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/tests/helpers.py +0 -0
  314. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/tests/test_models.py +0 -0
  315. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/tests/test_rule.py +0 -0
  316. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/subnet_division/utils.py +0 -0
  317. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/tests/__init__.py +0 -0
  318. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/tests/mixins.py +0 -0
  319. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/tests/test_selenium.py +0 -0
  320. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/tests/test_users_integration.py +0 -0
  321. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/tests/test_utilities.py +0 -0
  322. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/tests/utils.py +0 -0
  323. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/urls.py +0 -0
  324. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller/vpn_backends.py +0 -0
  325. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller.egg-info/SOURCES.txt +0 -0
  326. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller.egg-info/dependency_links.txt +0 -0
  327. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller.egg-info/not-zip-safe +0 -0
  328. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller.egg-info/requires.txt +0 -0
  329. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/openwisp_controller.egg-info/top_level.txt +0 -0
  330. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/pyproject.toml +0 -0
  331. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/requirements.txt +0 -0
  332. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/setup.cfg +0 -0
  333. {openwisp_controller-1.2.2 → openwisp_controller-1.2.3}/setup.py +0 -0
@@ -1,6 +1,18 @@
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
+
4
16
  Version 1.2.2 [2026-03-06]
5
17
  --------------------------
6
18
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openwisp-controller
3
- Version: 1.2.2
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, 2, "final")
1
+ VERSION = (1, 2, 3, "final")
2
2
  __version__ = VERSION # alias
3
3
 
4
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:
@@ -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(
@@ -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):
@@ -41,8 +41,9 @@ class VpnSubnetDivisionRuleType(BaseSubnetDivisionRuleType):
41
41
  for vpn_client in qs:
42
42
  cls.provision_receiver(instance=vpn_client, created=True)
43
43
 
44
- @staticmethod
45
- def post_provision_handler(instance, provisioned, **kwargs):
44
+ @classmethod
45
+ def post_provision_handler(cls, instance, provisioned, **kwargs):
46
+ super().post_provision_handler(instance, provisioned, **kwargs)
46
47
  # Assign the first provisioned IP address to the VPNClient
47
48
  # only when subnets and IPs have been provisioned
48
49
  if provisioned and provisioned["ip_addresses"]:
@@ -1,16 +1,21 @@
1
1
  from unittest.mock import patch
2
2
 
3
- from django.test import TestCase
3
+ from django.test import TestCase, TransactionTestCase
4
4
  from django.urls import reverse
5
5
  from swapper import load_model
6
6
 
7
- from openwisp_controller.config.tests.utils import TestWireguardVpnMixin
7
+ from openwisp_controller.config.tests.test_admin import TestDeviceAdminMixin
8
+ from openwisp_controller.config.tests.utils import (
9
+ TestVpnX509Mixin,
10
+ TestWireguardVpnMixin,
11
+ )
8
12
  from openwisp_users.tests.utils import TestMultitenantAdminMixin
9
13
 
10
- from .helpers import SubnetDivisionAdminTestMixin
14
+ from .helpers import SubnetDivisionAdminTestMixin, SubnetDivisionTestMixin
11
15
 
12
16
  Subnet = load_model("openwisp_ipam", "Subnet")
13
17
  Device = load_model("config", "Device")
18
+ Config = load_model("config", "Config")
14
19
 
15
20
 
16
21
  class TestSubnetAdmin(
@@ -257,3 +262,132 @@ class TestDeviceAdmin(
257
262
  )
258
263
  self.assertEqual(subnet_response.status_code, 200)
259
264
  self.assertContains(subnet_response, self.config.device.name, 1)
265
+
266
+
267
+ class TestTransactionDeviceAdmin(
268
+ SubnetDivisionTestMixin,
269
+ TestVpnX509Mixin,
270
+ TestDeviceAdminMixin,
271
+ TransactionTestCase,
272
+ ):
273
+ ipam_label = "openwisp_ipam"
274
+ config_label = "config"
275
+
276
+ def test_vpn_template_switch_checksum_db(self):
277
+ admin = self._create_admin()
278
+ self.client.force_login(admin)
279
+ org = self._get_org()
280
+ vpn1_subnet = self._get_master_subnet(organization=org, subnet="10.0.0.0/24")
281
+ self._get_vpn_subdivision_rule(
282
+ number_of_ips=1,
283
+ number_of_subnets=1,
284
+ organization=org,
285
+ master_subnet=vpn1_subnet,
286
+ label="VPN1",
287
+ )
288
+ vpn1 = self._create_vpn(name="vpn1", organization=org, subnet=vpn1_subnet)
289
+ vpn2_subnet = self._get_master_subnet(organization=org, subnet="10.0.1.0/24")
290
+ self._get_vpn_subdivision_rule(
291
+ number_of_ips=1,
292
+ number_of_subnets=1,
293
+ organization=org,
294
+ master_subnet=vpn2_subnet,
295
+ label="VPN2",
296
+ )
297
+ vpn2 = self._create_vpn(name="vpn2", organization=org, subnet=vpn2_subnet)
298
+ vpn1_template = self._create_template(
299
+ organization=org,
300
+ name="vpn1-template",
301
+ type="vpn",
302
+ vpn=vpn1,
303
+ default_values={
304
+ "VPN1_subnet1_ip1": "10.0.0.1",
305
+ "VPN1_prefix": "24",
306
+ "ifname": "tun0",
307
+ },
308
+ auto_cert=True,
309
+ config={},
310
+ )
311
+ vpn1_template.config["openvpn"][0]["dev"] = "{{ ifname }}"
312
+ vpn1_template.config.update(
313
+ {
314
+ "network": [
315
+ {
316
+ "config_name": "interface",
317
+ "config_value": "lan",
318
+ "ipaddr": "{{ VPN1_subnet1_ip1 }}",
319
+ "netmask": "255.255.255.240",
320
+ }
321
+ ],
322
+ }
323
+ )
324
+ vpn1_template.full_clean()
325
+ vpn1_template.save()
326
+ vpn2_template = self._create_template(
327
+ organization=org,
328
+ name="vpn2-template",
329
+ type="vpn",
330
+ vpn=vpn2,
331
+ default_values={
332
+ "VPN2_subnet1_ip1": "10.0.1.1",
333
+ "VPN2_prefix": "32",
334
+ "ifname": "tun1",
335
+ },
336
+ auto_cert=True,
337
+ config={},
338
+ )
339
+ vpn2_template.config["openvpn"][0]["dev"] = "{{ ifname }}"
340
+ vpn2_template.config.update(
341
+ {
342
+ "network": [
343
+ {
344
+ "config_name": "interface",
345
+ "config_value": "lan",
346
+ "ipaddr": "{{ VPN2_subnet1_ip1 }}",
347
+ "netmask": "255.255.255.240",
348
+ }
349
+ ],
350
+ }
351
+ )
352
+ vpn2_template.full_clean()
353
+ vpn2_template.save()
354
+ default_template = self._create_template(
355
+ name="default-template",
356
+ default=True,
357
+ )
358
+ path = reverse(f"admin:{self.config_label}_device_add")
359
+ params = self._get_device_params(org=org)
360
+ params.update(
361
+ {"config-0-templates": f"{default_template.pk},{vpn1_template.pk}"}
362
+ )
363
+ response = self.client.post(path, data=params, follow=True)
364
+ self.assertEqual(response.status_code, 200)
365
+ config = Config.objects.get(device__name=params["name"])
366
+ config.refresh_from_db()
367
+ config._invalidate_backend_instance_cache()
368
+ initial_checksum = config.checksum
369
+ self.assertEqual(config.checksum_db, initial_checksum)
370
+ self.assertEqual(config.vpnclient_set.count(), 1)
371
+ self.assertEqual(config.vpnclient_set.first().vpn, vpn1)
372
+
373
+ path = reverse(
374
+ f"admin:{self.config_label}_device_change", args=[config.device_id]
375
+ )
376
+ params.update(
377
+ {
378
+ "config-0-templates": f"{default_template.pk},{vpn2_template.pk}",
379
+ "config-0-id": str(config.pk),
380
+ "config-0-device": str(config.device_id),
381
+ "config-INITIAL_FORMS": 1,
382
+ "_continue": True,
383
+ }
384
+ )
385
+ response = self.client.post(path, data=params, follow=True)
386
+ self.assertEqual(response.status_code, 200)
387
+ config.refresh_from_db()
388
+ config._invalidate_backend_instance_cache()
389
+ self.assertEqual(config.status, "modified")
390
+ self.assertEqual(config.vpnclient_set.count(), 1)
391
+ self.assertEqual(config.vpnclient_set.first().vpn, vpn2)
392
+ self.assertNotEqual(config.checksum, initial_checksum)
393
+ self.assertEqual(config.checksum, config.checksum_db)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openwisp-controller
3
- Version: 1.2.2
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