From 90dec987ba331de24e52b7506301436bfbb20a75 Mon Sep 17 00:00:00 2001 From: Lova ANDRIARIMALALA <43842786+Xpirix@users.noreply.github.com> Date: Mon, 12 Aug 2024 11:44:22 +0300 Subject: [PATCH 1/9] Remove plugins and planet apps --- .flake8 | 1 - qgis-app/{plugins => base}/middleware.py | 0 qgis-app/homepage.py | 31 - qgis-app/plugins/__init__.py | 17 - qgis-app/plugins/admin.py | 48 - qgis-app/plugins/api.py | 210 -- qgis-app/plugins/apps.py | 9 - qgis-app/plugins/celery.py | 28 - qgis-app/plugins/decorators.py | 39 - qgis-app/plugins/docs/future_developments.rst | 7 - qgis-app/plugins/docs/introduction.rst | 109 - qgis-app/plugins/docs/metadata.rst | 2 - qgis-app/plugins/forms.py | 272 --- qgis-app/plugins/management/__init__.py | 0 .../plugins/management/commands/__init__.py | 0 .../management/commands/cleanmediafolder.py | 38 - .../commands/generate_plugins_xml.py | 21 - .../commands/organize_old_package.py | 40 - qgis-app/plugins/migrations/0001_initial.py | 260 --- .../migrations/0002_plugins_feedback.py | 31 - .../migrations/0002_pluginversiondownload.py | 27 - .../0003_plugin_allow_update_name.py | 18 - .../migrations/0004_merge_20231122_0223.py | 14 - .../migrations/0004_merge_20231123_0018.py | 14 - .../migrations/0005_auto_20231214_2317.py | 23 - .../migrations/0005_plugin_maintainer.py | 29 - .../migrations/0005_pluginoutstandingtoken.py | 24 - .../migrations/0006_auto_20231218_0225.py | 28 - .../0006_plugin_display_created_by.py | 18 - .../migrations/0007_auto_20240109_0428.py | 30 - .../migrations/0008_merge_20240206_0448.py | 14 - .../migrations/0009_merge_20240321_0207.py | 14 - .../migrations/0010_merge_20240517_0729.py | 14 - ...r_pluginversiondownload_unique_together.py | 17 - .../0012_pluginversionfeedback_modified_on.py | 18 - qgis-app/plugins/migrations/__init__.py | 0 qgis-app/plugins/models.py | 1058 ---------- qgis-app/plugins/search_indexes.py | 20 - qgis-app/plugins/tasks/__init__.py | 4 - .../plugins/tasks/generate_plugins_xml.py | 91 - .../plugins/tasks/rebuild_search_index.py | 12 - qgis-app/plugins/tasks/update_feedjack.py | 10 - .../plugins/tasks/update_qgis_versions.py | 27 - .../templates/plugins/form_snippet.html | 27 - .../plugins/templates/plugins/pagination.html | 50 - .../templates/plugins/plugin_base.html | 52 - .../plugins/plugin_delete_confirm.html | 9 - .../templates/plugins/plugin_detail.html | 392 ---- .../templates/plugins/plugin_feedback.html | 235 --- .../templates/plugins/plugin_form.html | 108 - .../templates/plugins/plugin_list.html | 183 -- .../templates/plugins/plugin_list_my.html | 7 - .../plugins/plugin_permission_deny.html | 4 - .../plugins/plugin_token_delete_confirm.html | 9 - .../plugins/plugin_token_detail.html | 114 - .../templates/plugins/plugin_token_form.html | 26 - .../plugin_token_invalid_or_expired.html | 4 - .../templates/plugins/plugin_token_list.html | 77 - .../plugins/plugin_token_permission_deny.html | 4 - .../templates/plugins/plugin_upload.html | 45 - .../plugins/templates/plugins/plugins.xml | 30 - .../plugins/plugins_tagcloud_include.html | 10 - .../plugins_tagcloud_modal_include.html | 21 - .../templates/plugins/tagcloud_include.html | 14 - qgis-app/plugins/templates/plugins/user.html | 29 - .../plugins/version_delete_confirm.html | 9 - .../templates/plugins/version_detail.html | 77 - .../templates/plugins/version_form.html | 23 - .../plugins/version_permission_deny.html | 4 - qgis-app/plugins/templatetags/__init__.py | 0 .../plugins/templatetags/local_timezone.py | 22 - qgis-app/plugins/templatetags/plugin_utils.py | 62 - .../plugins/templatetags/plugins_tagcloud.py | 106 - qgis-app/plugins/templatetags/range_filter.py | 27 - .../plugins/templatetags/smart_paginate.py | 257 --- .../1.0-spaced/Hello World/HelloWorld.py | 40 - .../1.0-spaced/Hello World/__init__.py | 33 - .../1.0-spaced/Hello World/icon.png | Bin 1304 -> 0 bytes .../HelloWorld/1.0/HelloWorld/HelloWorld.py | 40 - .../HelloWorld/1.0/HelloWorld/__init__.py | 33 - .../tests/HelloWorld/1.0/HelloWorld/icon.png | Bin 1304 -> 0 bytes .../HelloWorld/1.1/HelloWorld/HelloWorld.py | 40 - .../HelloWorld/1.1/HelloWorld/__init__.py | 33 - .../tests/HelloWorld/1.1/HelloWorld/icon.png | Bin 1304 -> 0 bytes .../1.2-md-full/HelloWorld/HelloWorld.py | 40 - .../1.2-md-full/HelloWorld/__init__.py | 49 - .../1.2-md-full/HelloWorld/icon.png | Bin 1304 -> 0 bytes .../1.2-md-full/HelloWorld/metadata.txt | 29 - .../HelloWorld/HelloWorld.py | 40 - .../HelloWorld/__init__.py | 41 - .../1.2-md-txt-incomplete/HelloWorld/icon.png | Bin 1304 -> 0 bytes .../HelloWorld/metadata.txt | 2 - .../1.2-no-icon/HelloWorld/HelloWorld.py | 40 - .../1.2-no-icon/HelloWorld/__init__.py | 26 - .../1.2-no-icon/HelloWorld/icon.png | Bin 1304 -> 0 bytes .../1.2-no-icon/HelloWorld/metadata.txt | 21 - .../HelloWorld/HelloWorld.py | 40 - .../HelloWorld/__init__.py | 33 - .../1.2-qgs-1.6-md-init/HelloWorld/icon.png | Bin 1304 -> 0 bytes .../HelloWorld/metadata.txt | 21 - .../HelloWorld/HelloWorld.py | 40 - .../HelloWorld/__init__.py | 4 - .../1.2-qgs-1.6-no-init/HelloWorld/icon.png | Bin 1304 -> 0 bytes .../HelloWorld/metadata.txt | 21 - .../1.2-wierdname/HelloWorld/HelloWorld.py | 40 - .../1.2-wierdname/HelloWorld/__init__.py | 33 - .../1.2-wierdname/HelloWorld/icon.png | Bin 1304 -> 0 bytes .../1.2-wierdname/HelloWorld/metadata.txt | 24 - .../HelloWorld/1.2/HelloWorld/HelloWorld.py | 40 - .../HelloWorld/1.2/HelloWorld/__init__.py | 33 - .../tests/HelloWorld/1.2/HelloWorld/icon.png | Bin 1304 -> 0 bytes .../HelloWorld/1.2/HelloWorld/metadata.txt | 23 - .../HelloWorld/HelloWorld.py | 40 - .../HelloWorld/__init__.py | 4 - .../1.3-full-md-no-init/HelloWorld/icon.png | Bin 325 -> 0 bytes .../HelloWorld/metadata.txt | 26 - .../HelloWorld/HelloWorld.py | 40 - .../HelloWorld/__init__.py | 10 - .../HelloWorld/icon.png | Bin 325 -> 0 bytes .../HelloWorld/metadata.txt | 31 - .../HelloWorld/HelloWorld.py | 40 - .../HelloWorld/__init__.py | 10 - .../HelloWorld/icon.png | Bin 325 -> 0 bytes .../HelloWorld/metadata.txt | 31 - .../HelloWorld/HelloWorld.py | 40 - .../HelloWorld/__init__.py | 10 - .../HelloWorld/icons/icon.png | Bin 1319 -> 0 bytes .../HelloWorld/metadata.txt | 31 - .../HelloWorld/HelloWorld.py | 40 - .../HelloWorld/__init__.py | 4 - .../HelloWorld/icons/icon.png | Bin 1319 -> 0 bytes .../HelloWorld/metadata.txt | 31 - .../HelloWorld/HelloWorld.py | 40 - .../HelloWorld/__init__.py | 10 - .../HelloWorld/icons/icon.png | Bin 1319 -> 0 bytes .../HelloWorld/metadata.txt | 32 - .../HelloWorld/HelloWorld.py | 40 - .../HelloWorld/__init__.py | 10 - .../HelloWorld/icons/icon.png | Bin 1319 -> 0 bytes .../HelloWorld/metadata.txt | 31 - .../HelloWorld/HelloWorld.py | 40 - .../HelloWorld/__init__.py | 10 - .../HelloWorld/icons/icon.png | Bin 1319 -> 0 bytes .../HelloWorld/metadata.txt | 32 - .../HelloWorld/HelloWorld.py | 40 - .../HelloWorld/__init__.py | 10 - .../HelloWorld/icons/icon.png | Bin 1319 -> 0 bytes .../HelloWorld/metadata.txt | 32 - .../HelloWorld/HelloWorld.py | 40 - .../HelloWorld/__init__.py | 10 - .../HelloWorld/icons/icon.png | Bin 1319 -> 0 bytes .../HelloWorld/metadata.txt | 34 - qgis-app/plugins/tests/HelloWorld/Makefile | 3 - qgis-app/plugins/tests/HelloWorld/README | 13 - qgis-app/plugins/tests/__init__.py | 5 - .../plugins/tests/test_change_maintainer.py | 101 - qgis-app/plugins/tests/test_download.py | 85 - .../plugins/tests/test_filter_template.py | 56 - qgis-app/plugins/tests/test_models.py | 129 -- qgis-app/plugins/tests/test_plugin_list.py | 57 - qgis-app/plugins/tests/test_plugin_update.py | 169 -- qgis-app/plugins/tests/test_plugin_upload.py | 87 - .../tests/test_plugin_version_feedback.py | 502 ----- qgis-app/plugins/tests/test_rename_plugin.py | 111 - qgis-app/plugins/tests/test_simple_tag.py | 67 - qgis-app/plugins/tests/test_task.py | 94 - qgis-app/plugins/tests/test_token_auth.py | 163 -- qgis-app/plugins/tests/test_utils.py | 37 - qgis-app/plugins/tests/test_validator.py | 283 --- qgis-app/plugins/tests/test_view.py | 27 - .../tests/testfiles/change_metadata.zip_ | Bin 45562 -> 0 bytes .../tests/testfiles/invalid_metadata_link.zip | Bin 39889 -> 0 bytes .../tests/testfiles/invalid_url_scheme.zip | Bin 39918 -> 0 bytes .../tests/testfiles/multi_parents_plugin.zip_ | Bin 45713 -> 0 bytes .../testfiles/plugin_without_license.zip_ | Bin 39590 -> 0 bytes .../tests/testfiles/valid_metadata_link.zip | Bin 45877 -> 0 bytes .../plugins/tests/testfiles/valid_plugin.zip_ | Bin 45559 -> 0 bytes .../tests/testfiles/valid_plugin_0.0.2.zip_ | Bin 45559 -> 0 bytes .../tests/testfiles/valid_plugin_0.0.3.zip_ | Bin 45559 -> 0 bytes .../plugins/tests/testfiles/web_not_exist.zip | Bin 39906 -> 0 bytes qgis-app/plugins/tests/tests.py | 26 - qgis-app/plugins/tests/upload_test.py | 6 - qgis-app/plugins/tests/versionfield.py | 90 - qgis-app/plugins/tests/ws_test.py | 37 - qgis-app/plugins/urls.py | 372 ---- qgis-app/plugins/utils.py | 58 - qgis-app/plugins/validator.py | 387 ---- qgis-app/plugins/views.py | 1864 ----------------- qgis-app/settings.py | 3 +- qgis-app/settings_docker.py | 28 +- .../templates/admin/auth/change_list.html | 14 - qgis-app/urls.py | 28 - qgis-app/userexport/__init__.py | 0 qgis-app/userexport/admin.py | 3 - qgis-app/userexport/models.py | 3 - qgis-app/userexport/tests.py | 3 - qgis-app/userexport/urls.py | 14 - qgis-app/userexport/views.py | 83 - 198 files changed, 2 insertions(+), 11388 deletions(-) rename qgis-app/{plugins => base}/middleware.py (100%) delete mode 100644 qgis-app/homepage.py delete mode 100644 qgis-app/plugins/__init__.py delete mode 100644 qgis-app/plugins/admin.py delete mode 100644 qgis-app/plugins/api.py delete mode 100644 qgis-app/plugins/apps.py delete mode 100644 qgis-app/plugins/celery.py delete mode 100644 qgis-app/plugins/decorators.py delete mode 100644 qgis-app/plugins/docs/future_developments.rst delete mode 100644 qgis-app/plugins/docs/introduction.rst delete mode 100755 qgis-app/plugins/docs/metadata.rst delete mode 100644 qgis-app/plugins/forms.py delete mode 100644 qgis-app/plugins/management/__init__.py delete mode 100644 qgis-app/plugins/management/commands/__init__.py delete mode 100644 qgis-app/plugins/management/commands/cleanmediafolder.py delete mode 100644 qgis-app/plugins/management/commands/generate_plugins_xml.py delete mode 100644 qgis-app/plugins/management/commands/organize_old_package.py delete mode 100644 qgis-app/plugins/migrations/0001_initial.py delete mode 100644 qgis-app/plugins/migrations/0002_plugins_feedback.py delete mode 100644 qgis-app/plugins/migrations/0002_pluginversiondownload.py delete mode 100644 qgis-app/plugins/migrations/0003_plugin_allow_update_name.py delete mode 100644 qgis-app/plugins/migrations/0004_merge_20231122_0223.py delete mode 100644 qgis-app/plugins/migrations/0004_merge_20231123_0018.py delete mode 100644 qgis-app/plugins/migrations/0005_auto_20231214_2317.py delete mode 100644 qgis-app/plugins/migrations/0005_plugin_maintainer.py delete mode 100644 qgis-app/plugins/migrations/0005_pluginoutstandingtoken.py delete mode 100644 qgis-app/plugins/migrations/0006_auto_20231218_0225.py delete mode 100644 qgis-app/plugins/migrations/0006_plugin_display_created_by.py delete mode 100644 qgis-app/plugins/migrations/0007_auto_20240109_0428.py delete mode 100644 qgis-app/plugins/migrations/0008_merge_20240206_0448.py delete mode 100644 qgis-app/plugins/migrations/0009_merge_20240321_0207.py delete mode 100644 qgis-app/plugins/migrations/0010_merge_20240517_0729.py delete mode 100644 qgis-app/plugins/migrations/0011_alter_pluginversiondownload_unique_together.py delete mode 100644 qgis-app/plugins/migrations/0012_pluginversionfeedback_modified_on.py delete mode 100644 qgis-app/plugins/migrations/__init__.py delete mode 100644 qgis-app/plugins/models.py delete mode 100644 qgis-app/plugins/search_indexes.py delete mode 100644 qgis-app/plugins/tasks/__init__.py delete mode 100644 qgis-app/plugins/tasks/generate_plugins_xml.py delete mode 100644 qgis-app/plugins/tasks/rebuild_search_index.py delete mode 100644 qgis-app/plugins/tasks/update_feedjack.py delete mode 100644 qgis-app/plugins/tasks/update_qgis_versions.py delete mode 100755 qgis-app/plugins/templates/plugins/form_snippet.html delete mode 100644 qgis-app/plugins/templates/plugins/pagination.html delete mode 100644 qgis-app/plugins/templates/plugins/plugin_base.html delete mode 100644 qgis-app/plugins/templates/plugins/plugin_delete_confirm.html delete mode 100644 qgis-app/plugins/templates/plugins/plugin_detail.html delete mode 100644 qgis-app/plugins/templates/plugins/plugin_feedback.html delete mode 100644 qgis-app/plugins/templates/plugins/plugin_form.html delete mode 100644 qgis-app/plugins/templates/plugins/plugin_list.html delete mode 100644 qgis-app/plugins/templates/plugins/plugin_list_my.html delete mode 100644 qgis-app/plugins/templates/plugins/plugin_permission_deny.html delete mode 100644 qgis-app/plugins/templates/plugins/plugin_token_delete_confirm.html delete mode 100644 qgis-app/plugins/templates/plugins/plugin_token_detail.html delete mode 100644 qgis-app/plugins/templates/plugins/plugin_token_form.html delete mode 100644 qgis-app/plugins/templates/plugins/plugin_token_invalid_or_expired.html delete mode 100644 qgis-app/plugins/templates/plugins/plugin_token_list.html delete mode 100644 qgis-app/plugins/templates/plugins/plugin_token_permission_deny.html delete mode 100644 qgis-app/plugins/templates/plugins/plugin_upload.html delete mode 100644 qgis-app/plugins/templates/plugins/plugins.xml delete mode 100644 qgis-app/plugins/templates/plugins/plugins_tagcloud_include.html delete mode 100644 qgis-app/plugins/templates/plugins/plugins_tagcloud_modal_include.html delete mode 100644 qgis-app/plugins/templates/plugins/tagcloud_include.html delete mode 100644 qgis-app/plugins/templates/plugins/user.html delete mode 100644 qgis-app/plugins/templates/plugins/version_delete_confirm.html delete mode 100644 qgis-app/plugins/templates/plugins/version_detail.html delete mode 100644 qgis-app/plugins/templates/plugins/version_form.html delete mode 100644 qgis-app/plugins/templates/plugins/version_permission_deny.html delete mode 100644 qgis-app/plugins/templatetags/__init__.py delete mode 100644 qgis-app/plugins/templatetags/local_timezone.py delete mode 100755 qgis-app/plugins/templatetags/plugin_utils.py delete mode 100644 qgis-app/plugins/templatetags/plugins_tagcloud.py delete mode 100755 qgis-app/plugins/templatetags/range_filter.py delete mode 100644 qgis-app/plugins/templatetags/smart_paginate.py delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.0-spaced/Hello World/HelloWorld.py delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.0-spaced/Hello World/__init__.py delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.0-spaced/Hello World/icon.png delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.0/HelloWorld/HelloWorld.py delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.0/HelloWorld/__init__.py delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.0/HelloWorld/icon.png delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.1/HelloWorld/HelloWorld.py delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.1/HelloWorld/__init__.py delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.1/HelloWorld/icon.png delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.2-md-full/HelloWorld/HelloWorld.py delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.2-md-full/HelloWorld/__init__.py delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.2-md-full/HelloWorld/icon.png delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.2-md-full/HelloWorld/metadata.txt delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.2-md-txt-incomplete/HelloWorld/HelloWorld.py delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.2-md-txt-incomplete/HelloWorld/__init__.py delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.2-md-txt-incomplete/HelloWorld/icon.png delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.2-md-txt-incomplete/HelloWorld/metadata.txt delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.2-no-icon/HelloWorld/HelloWorld.py delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.2-no-icon/HelloWorld/__init__.py delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.2-no-icon/HelloWorld/icon.png delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.2-no-icon/HelloWorld/metadata.txt delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-md-init/HelloWorld/HelloWorld.py delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-md-init/HelloWorld/__init__.py delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-md-init/HelloWorld/icon.png delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-md-init/HelloWorld/metadata.txt delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-no-init/HelloWorld/HelloWorld.py delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-no-init/HelloWorld/__init__.py delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-no-init/HelloWorld/icon.png delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-no-init/HelloWorld/metadata.txt delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.2-wierdname/HelloWorld/HelloWorld.py delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.2-wierdname/HelloWorld/__init__.py delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.2-wierdname/HelloWorld/icon.png delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.2-wierdname/HelloWorld/metadata.txt delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.2/HelloWorld/HelloWorld.py delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.2/HelloWorld/__init__.py delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.2/HelloWorld/icon.png delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.2/HelloWorld/metadata.txt delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.3-full-md-no-init/HelloWorld/HelloWorld.py delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.3-full-md-no-init/HelloWorld/__init__.py delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.3-full-md-no-init/HelloWorld/icon.png delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.3-full-md-no-init/HelloWorld/metadata.txt delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.5-full-md-no-init-experimental/HelloWorld/HelloWorld.py delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.5-full-md-no-init-experimental/HelloWorld/__init__.py delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.5-full-md-no-init-experimental/HelloWorld/icon.png delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.5-full-md-no-init-experimental/HelloWorld/metadata.txt delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.6-full-md-no-init-unicode-error/HelloWorld/HelloWorld.py delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.6-full-md-no-init-unicode-error/HelloWorld/__init__.py delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.6-full-md-no-init-unicode-error/HelloWorld/icon.png delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.6-full-md-no-init-unicode-error/HelloWorld/metadata.txt delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.7-full-md-no-init-subfolder-icon/HelloWorld/HelloWorld.py delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.7-full-md-no-init-subfolder-icon/HelloWorld/__init__.py delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.7-full-md-no-init-subfolder-icon/HelloWorld/icons/icon.png delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.7-full-md-no-init-subfolder-icon/HelloWorld/metadata.txt delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.8-author-slashes-error/HelloWorld/HelloWorld.py delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.8-author-slashes-error/HelloWorld/__init__.py delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.8-author-slashes-error/HelloWorld/icons/icon.png delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.8-author-slashes-error/HelloWorld/metadata.txt delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.9-full-md-max_qgs_version/HelloWorld/HelloWorld.py delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.9-full-md-max_qgs_version/HelloWorld/__init__.py delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.9-full-md-max_qgs_version/HelloWorld/icons/icon.png delete mode 100644 qgis-app/plugins/tests/HelloWorld/1.9-full-md-max_qgs_version/HelloWorld/metadata.txt delete mode 100644 qgis-app/plugins/tests/HelloWorld/2.0-full-md-empty-max_qgs_version/HelloWorld/HelloWorld.py delete mode 100644 qgis-app/plugins/tests/HelloWorld/2.0-full-md-empty-max_qgs_version/HelloWorld/__init__.py delete mode 100644 qgis-app/plugins/tests/HelloWorld/2.0-full-md-empty-max_qgs_version/HelloWorld/icons/icon.png delete mode 100644 qgis-app/plugins/tests/HelloWorld/2.0-full-md-empty-max_qgs_version/HelloWorld/metadata.txt delete mode 100644 qgis-app/plugins/tests/HelloWorld/2.1-full-md-max_qgs_version_2.999/HelloWorld/HelloWorld.py delete mode 100644 qgis-app/plugins/tests/HelloWorld/2.1-full-md-max_qgs_version_2.999/HelloWorld/__init__.py delete mode 100644 qgis-app/plugins/tests/HelloWorld/2.1-full-md-max_qgs_version_2.999/HelloWorld/icons/icon.png delete mode 100644 qgis-app/plugins/tests/HelloWorld/2.1-full-md-max_qgs_version_2.999/HelloWorld/metadata.txt delete mode 100644 qgis-app/plugins/tests/HelloWorld/2.2-full-md-empty-max_qgs_category_web/HelloWorld/HelloWorld.py delete mode 100644 qgis-app/plugins/tests/HelloWorld/2.2-full-md-empty-max_qgs_category_web/HelloWorld/__init__.py delete mode 100644 qgis-app/plugins/tests/HelloWorld/2.2-full-md-empty-max_qgs_category_web/HelloWorld/icons/icon.png delete mode 100644 qgis-app/plugins/tests/HelloWorld/2.2-full-md-empty-max_qgs_category_web/HelloWorld/metadata.txt delete mode 100644 qgis-app/plugins/tests/HelloWorld/2.3-full-changed-repository/HelloWorld/HelloWorld.py delete mode 100644 qgis-app/plugins/tests/HelloWorld/2.3-full-changed-repository/HelloWorld/__init__.py delete mode 100644 qgis-app/plugins/tests/HelloWorld/2.3-full-changed-repository/HelloWorld/icons/icon.png delete mode 100644 qgis-app/plugins/tests/HelloWorld/2.3-full-changed-repository/HelloWorld/metadata.txt delete mode 100644 qgis-app/plugins/tests/HelloWorld/Makefile delete mode 100644 qgis-app/plugins/tests/HelloWorld/README delete mode 100644 qgis-app/plugins/tests/__init__.py delete mode 100644 qgis-app/plugins/tests/test_change_maintainer.py delete mode 100644 qgis-app/plugins/tests/test_download.py delete mode 100644 qgis-app/plugins/tests/test_filter_template.py delete mode 100644 qgis-app/plugins/tests/test_models.py delete mode 100644 qgis-app/plugins/tests/test_plugin_list.py delete mode 100644 qgis-app/plugins/tests/test_plugin_update.py delete mode 100644 qgis-app/plugins/tests/test_plugin_upload.py delete mode 100644 qgis-app/plugins/tests/test_plugin_version_feedback.py delete mode 100644 qgis-app/plugins/tests/test_rename_plugin.py delete mode 100644 qgis-app/plugins/tests/test_simple_tag.py delete mode 100644 qgis-app/plugins/tests/test_task.py delete mode 100644 qgis-app/plugins/tests/test_token_auth.py delete mode 100644 qgis-app/plugins/tests/test_utils.py delete mode 100644 qgis-app/plugins/tests/test_validator.py delete mode 100644 qgis-app/plugins/tests/test_view.py delete mode 100644 qgis-app/plugins/tests/testfiles/change_metadata.zip_ delete mode 100644 qgis-app/plugins/tests/testfiles/invalid_metadata_link.zip delete mode 100644 qgis-app/plugins/tests/testfiles/invalid_url_scheme.zip delete mode 100644 qgis-app/plugins/tests/testfiles/multi_parents_plugin.zip_ delete mode 100644 qgis-app/plugins/tests/testfiles/plugin_without_license.zip_ delete mode 100644 qgis-app/plugins/tests/testfiles/valid_metadata_link.zip delete mode 100644 qgis-app/plugins/tests/testfiles/valid_plugin.zip_ delete mode 100644 qgis-app/plugins/tests/testfiles/valid_plugin_0.0.2.zip_ delete mode 100644 qgis-app/plugins/tests/testfiles/valid_plugin_0.0.3.zip_ delete mode 100644 qgis-app/plugins/tests/testfiles/web_not_exist.zip delete mode 100644 qgis-app/plugins/tests/tests.py delete mode 100644 qgis-app/plugins/tests/upload_test.py delete mode 100755 qgis-app/plugins/tests/versionfield.py delete mode 100644 qgis-app/plugins/tests/ws_test.py delete mode 100644 qgis-app/plugins/urls.py delete mode 100644 qgis-app/plugins/utils.py delete mode 100644 qgis-app/plugins/validator.py delete mode 100644 qgis-app/plugins/views.py delete mode 100644 qgis-app/templates/admin/auth/change_list.html delete mode 100644 qgis-app/userexport/__init__.py delete mode 100644 qgis-app/userexport/admin.py delete mode 100644 qgis-app/userexport/models.py delete mode 100644 qgis-app/userexport/tests.py delete mode 100755 qgis-app/userexport/urls.py delete mode 100644 qgis-app/userexport/views.py diff --git a/.flake8 b/.flake8 index 01572a25..e8cc9419 100644 --- a/.flake8 +++ b/.flake8 @@ -3,7 +3,6 @@ exclude = */docs/*,*/.tox/*,*/.venv/*,*/.pycharm_helpers/*,*/migrations/*,docs/* */manage.py,*/wsgi.py,*/django.wsgi, # we don't check this for now, until it's been fixed. otherwise it will throw lot of errors qgis-app/plugins/*, - qgis-app/userexport/*, qgis-app/lib/templatetags/*, vagrant_assets/*, qgis-app/users/*, diff --git a/qgis-app/plugins/middleware.py b/qgis-app/base/middleware.py similarity index 100% rename from qgis-app/plugins/middleware.py rename to qgis-app/base/middleware.py diff --git a/qgis-app/homepage.py b/qgis-app/homepage.py deleted file mode 100644 index a7e9e51f..00000000 --- a/qgis-app/homepage.py +++ /dev/null @@ -1,31 +0,0 @@ -from django.contrib.flatpages.models import FlatPage -from django.shortcuts import render -from django.template import RequestContext -from django.utils.translation import gettext_lazy as _ -from plugins.models import Plugin - -# from feedjack.models import Post - - -def homepage(request): - """ - Renders the home page - """ - latest = Plugin.latest_objects.all()[:10] - featured = Plugin.featured_objects.all()[:10] - popular = Plugin.popular_objects.all()[:10] - try: - content = FlatPage.objects.get(url="/").content - except FlatPage.DoesNotExist: - content = _('To add content here, create a FlatPage with url="/"') - - return render( - request, - "flatpages/homepage.html", - { - "featured": featured, - "latest": latest, - "popular": popular, - "content": content, - }, - ) diff --git a/qgis-app/plugins/__init__.py b/qgis-app/plugins/__init__.py deleted file mode 100644 index cf9dc02f..00000000 --- a/qgis-app/plugins/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -from __future__ import absolute_import, unicode_literals - -# This will make sure the app is always imported when -# Django starts so that shared_task will use this app. -from .celery import app as celery_app - -default_app_config = "plugins.apps.PluginsConfig" -__all__ = [ - "celery_app", - "models", - "views", - "admin", - "urls", - "api", - "forms", - "validator", -] diff --git a/qgis-app/plugins/admin.py b/qgis-app/plugins/admin.py deleted file mode 100644 index 81b5085d..00000000 --- a/qgis-app/plugins/admin.py +++ /dev/null @@ -1,48 +0,0 @@ -from django.contrib import admin -from plugins.models import Plugin, PluginVersion, PluginVersionDownload # , PluginCrashReport - - -class PluginAdmin(admin.ModelAdmin): - list_filter = ("featured",) - list_display = ( - "name", - "featured", - "created_by", - "created_on", - "downloads", - "stable", - "experimental", - ) - search_fields = ("name",) - - -class PluginVersionAdmin(admin.ModelAdmin): - list_filter = ("experimental", "approved", "plugin") - list_display = ( - "plugin", - "approved", - "version", - "experimental", - "created_on", - "downloads", - ) - - -class PluginVersionDownloadAdmin(admin.ModelAdmin): - list_display = ( - "plugin_version", - "download_date", - "download_count" - ) - raw_id_fields = ( - "plugin_version", - ) - -# class PluginCrashReportAdmin(admin.ModelAdmin): -# pass - - -admin.site.register(Plugin, PluginAdmin) -admin.site.register(PluginVersion, PluginVersionAdmin) -admin.site.register(PluginVersionDownload, PluginVersionDownloadAdmin) -# admin.site.register(PluginCrashReport, PluginCrashReportAdmin) diff --git a/qgis-app/plugins/api.py b/qgis-app/plugins/api.py deleted file mode 100644 index 72615d68..00000000 --- a/qgis-app/plugins/api.py +++ /dev/null @@ -1,210 +0,0 @@ -""" -XML-RPC webservices for the plugin web application -""" - -from base64 import b64decode -from io import BytesIO -from xmlrpc.server import Fault - -from django.contrib.contenttypes.models import ContentType -from django.core.exceptions import PermissionDenied, ValidationError -from django.core.files.uploadedfile import InMemoryUploadedFile - -# Transaction -from django.db import IntegrityError, connection -from django.utils.translation import gettext_lazy as _ -from plugins.models import * -from plugins.validator import validator -from plugins.views import plugin_notify -from rpc4django import rpcmethod -from taggit.models import Tag - - -@rpcmethod(name="plugin.maintainers", signature=["string"], login_required=True) -def plugin_maintaners(**kwargs): - """ - Returns a CSV list of plugin maintainers - """ - request = kwargs.get("request") - if not request.user.is_superuser: - raise PermissionDenied() - return "\n".join( - [ - u.email - for u in User.objects.filter( - plugins_created_by__isnull=False, email__isnull=False - ) - .exclude(email="") - .order_by("email") - .distinct() - ] - ) - - -@rpcmethod(name="plugin.upload", signature=["array", "base64"], login_required=True) -def plugin_upload(package, **kwargs): - """ - Creates a new plugin or updates an existing one - Returns an array containing the ID (primary key) of the plugin and the ID of the version. - """ - try: - # JSON-RPC cannot deserialize base64 strings to bytes, do it here instead - if isinstance(package, str): - package = b64decode(package.encode('utf-8')) - - request = kwargs.get("request") - package = BytesIO(package) - package.len = package.getbuffer().nbytes - try: - cleaned_data = dict(validator(package)) - except ValidationError as e: - msg = _( - "File upload must be a valid QGIS Python plugin compressed archive." - ) - raise Fault(1, "%s %s" % (msg, ",".join(e.messages))) - - plugin_data = { - "name": cleaned_data["name"], - "package_name": cleaned_data["package_name"], - "description": cleaned_data["description"], - "created_by": request.user, - "icon": cleaned_data["icon_file"], - "author": cleaned_data["author"], - "email": cleaned_data["email"], - "about": cleaned_data["about"], - } - - # Gets existing plugin - try: - plugin = Plugin.objects.get(package_name=plugin_data["package_name"]) - # Apply new values - plugin.name = plugin_data["name"] - plugin.description = plugin_data["description"] - plugin.icon = plugin_data["icon"] - is_new = False - except Plugin.DoesNotExist: - plugin = Plugin(**plugin_data) - is_new = True - - # Optional Metadata: - if cleaned_data.get("homepage"): - plugin.homepage = cleaned_data.get("homepage") - if cleaned_data.get("tracker"): - plugin.tracker = cleaned_data.get("tracker") - if cleaned_data.get("repository"): - plugin.repository = cleaned_data.get("repository") - if cleaned_data.get("deprecated"): - plugin.deprecated = cleaned_data.get("deprecated") - - plugin.save() - - if is_new: - plugin_notify(plugin) - - # Takes care of tags - if cleaned_data.get("tags"): - plugin.tags.set( - [t.strip().lower() for t in cleaned_data.get("tags").split(",")] - ) - - version_data = { - "plugin": plugin, - "min_qg_version": cleaned_data["qgisMinimumVersion"], - "version": cleaned_data["version"], - "created_by": request.user, - "package": InMemoryUploadedFile( - package, - "package", - "%s.zip" % plugin.package_name, - "application/zip", - package.len, - "UTF-8", - ), - "approved": request.user.has_perm("plugins.can_approve") or plugin.approved, - } - - # Optional version metadata - if cleaned_data.get("experimental"): - version_data["experimental"] = cleaned_data.get("experimental") - if cleaned_data.get("changelog"): - version_data["changelog"] = cleaned_data.get("changelog") - if cleaned_data.get("qgisMaximumVersion"): - version_data["max_qg_version"] = cleaned_data.get("qgisMaximumVersion") - - new_version = PluginVersion(**version_data) - new_version.clean() - new_version.save() - except IntegrityError as e: - # Avoids error: current transaction is aborted, commands ignored until - # end of transaction block - connection.close() - raise Fault(1, e.message) - except ValidationError as e: - raise Fault(1, e.message) - except Exception as e: - raise Fault(1, "%s" % e) - - return (plugin.pk, new_version.pk) - - -@rpcmethod(name="plugin.tags", signature=["array"], login_required=False) -def plugin_tags(**kwargs): - """ - Returns a list of current tags, in alphabetical order - """ - return [t.name for t in Tag.objects.all().order_by("name")] - - -@rpcmethod( - name="plugin.vote", signature=["array", "integer", "integer"], login_required=False -) -def plugin_vote(plugin_id, vote, **kwargs): - """ - Vote a plugin, valid values are 1-5 - """ - try: - request = kwargs.get("request") - except: - msg = _("Invalid request.") - raise ValidationError(msg) - try: - plugin = Plugin.objects.get(pk=plugin_id) - except Plugin.DoesNotExist: - msg = _("Plugin with id %s does not exists.") % plugin_id - raise ValidationError(msg) - if not int(vote) in range(1, 6): - msg = _("%s is not a valid vote (1-5).") % vote - raise ValidationError(msg) - cookies = request.COOKIES - if request.user.is_anonymous: - # Get the cookie - cookie_name = "vote-%s.%s.%s" % ( - ContentType.objects.get(app_label="plugins", model="plugin").pk, - plugin_id, - plugin.rating.field.key[:6], - ) - if not request.COOKIES.get(cookie_name, False): - # Get the IP - ip_address = request.META["REMOTE_ADDR"] - # Check if a recent vote exists - rating = ( - plugin.rating.get_ratings() - .filter( - cookie__isnull=False, - ip_address=ip_address, - date_changed__gte=datetime.datetime.now() - - datetime.timedelta(days=10), - ) - .order_by("-date_changed") - ) - # Change vote if exists - if len(rating): - cookies = {cookie_name: rating[0].cookie} - return [ - plugin.rating.add( - score=int(vote), - user=request.user, - ip_address=request.META["REMOTE_ADDR"], - cookies=cookies, - ) - ] diff --git a/qgis-app/plugins/apps.py b/qgis-app/plugins/apps.py deleted file mode 100644 index f11ac0de..00000000 --- a/qgis-app/plugins/apps.py +++ /dev/null @@ -1,9 +0,0 @@ -from django.apps import AppConfig - - -class PluginsConfig(AppConfig): - name = "plugins" - verbose_name = "QGIS Plugins" - - def ready(self): - from . import api diff --git a/qgis-app/plugins/celery.py b/qgis-app/plugins/celery.py deleted file mode 100644 index cdc9b450..00000000 --- a/qgis-app/plugins/celery.py +++ /dev/null @@ -1,28 +0,0 @@ -from __future__ import absolute_import - -import os - -from celery import Celery -import logging - -logger = logging.getLogger('plugins') - - -# set the default Django settings module for the 'celery' program. -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings_docker") - -app = Celery("plugins") - -# Using a string here means the worker doesn't have to serialize -# the configuration object to child processes. -# - namespace='CELERY' means all celery-related configuration keys -# should have a `CELERY_` prefix. -app.config_from_object("django.conf:settings", namespace="CELERY") - -# Load task modules from all registered Django app configs. -app.autodiscover_tasks() - - -@app.task(bind=True) -def debug_task(self): - print("Request: {0!r}".format(self.request)) diff --git a/qgis-app/plugins/decorators.py b/qgis-app/plugins/decorators.py deleted file mode 100644 index 69509b70..00000000 --- a/qgis-app/plugins/decorators.py +++ /dev/null @@ -1,39 +0,0 @@ -from functools import wraps -from django.http import HttpResponseForbidden -from rest_framework_simplejwt.authentication import JWTAuthentication -from plugins.models import Plugin, PluginOutstandingToken -from rest_framework_simplejwt.exceptions import InvalidToken, TokenError -from rest_framework_simplejwt.token_blacklist.models import BlacklistedToken, OutstandingToken -import datetime - -def has_valid_token(function): - @wraps(function) - def wrap(request, *args, **kwargs): - auth_token = request.META.get("HTTP_AUTHORIZATION") - package_name = kwargs.get('package_name') - if not str(auth_token).startswith('Bearer'): - raise InvalidToken("Invalid token") - - # Validate JWT token - authentication = JWTAuthentication() - try: - validated_token = authentication.get_validated_token(auth_token[7:]) - plugin_id = validated_token.payload.get('plugin_id') - jti = validated_token.payload.get('refresh_jti') - token_id = OutstandingToken.objects.get(jti=jti).pk - is_blacklisted = BlacklistedToken.objects.filter(token_id=token_id).exists() - if not plugin_id or is_blacklisted: - raise InvalidToken("Invalid token") - - plugin = Plugin.objects.get(pk=plugin_id) - if not plugin or plugin.package_name != package_name: - raise InvalidToken("Invalid token") - plugin_token = PluginOutstandingToken.objects.get(token__pk=token_id, plugin=plugin) - plugin_token.last_used_on = datetime.datetime.now() - plugin_token.save() - request.plugin_token = plugin_token - return function(request, *args, **kwargs) - except (InvalidToken, TokenError) as e: - return HttpResponseForbidden(str(e)) - - return wrap diff --git a/qgis-app/plugins/docs/future_developments.rst b/qgis-app/plugins/docs/future_developments.rst deleted file mode 100644 index 1123ebd8..00000000 --- a/qgis-app/plugins/docs/future_developments.rst +++ /dev/null @@ -1,7 +0,0 @@ -Future developments -=================== - -* add live search support to plugin tags -* add full text search to all apps (django-haystack is already in place) -* better error messages with links to conflicting plugins -* add &locale= to plugins.xml and support localization of plugins web interface diff --git a/qgis-app/plugins/docs/introduction.rst b/qgis-app/plugins/docs/introduction.rst deleted file mode 100644 index 8f041562..00000000 --- a/qgis-app/plugins/docs/introduction.rst +++ /dev/null @@ -1,109 +0,0 @@ -======================== -QGIS Plugins application -======================== - -Author: Alessandro Pasotti (www.itopen.it) - -The Plugin model -================ - -The plugin model represents a QGIS plugin and holds general informations such as title and description and icon. - -The plugin can have zero or more *owners* (also named 'collaborators'), *owners* have the same permissions of the original plugin creator. - -Permissions ------------ - -These rules have been implemented: - -* every registered user can add a new plugin -* *staff* users can approve or disapprove all plugin versions -* users which have the special permission `plugins.can_approve` get the versions they upload automatically approved -* users which have the special permission `plugins.can_approve` can approve versions uploaded by others as long as they are in the list of the plugin *owners* -* a particular plugin can be deleted and edited only by *staff* users and plugin *owners* -* if a user without `plugins.can_approve` permission uploads a new version, the plugin version is automatically unapproved. - - -Trust management ----------------- - -Staff members can grant *trust* to selected plugin creators setting `plugins.can_approve` permission through the front-end application. - -The plugin details view offers direct links to grant trust to the plugin creator or the plugin *owners*. - - -The PluginVersion model -======================= - -Each plugin can have several versions, a version specify the minimum QGIS version needed in order to run that particular plugin version and other informations such as if the version belongs to the "stable" o to the "experimental" branch. - -Validation ----------- - -The validation takes place in the PluginVersions forms, at loading time, the compressed file is checked for: - -* file size <= `PLUGIN_MAX_UPLOAD_SIZE` -* zip contains `__init__.py` in first level dir -* `__init__.py` must contain valid metadata - - -* `version` must be unique whithin a plugin -* there must be one and only *last* versions in each plugin's branch - -At the time of plugin creation, the name of the folder inside the compressed file is stored in the variable `package_name`, this value must be unique and cannot be changed. `package_name` is also used to build SEF URLs, for example the plugin's page for the plugin *Hello World Plugin* with `package_name` *HelloWorld* is ``_ - -The `package_name` (and hence the first level folder inside the compressed zip file) cannot contain only ASCI letters (A-Z and a-z), digits and the characters underscore (_) and minus (-) and cannot start with a -digit. - -Example from the `HelloWorld` plugin compressed zip file:: - - Archive: plugins/tests/HelloWorld/HelloWorld_1.2.zip - Length Date Time Name - --------- ---------- ----- ---- - 0 2011-11-13 15:05 HelloWorld/ - 1304 2011-11-13 12:40 HelloWorld/icon.png - 374 2011-11-13 15:05 HelloWorld/metadata.txt - 1094 2011-11-13 12:40 HelloWorld/HelloWorld.py - 396 2011-11-13 12:40 HelloWorld/__init__.py - --------- ------- - 3168 5 files - -Metadata -======== - -You can find detailed informations about metadata in the -`PyQGIS developer cookbook `_ - - -Configuration -============= - -All values can be overridden in `settings.py` - -========================== ============= ======================= -Parameter Default Notes -========================== ============= ======================= -PLUGINS_STORAGE_PATH packages -PLUGIN_MAX_UPLOAD_SIZE 1048576 in bytes -PLUGINS_FRESH_DAYS 30 days -MAIL_FROM_ADDRESS - used in email notifications -PLUGIN_REQUIRED_METADATA [#f1]_ used in validator -PLUGIN_OPTIONAL_METADATA [#f2]_ used in validator -========================== ============= ======================= - - -Plugins XML -=========== - -Plugins XML is available at `http://plugins.qgis.org/plugins/plugins.xml` - -accepted parameters: - * qgis: qgis version - * stable_only: 0/1, default to 0 - * package_name: package name (to get all versions for the given plugin) - - -.. rubric:: Footnotes - -.. [#f1] 'name', 'description', 'version', 'qgisMinimumVersion' -.. [#f2] Supported by metadata.txt only: 'homepage', 'changelog', 'tracker', 'repository', 'tags' diff --git a/qgis-app/plugins/docs/metadata.rst b/qgis-app/plugins/docs/metadata.rst deleted file mode 100755 index 623eba09..00000000 --- a/qgis-app/plugins/docs/metadata.rst +++ /dev/null @@ -1,2 +0,0 @@ -You can find detailed informations about metadata in the -`PyQGIS developer cookbook `_ diff --git a/qgis-app/plugins/forms.py b/qgis-app/plugins/forms.py deleted file mode 100644 index 9f98f03f..00000000 --- a/qgis-app/plugins/forms.py +++ /dev/null @@ -1,272 +0,0 @@ -# i18n -import re - -from django import forms -from django.contrib.auth.models import User -from django.forms import CharField, ModelForm, ValidationError -from django.utils.safestring import mark_safe -from django.utils.translation import gettext_lazy as _ -from plugins.models import Plugin, PluginOutstandingToken, PluginVersion, PluginVersionFeedback -from plugins.validator import validator -from taggit.forms import * - - -def _clean_tags(tags): - """Return a stripped and cleaned tag list, empty tags are deleted""" - if tags: - _tags = [] - for t in tags.split(","): - if t.strip(): - _tags.append(t.strip()) - return ",".join(_tags) - return None - - -class PluginForm(ModelForm): - """ - Form for plugin editing - """ - - required_css_class = "required" - tags = TagField(required=False) - - class Meta: - model = Plugin - fields = ( - "description", - "about", - "author", - "email", - "icon", - "deprecated", - "homepage", - "tracker", - "repository", - "owners", - "maintainer", - "display_created_by", - "tags", - "server", - ) - - def __init__(self, *args, **kwargs): - super(PluginForm, self).__init__(*args, **kwargs) - self.fields['owners'].label = "Collaborators" - - choices = ( - (self.instance.created_by.pk, self.instance.created_by.username + " (Plugin creator)"), - ) - for owner in self.instance.owners.exclude(pk=self.instance.created_by.pk): - choices += ((owner.pk, owner.username + " (Collaborator)"),) - - self.fields['maintainer'].choices = choices - self.fields['maintainer'].label = "Maintainer" - - def clean(self): - """ - Check author - """ - if self.cleaned_data.get("author") and not re.match( - r"^[^/]+$", self.cleaned_data.get("author") - ): - raise ValidationError(_("Author name cannot contain slashes.")) - return super(PluginForm, self).clean() - - -class PluginVersionForm(ModelForm): - """ - Form for version upload on existing plugins - """ - - required_css_class = "required" - changelog = forms.fields.CharField( - label=_("Changelog"), - required=False, - help_text=_( - "Insert here a short description of the changes that have been made in this version. This field is not mandatory and it is automatically filled from the metadata.txt file." - ), - widget=forms.Textarea, - ) - - def __init__(self, *args, **kwargs): - kwargs.pop("is_trusted") - super(PluginVersionForm, self).__init__(*args, **kwargs) - # FIXME: check why this is not working correctly anymore - # now "approved" is removed from the form (see Meta) - # instance = getattr(self, 'instance', None) - # if instance and not is_trusted: - # self.fields['approved'].initial = False - # self.fields['approved'].widget.attrs = {'disabled':'disabled'} - # instance.approved = False - - class Meta: - model = PluginVersion - exclude = ( - "created_by", - "plugin", - "approved", - "version", - "min_qg_version", - "max_qg_version", - ) - fields = ("package", "experimental", "changelog") - - def clean(self): - """ - Only read package if uploaded - """ - # Override package - changelog = self.cleaned_data.get("changelog") - - if self.files: - package = self.cleaned_data.get("package") - try: - cleaned_data = validator(package) - if ( - "experimental" in dict(cleaned_data) - and "experimental" in self.cleaned_data - and dict(cleaned_data)["experimental"] - != self.cleaned_data["experimental"] - ): - msg = _( - "The 'experimental' flag in the form does not match the 'experimental' flag in the plugins package metadata.
" - ) - raise ValidationError(mark_safe("%s" % msg)) - self.cleaned_data.update(cleaned_data) - except ValidationError as e: - msg = _( - "There were errors reading plugin package (please check also your plugin's metadata).
" - ) - raise ValidationError( - mark_safe("%s %s" % (msg, "
".join(e.messages))) - ) - # Populate instance - self.instance.min_qg_version = self.cleaned_data.get("qgisMinimumVersion") - self.instance.max_qg_version = self.cleaned_data.get("qgisMaximumVersion") - self.instance.version = PluginVersion.clean_version( - self.cleaned_data.get("version") - ) - self.instance.server = self.cleaned_data.get("server") - - # Check plugin folder name - if ( - self.cleaned_data.get("package_name") - and self.cleaned_data.get("package_name") - != self.instance.plugin.package_name - ): - raise ValidationError( - _( - "Plugin folder name mismatch: the plugin main folder name in the compressed file (%s) is different from the original plugin package name (%s)." - ) - % ( - self.cleaned_data.get("package_name"), - self.instance.plugin.package_name, - ) - ) - # Also set changelog from metadata - if changelog: - self.cleaned_data["changelog"] = changelog - # Clean tags - self.cleaned_data["tags"] = _clean_tags(self.cleaned_data.get("tags", None)) - self.instance.changelog = self.cleaned_data.get("changelog") - return super(PluginVersionForm, self).clean() - - -class PackageUploadForm(forms.Form): - """ - Single step upload for new plugins - """ - - experimental = forms.BooleanField( - required=False, - label=_("Experimental"), - help_text=_( - "Please check this box if the plugin is experimental. Please note that this field might be overridden by metadata (if present)." - ), - ) - package = forms.FileField( - label=_("Plugin package"), - help_text=_("Please select the zipped file of the plugin."), - ) - - def clean_package(self): - """ - Populates cleaned_data with metadata from the zip package - """ - package = self.cleaned_data.get("package") - try: - self.cleaned_data.update(validator(package)) - except ValidationError as e: - msg = _( - "There were errors reading plugin package (please check also your plugin's metadata)." - ) - raise ValidationError(mark_safe("%s %s" % (msg, ",".join(e.messages)))) - # Disabled: now the PackageUploadForm also accepts updates - # if Plugin.objects.filter(package_name = self.cleaned_data['package_name']).count(): - # raise ValidationError(_('A plugin with this package name (%s) already exists. To update an existing plugin, you should open the plugin\'s details view and add a new version from there.') % self.cleaned_data['package_name']) - - # if Plugin.objects.filter(name = self.cleaned_data['name']).count(): - # raise ValidationError(_('A plugin with this name (%s) already exists.') % self.cleaned_data['name']) - self.cleaned_data["version"] = PluginVersion.clean_version( - self.cleaned_data["version"] - ) - # Checks for version - if Plugin.objects.filter( - package_name=self.cleaned_data["package_name"], - pluginversion__version=self.cleaned_data["version"], - ).count(): - raise ValidationError( - _( - "A plugin with this name (%s) and version number (%s) already exists." - ) - % (self.cleaned_data["name"], self.cleaned_data["version"]) - ) - # Clean tags - self.cleaned_data["tags"] = _clean_tags(self.cleaned_data.get("tags", None)) - return package - - -class VersionFeedbackForm(forms.Form): - """Feedback for a plugin version""" - - feedback = forms.CharField( - widget=forms.Textarea( - attrs={ - "placeholder": _( - "Please provide clear feedback as a task. \n" - "You can create multiple tasks with '- [ ]'.\n" - "e.g:\n" - "- [ ] first task\n" - "- [ ] second task" - ), - "rows": "5", - "class": "span12" - } - ) - ) - - def clean(self): - super().clean() - feedback = self.cleaned_data.get('feedback') - - if feedback: - lines: list = feedback.split('\n') - bullet_points: list = [ - line[6:].strip() for line in lines if line.strip().startswith('- [ ]') - ] - has_bullet_point = len(bullet_points) >= 1 - tasks: list = bullet_points if has_bullet_point else [feedback] - self.cleaned_data['tasks'] = tasks - - return self.cleaned_data - -class PluginTokenForm(ModelForm): - """ - Form for token description editing - """ - - class Meta: - model = PluginOutstandingToken - fields = ( - "description", - ) \ No newline at end of file diff --git a/qgis-app/plugins/management/__init__.py b/qgis-app/plugins/management/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/qgis-app/plugins/management/commands/__init__.py b/qgis-app/plugins/management/commands/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/qgis-app/plugins/management/commands/cleanmediafolder.py b/qgis-app/plugins/management/commands/cleanmediafolder.py deleted file mode 100644 index da18bda9..00000000 --- a/qgis-app/plugins/management/commands/cleanmediafolder.py +++ /dev/null @@ -1,38 +0,0 @@ -import os -from shutil import rmtree -from django.conf import settings -from django.core.management import call_command -from django.core.management.base import BaseCommand - -class Command(BaseCommand): - help = 'Run collectstatic and delete folders from media that exist in static' - - def handle(self, *args, **options): - confirm = input("Do you want to run 'collectstatic' first? (yes/no): ") - if confirm.lower() == 'yes': - # Run collectstatic command - call_command('collectstatic', interactive=False) - - # Get the paths of static and media folders - static_root = settings.STATIC_ROOT - media_root = settings.MEDIA_ROOT - - # Iterate over each directory in the static folder - for static_dir in os.listdir(static_root): - static_path = os.path.join(static_root, static_dir) - - # Check if the path is a directory and exists in the media folder - if os.path.isdir(static_path) and os.path.exists(os.path.join(media_root, static_dir)): - confirm = input(f"Are you sure you want to delete '{static_dir}' from the media folder? (yes/no): ") - - if confirm.lower() == 'yes': - try: - # Delete the corresponding folder in the media folder - rmtree(os.path.join(media_root, static_dir)) - self.stdout.write(self.style.SUCCESS(f'Deleted {static_dir} from media folder.')) - except Exception as e: - self.stderr.write(self.style.ERROR(f'Error deleting {static_dir}: {str(e)}')) - else: - self.stdout.write(self.style.WARNING(f'Skipped deletion of {static_dir}.')) - - self.stdout.write(self.style.SUCCESS('The media folder has been cleaned.')) diff --git a/qgis-app/plugins/management/commands/generate_plugins_xml.py b/qgis-app/plugins/management/commands/generate_plugins_xml.py deleted file mode 100644 index 1795dcf9..00000000 --- a/qgis-app/plugins/management/commands/generate_plugins_xml.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- -from django.core.management.base import BaseCommand -from plugins.tasks.generate_plugins_xml import generate_plugins_xml -from django.conf import settings - -class Command(BaseCommand): - - help = "Fetch and cached plugins xml" - - def add_arguments(self, parser): - parser.add_argument( - "-s", - "--site", - dest="site", - default=settings.DEFAULT_PLUGINS_SITE, - help="Site url to get the source of plugins", - ) - - def handle(self, *args, **options): - site = options.get("site") - generate_plugins_xml.delay(site=site) diff --git a/qgis-app/plugins/management/commands/organize_old_package.py b/qgis-app/plugins/management/commands/organize_old_package.py deleted file mode 100644 index 991ed958..00000000 --- a/qgis-app/plugins/management/commands/organize_old_package.py +++ /dev/null @@ -1,40 +0,0 @@ -# myapp/management/commands/organize_packages.py -import os -import shutil -from django.core.management.base import BaseCommand -from django.conf import settings -from plugins.models import PluginVersion - -PLUGINS_STORAGE_PATH = getattr(settings, "PLUGINS_STORAGE_PATH", "packages") -class Command(BaseCommand): - help = 'Organize packages created before 2014 into folders by year' - - def handle(self, *args, **options): - packages_dir = os.path.join(settings.MEDIA_ROOT, PLUGINS_STORAGE_PATH) - - # Some of the packages created on 2014 also need to be organized - versions = PluginVersion.objects.filter(created_on__lt='2014-12-31').exclude(package__icontains='2014/') - self.stdout.write(self.style.NOTICE(f'{versions.count()} packages will be organized.')) - - for version in versions: - year_folder = os.path.join(packages_dir, str(version.created_on.year)) - - # Create the year folder if it doesn't exist - os.makedirs(year_folder, exist_ok=True) - - # Copy the package file to the year folder - old_path = version.package.path - if os.path.exists(old_path): - new_path = os.path.join(year_folder, os.path.basename(old_path)) - if not os.path.exists(new_path): - shutil.copy(old_path, year_folder) - - # Update the model with the new package path - version.package.name = os.path.relpath(new_path, settings.MEDIA_ROOT) - version.save() - else: - self.stdout.write(self.style.WARNING(f'Plugin version id {version.pk} ignored: {new_path} already exists.')) - else: - self.stdout.write(self.style.WARNING(f'Plugin version id {version.pk} ignored: {old_path} is not found.')) - - self.stdout.write(self.style.SUCCESS('Packages organized successfully')) diff --git a/qgis-app/plugins/migrations/0001_initial.py b/qgis-app/plugins/migrations/0001_initial.py deleted file mode 100644 index 41119f5b..00000000 --- a/qgis-app/plugins/migrations/0001_initial.py +++ /dev/null @@ -1,260 +0,0 @@ -# Generated by Django 2.1.7 on 2019-03-12 04:30 - -import django.db.models.deletion -import plugins.models -import taggit_autosuggest.managers -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ("taggit", "0001_initial"), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name="Plugin", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "created_on", - models.DateTimeField(auto_now_add=True, verbose_name="Created on"), - ), - ( - "modified_on", - models.DateTimeField(editable=False, verbose_name="Modified on"), - ), - ( - "author", - models.CharField( - help_text="This is the plugin's original author, if different from the uploader, this field will appear in the XML and in the web GUI", - max_length=256, - verbose_name="Author", - ), - ), - ( - "email", - models.EmailField(max_length=254, verbose_name="Author email"), - ), - ( - "homepage", - models.URLField( - blank=True, null=True, verbose_name="Plugin homepage" - ), - ), - ( - "repository", - models.URLField(null=True, verbose_name="Code repository"), - ), - ("tracker", models.URLField(null=True, verbose_name="Tracker")), - ( - "package_name", - models.CharField( - editable=False, - help_text="This is the plugin's internal name, equals to the main folder name", - max_length=256, - unique=True, - verbose_name="Package Name", - ), - ), - ( - "name", - models.CharField( - help_text="Must be unique", - max_length=256, - unique=True, - verbose_name="Name", - ), - ), - ("description", models.TextField(verbose_name="Description")), - ("about", models.TextField(null=True, verbose_name="About")), - ( - "icon", - models.ImageField( - blank=True, - null=True, - upload_to="packages/%Y", - verbose_name="Icon", - ), - ), - ( - "downloads", - models.IntegerField( - default=0, editable=False, verbose_name="Downloads" - ), - ), - ( - "featured", - models.BooleanField( - db_index=True, default=False, verbose_name="Featured" - ), - ), - ( - "deprecated", - models.BooleanField( - db_index=True, default=False, verbose_name="Deprecated" - ), - ), - ( - "server", - models.BooleanField( - db_index=True, default=False, verbose_name="Server" - ), - ), - ( - "rating_votes", - models.PositiveIntegerField(blank=True, default=0, editable=False), - ), - ( - "rating_score", - models.IntegerField(blank=True, default=0, editable=False), - ), - ( - "created_by", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="plugins_created_by", - to=settings.AUTH_USER_MODEL, - verbose_name="Created by", - ), - ), - ( - "owners", - models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL), - ), - ( - "tags", - taggit_autosuggest.managers.TaggableManager( - blank=True, - help_text="A comma-separated list of tags.", - through="taggit.TaggedItem", - to="taggit.Tag", - verbose_name="Tags", - ), - ), - ], - options={ - "ordering": ("name",), - "permissions": (("can_approve", "Can approve plugins versions"),), - }, - ), - migrations.CreateModel( - name="PluginVersion", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "created_on", - models.DateTimeField(auto_now_add=True, verbose_name="Created on"), - ), - ( - "downloads", - models.IntegerField( - default=0, editable=False, verbose_name="Downloads" - ), - ), - ( - "min_qg_version", - plugins.models.QGVersionZeroForcedField( - db_index=True, - max_length=32, - verbose_name="Minimum QGIS version", - ), - ), - ( - "max_qg_version", - plugins.models.QGVersionZeroForcedField( - blank=True, - db_index=True, - max_length=32, - null=True, - verbose_name="Maximum QGIS version", - ), - ), - ( - "version", - plugins.models.VersionField( - db_index=True, max_length=32, verbose_name="Version" - ), - ), - ( - "changelog", - models.TextField(blank=True, null=True, verbose_name="Changelog"), - ), - ( - "package", - models.FileField( - upload_to="packages/%Y", verbose_name="Plugin package" - ), - ), - ( - "experimental", - models.BooleanField( - db_index=True, - default=False, - help_text="Check this box if this version is experimental, leave unchecked if it's stable. Please note that this field might be overridden by metadata (if present).", - verbose_name="Experimental flag", - ), - ), - ( - "approved", - models.BooleanField( - db_index=True, - default=True, - help_text="Set to false if you wish to unapprove the plugin version.", - verbose_name="Approved", - ), - ), - ( - "external_deps", - models.CharField( - help_text="PIP install string", - max_length=512, - null=True, - verbose_name="External dependencies", - ), - ), - ( - "created_by", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL, - verbose_name="Created by", - ), - ), - ( - "plugin", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, to="plugins.Plugin" - ), - ), - ], - options={ - "ordering": ("plugin", "-version", "experimental"), - }, - ), - migrations.AlterUniqueTogether( - name="pluginversion", - unique_together={("plugin", "version")}, - ), - ] diff --git a/qgis-app/plugins/migrations/0002_plugins_feedback.py b/qgis-app/plugins/migrations/0002_plugins_feedback.py deleted file mode 100644 index 76f760a3..00000000 --- a/qgis-app/plugins/migrations/0002_plugins_feedback.py +++ /dev/null @@ -1,31 +0,0 @@ -# Generated by Django 2.2.25 on 2023-06-17 03:19 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('plugins', '0001_initial'), - ] - - operations = [ - migrations.CreateModel( - name='PluginVersionFeedback', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('task', models.TextField(help_text='A feedback task. Please write your review as a task for this plugin.', max_length=1000, verbose_name='Task')), - ('created_on', models.DateTimeField(auto_now_add=True, verbose_name='Created on')), - ('completed_on', models.DateTimeField(blank=True, null=True, verbose_name='Completed on')), - ('is_completed', models.BooleanField(db_index=True, default=False, verbose_name='Completed')), - ('reviewer', models.ForeignKey(help_text='The user who reviewed this plugin.', on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Reviewed by')), - ('version', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='feedback', to='plugins.PluginVersion')), - ], - options={ - 'ordering': ['created_on'], - }, - ), - ] diff --git a/qgis-app/plugins/migrations/0002_pluginversiondownload.py b/qgis-app/plugins/migrations/0002_pluginversiondownload.py deleted file mode 100644 index 6958c874..00000000 --- a/qgis-app/plugins/migrations/0002_pluginversiondownload.py +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by Django 2.2.25 on 2023-06-29 03:29 - -from django.db import migrations, models -import django.db.models.deletion -import django.utils.timezone - - -class Migration(migrations.Migration): - - dependencies = [ - ('plugins', '0001_initial'), - ] - - operations = [ - migrations.CreateModel( - name='PluginVersionDownload', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('download_date', models.DateField(default=django.utils.timezone.now)), - ('download_count', models.IntegerField(default=0)), - ('plugin_version', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='plugins.PluginVersion')), - ], - options={ - 'unique_together': {('plugin_version', 'download_date')}, - }, - ), - ] diff --git a/qgis-app/plugins/migrations/0003_plugin_allow_update_name.py b/qgis-app/plugins/migrations/0003_plugin_allow_update_name.py deleted file mode 100644 index dac62f7f..00000000 --- a/qgis-app/plugins/migrations/0003_plugin_allow_update_name.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.25 on 2023-11-07 03:04 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('plugins', '0002_pluginversiondownload'), - ] - - operations = [ - migrations.AddField( - model_name='plugin', - name='allow_update_name', - field=models.BooleanField(default=False, help_text='Allow name in metadata.txt to update the plugin name', verbose_name='Allow update name'), - ), - ] diff --git a/qgis-app/plugins/migrations/0004_merge_20231122_0223.py b/qgis-app/plugins/migrations/0004_merge_20231122_0223.py deleted file mode 100644 index 8251bc87..00000000 --- a/qgis-app/plugins/migrations/0004_merge_20231122_0223.py +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Django 2.2.25 on 2023-11-30 05:13 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('plugins', '0002_plugins_feedback'), - ('plugins', '0003_plugin_allow_update_name'), - ] - - operations = [ - ] diff --git a/qgis-app/plugins/migrations/0004_merge_20231123_0018.py b/qgis-app/plugins/migrations/0004_merge_20231123_0018.py deleted file mode 100644 index 8e18926d..00000000 --- a/qgis-app/plugins/migrations/0004_merge_20231123_0018.py +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Django 3.2.11 on 2023-11-23 00:18 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('plugins', '0002_plugins_feedback'), - ('plugins', '0003_plugin_allow_update_name'), - ] - - operations = [ - ] diff --git a/qgis-app/plugins/migrations/0005_auto_20231214_2317.py b/qgis-app/plugins/migrations/0005_auto_20231214_2317.py deleted file mode 100644 index 8cd7045b..00000000 --- a/qgis-app/plugins/migrations/0005_auto_20231214_2317.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 2.2.25 on 2023-12-14 23:17 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('plugins', '0004_merge_20231122_0223'), - ] - - operations = [ - migrations.AddField( - model_name='pluginversiondownload', - name='country_code', - field=models.CharField(default='N/D', max_length=3), - ), - migrations.AddField( - model_name='pluginversiondownload', - name='country_name', - field=models.CharField(default='N/D', max_length=100), - ), - ] diff --git a/qgis-app/plugins/migrations/0005_plugin_maintainer.py b/qgis-app/plugins/migrations/0005_plugin_maintainer.py deleted file mode 100644 index dfeffa0b..00000000 --- a/qgis-app/plugins/migrations/0005_plugin_maintainer.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 2.2.25 on 2023-11-29 22:45 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - -def populate_maintainer(apps, schema_editor): - Plugin = apps.get_model('plugins', 'Plugin') - - # Set the maintainer as the plugin creator by default - for obj in Plugin.objects.all(): - obj.maintainer = obj.created_by - obj.save() - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('plugins', '0004_merge_20231122_0223'), - ] - - operations = [ - migrations.AddField( - model_name='plugin', - name='maintainer', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='plugins_maintainer', to=settings.AUTH_USER_MODEL, verbose_name='Maintainer'), - ), - migrations.RunPython(populate_maintainer), - ] diff --git a/qgis-app/plugins/migrations/0005_pluginoutstandingtoken.py b/qgis-app/plugins/migrations/0005_pluginoutstandingtoken.py deleted file mode 100644 index 4e406433..00000000 --- a/qgis-app/plugins/migrations/0005_pluginoutstandingtoken.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by Django 2.2.25 on 2023-12-11 23:36 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('token_blacklist', '0007_auto_20171017_2214'), - ('plugins', '0004_merge_20231122_0223'), - ] - - operations = [ - migrations.CreateModel( - name='PluginOutstandingToken', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('is_blacklisted', models.BooleanField(default=False)), - ('plugin', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='plugins.Plugin')), - ('token', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='token_blacklist.OutstandingToken')), - ], - ), - ] diff --git a/qgis-app/plugins/migrations/0006_auto_20231218_0225.py b/qgis-app/plugins/migrations/0006_auto_20231218_0225.py deleted file mode 100644 index 60d0af5f..00000000 --- a/qgis-app/plugins/migrations/0006_auto_20231218_0225.py +++ /dev/null @@ -1,28 +0,0 @@ -# Generated by Django 2.2.25 on 2023-12-18 02:25 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('plugins', '0005_pluginoutstandingtoken'), - ] - - operations = [ - migrations.AddField( - model_name='pluginoutstandingtoken', - name='description', - field=models.CharField(blank=True, help_text="Describe this token so that it's easier to remember where you're using it.", max_length=512, null=True, verbose_name='Description'), - ), - migrations.AddField( - model_name='pluginoutstandingtoken', - name='is_newly_created', - field=models.BooleanField(default=False), - ), - migrations.AddField( - model_name='pluginoutstandingtoken', - name='last_used_on', - field=models.DateTimeField(blank=True, null=True, verbose_name='Last used on'), - ), - ] diff --git a/qgis-app/plugins/migrations/0006_plugin_display_created_by.py b/qgis-app/plugins/migrations/0006_plugin_display_created_by.py deleted file mode 100644 index 85af8484..00000000 --- a/qgis-app/plugins/migrations/0006_plugin_display_created_by.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.25 on 2023-11-29 23:22 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('plugins', '0005_plugin_maintainer'), - ] - - operations = [ - migrations.AddField( - model_name='plugin', - name='display_created_by', - field=models.BooleanField(default=False, verbose_name='Display "Created by" in plugin details'), - ), - ] diff --git a/qgis-app/plugins/migrations/0007_auto_20240109_0428.py b/qgis-app/plugins/migrations/0007_auto_20240109_0428.py deleted file mode 100644 index 6d6af2da..00000000 --- a/qgis-app/plugins/migrations/0007_auto_20240109_0428.py +++ /dev/null @@ -1,30 +0,0 @@ -# Generated by Django 2.2.25 on 2024-01-09 04:28 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('plugins', '0006_auto_20231218_0225'), - ] - - operations = [ - migrations.AddField( - model_name='pluginversion', - name='is_from_token', - field=models.BooleanField(default=False, verbose_name='Is uploaded using token'), - ), - migrations.AddField( - model_name='pluginversion', - name='token', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='plugins.PluginOutstandingToken', verbose_name='Token used'), - ), - migrations.AlterField( - model_name='pluginversion', - name='created_by', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Created by'), - ), - ] diff --git a/qgis-app/plugins/migrations/0008_merge_20240206_0448.py b/qgis-app/plugins/migrations/0008_merge_20240206_0448.py deleted file mode 100644 index ad09b265..00000000 --- a/qgis-app/plugins/migrations/0008_merge_20240206_0448.py +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Django 2.2.25 on 2024-02-04 23:16 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('plugins', '0007_auto_20240109_0428'), - ('plugins', '0006_plugin_display_created_by'), - ] - - operations = [ - ] diff --git a/qgis-app/plugins/migrations/0009_merge_20240321_0207.py b/qgis-app/plugins/migrations/0009_merge_20240321_0207.py deleted file mode 100644 index 45cd685a..00000000 --- a/qgis-app/plugins/migrations/0009_merge_20240321_0207.py +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Django 4.2.11 on 2024-03-21 02:07 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('plugins', '0004_merge_20231123_0018'), - ('plugins', '0008_merge_20240206_0448'), - ] - - operations = [ - ] diff --git a/qgis-app/plugins/migrations/0010_merge_20240517_0729.py b/qgis-app/plugins/migrations/0010_merge_20240517_0729.py deleted file mode 100644 index 54355764..00000000 --- a/qgis-app/plugins/migrations/0010_merge_20240517_0729.py +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Django 4.2.13 on 2024-05-17 07:29 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('plugins', '0005_auto_20231214_2317'), - ('plugins', '0009_merge_20240321_0207'), - ] - - operations = [ - ] diff --git a/qgis-app/plugins/migrations/0011_alter_pluginversiondownload_unique_together.py b/qgis-app/plugins/migrations/0011_alter_pluginversiondownload_unique_together.py deleted file mode 100644 index fbd85028..00000000 --- a/qgis-app/plugins/migrations/0011_alter_pluginversiondownload_unique_together.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 4.2.13 on 2024-05-28 06:47 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('plugins', '0010_merge_20240517_0729'), - ] - - operations = [ - migrations.AlterUniqueTogether( - name='pluginversiondownload', - unique_together={('plugin_version', 'download_date', 'country_code', 'country_name')}, - ), - ] diff --git a/qgis-app/plugins/migrations/0012_pluginversionfeedback_modified_on.py b/qgis-app/plugins/migrations/0012_pluginversionfeedback_modified_on.py deleted file mode 100644 index 2e637f4e..00000000 --- a/qgis-app/plugins/migrations/0012_pluginversionfeedback_modified_on.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 4.2.13 on 2024-06-13 08:06 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('plugins', '0011_alter_pluginversiondownload_unique_together'), - ] - - operations = [ - migrations.AddField( - model_name='pluginversionfeedback', - name='modified_on', - field=models.DateTimeField(blank=True, editable=False, null=True, verbose_name='Modified on'), - ), - ] diff --git a/qgis-app/plugins/migrations/__init__.py b/qgis-app/plugins/migrations/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/qgis-app/plugins/models.py b/qgis-app/plugins/models.py deleted file mode 100644 index 5b4a7ff2..00000000 --- a/qgis-app/plugins/models.py +++ /dev/null @@ -1,1058 +0,0 @@ -# -*- coding: utf-8 -*- - -import datetime -import os -import re - -from django.conf import settings -from django.contrib.auth.models import User -from django.db import models -from django.urls import reverse -from django.utils.translation import gettext_lazy as _ -from django.utils import timezone -from djangoratings.fields import AnonymousRatingField -from taggit_autosuggest.managers import TaggableManager -from rest_framework_simplejwt.token_blacklist.models import OutstandingToken - -from django.db.models import OuterRef, Count, Subquery, F - -PLUGINS_STORAGE_PATH = getattr(settings, "PLUGINS_STORAGE_PATH", "packages/%Y") -PLUGINS_FRESH_DAYS = getattr(settings, "PLUGINS_FRESH_DAYS", 30) - - -# Used in Version fields to transform DB value back to human readable string -# Allows "-" for processing plugin -VERSION_RE = r"(^|(?<=\.))0+(?!(\.|$|-))|\.#+" - - -class BasePluginManager(models.Manager): - """ - Adds a score - """ - - def get_queryset(self): - return ( - super(BasePluginManager, self) - .get_queryset() - .extra( - select={ - "average_vote": "rating_score/(rating_votes+0.001)", - "latest_version_date": ( - "SELECT created_on FROM plugins_pluginversion WHERE " - "plugins_pluginversion.plugin_id = plugins_plugin.id " - "AND approved = TRUE " - "ORDER BY created_on DESC LIMIT 1" - ), - } - ) - ) - - -class ApprovedPlugins(BasePluginManager): - """ - Shows only public plugins: i.e. those with - and with at least one approved version ("stable" or "experimental") - """ - - def get_queryset(self): - return ( - super(ApprovedPlugins, self) - .get_queryset() - .filter(pluginversion__approved=True) - .distinct() - ) - - -class StablePlugins(BasePluginManager): - """ - Shows only public plugins: i.e. those with "approved" flag set - and with one "stable" version - """ - - def get_queryset(self): - return ( - super(StablePlugins, self) - .get_queryset() - .filter(pluginversion__approved=True, pluginversion__experimental=False) - .distinct() - ) - - -class ExperimentalPlugins(BasePluginManager): - """ - Shows only public plugins: i.e. those with "approved" flag set - and with one "experimental" version - """ - - def get_queryset(self): - return ( - super(ExperimentalPlugins, self) - .get_queryset() - .filter(pluginversion__approved=True, pluginversion__experimental=True) - .distinct() - ) - - -class FeaturedPlugins(BasePluginManager): - """ - Shows only public featured stable plugins: i.e. those with "approved" flag set - and "featured" flag set - """ - - def get_queryset(self): - return ( - super(FeaturedPlugins, self) - .get_queryset() - .filter(pluginversion__approved=True, featured=True) - .order_by("-created_on") - .distinct() - ) - - -class FreshPlugins(BasePluginManager): - """ - Shows only approved plugins: i.e. those with "approved" version flag set - and created less than "days" ago. - """ - - def __init__(self, days=PLUGINS_FRESH_DAYS, *args, **kwargs): - self.days = days - return super(FreshPlugins, self).__init__(*args, **kwargs) - - def get_queryset(self): - return ( - super(FreshPlugins, self) - .get_queryset() - .filter( - deprecated=False, - pluginversion__approved=True, - created_on__gte=datetime.datetime.now() - - datetime.timedelta(days=self.days), - ) - .order_by("-created_on") - .distinct() - ) - - -class LatestPlugins(BasePluginManager): - """ - Shows only approved plugins ordered descending by latest_version - and the latest_version - """ - - def __init__(self, days=PLUGINS_FRESH_DAYS, *args, **kwargs): - self.days = days - return super(LatestPlugins, self).__init__(*args, **kwargs) - - def get_queryset(self): - return ( - super(LatestPlugins, self) - .get_queryset() - .filter( - deprecated=False, - pluginversion__approved=True, - pluginversion__created_on__gte=( - datetime.datetime.now() - datetime.timedelta(days=self.days) - ), - ) - .order_by("-latest_version_date") - .distinct() - ) - - -class UnapprovedPlugins(BasePluginManager): - """ - Shows only unapproved and not deprecated plugins - """ - - def get_queryset(self): - return ( - super(UnapprovedPlugins, self) - .get_queryset() - .filter(pluginversion__approved=False, deprecated=False) - .extra( - select={ - "average_vote": "rating_score/(rating_votes+0.001)", - "latest_version_date": ( - "SELECT created_on FROM plugins_pluginversion WHERE " - "plugins_pluginversion.plugin_id = plugins_plugin.id " - "ORDER BY created_on DESC LIMIT 1" - ), - } - ) - .distinct() - ) - - -class DeprecatedPlugins(BasePluginManager): - """ - Shows only deprecated plugins - """ - - def get_queryset(self): - return ( - super(DeprecatedPlugins, self) - .get_queryset() - .filter(deprecated=True) - .distinct() - ) - - -class PopularPlugins(ApprovedPlugins): - """ - Shows only approved plugins, sort by popularity algorithm - """ - - def get_queryset(self): - return ( - super(PopularPlugins, self) - .get_queryset() - .filter(deprecated=False) - .extra( - select={ - "popularity": "plugins_plugin.downloads * (1 + (rating_score/(rating_votes+0.01)/3))" - } - ) - .order_by("-popularity") - .distinct() - ) - - -class MostDownloadedPlugins(ApprovedPlugins): - """ - Shows only approved plugins, sort by downloads - """ - - def get_queryset(self): - return ( - super(MostDownloadedPlugins, self) - .get_queryset() - .filter(deprecated=False) - .order_by("-downloads") - .distinct() - ) - - -class MostVotedPlugins(ApprovedPlugins): - """ - Shows only approved plugins, sort by vote number - """ - - def get_queryset(self): - return ( - super(MostVotedPlugins, self) - .get_queryset() - .filter(deprecated=False) - .order_by("-rating_votes") - .distinct() - ) - - -class MostRatedPlugins(ApprovedPlugins): - """ - Shows only approved plugins, sort by vote/number of votes number - """ - - def get_queryset(self): - return ( - super(MostRatedPlugins, self) - .get_queryset() - .filter(deprecated=False) - .order_by("-average_vote") - .distinct() - ) - - -class TaggablePlugins(TaggableManager): - """ - Shows only public plugins: i.e. those with "approved" flag set - """ - - def get_queryset(self): - return ( - super(TaggablePlugins, self) - .get_queryset() - .filter(deprecated=False, pluginversion__approved=True) - .distinct() - ) - - -class ServerPlugins(ApprovedPlugins): - """ - Shows only Server plugins - """ - - def get_queryset(self): - return super(ServerPlugins, self).get_queryset().filter(server=True).distinct() - - -class FeedbackCompletedPlugins(models.Manager): - """ - Show only unapproved plugins with resolved feedbacks - """ - def get_queryset(self): - feedback_count_subquery = PluginVersionFeedback.objects.filter( - version=OuterRef('pluginversion'), - is_completed=True - ).values('version').annotate( - completed_count=Count('id') - ).values('completed_count') - - return ( - super(FeedbackCompletedPlugins, self) - .get_queryset() - .filter( - pluginversion__approved=False, - deprecated=False - ) - .annotate( - total_feedback_count=Count('pluginversion__feedback'), - completed_feedback_count=Subquery(feedback_count_subquery) - ) - .filter( - total_feedback_count=F('completed_feedback_count') - ) - .extra( - select={ - "average_vote": "rating_score/(rating_votes+0.001)", - "latest_version_date": ( - "SELECT created_on FROM plugins_pluginversion WHERE " - "plugins_pluginversion.plugin_id = plugins_plugin.id " - "ORDER BY created_on DESC LIMIT 1" - ), - } - ).distinct() - ) - -class FeedbackReceivedPlugins(models.Manager): - """ - Show only unapproved plugins with a pending feedback - """ - def get_queryset(self): - feedback_count_subquery = PluginVersionFeedback.objects.filter( - version=OuterRef('pluginversion'), - is_completed=False - ).values('version').annotate( - received_count=Count('id') - ).values('received_count') - - return ( - super(FeedbackReceivedPlugins, self) - .get_queryset() - .filter( - pluginversion__approved=False, - deprecated=False - ) - .annotate( - received_feedback_count=Subquery(feedback_count_subquery) - ) - .filter( - received_feedback_count__gte=1 - ) - .extra( - select={ - "average_vote": "rating_score/(rating_votes+0.001)", - "latest_version_date": ( - "SELECT created_on FROM plugins_pluginversion WHERE " - "plugins_pluginversion.plugin_id = plugins_plugin.id " - "ORDER BY created_on DESC LIMIT 1" - ), - } - ).distinct() - ) - - -class FeedbackPendingPlugins(models.Manager): - """ - Show only unapproved plugins with a feedback - """ - def get_queryset(self): - return ( - super(FeedbackPendingPlugins, self) - .get_queryset() - .filter( - pluginversion__approved=False, - deprecated=False - ) - .annotate( - total_feedback_count=Count('pluginversion__feedback'), - ) - .filter( - total_feedback_count=0 - ) - .extra( - select={ - "average_vote": "rating_score/(rating_votes+0.001)", - "latest_version_date": ( - "SELECT created_on FROM plugins_pluginversion WHERE " - "plugins_pluginversion.plugin_id = plugins_plugin.id " - "ORDER BY created_on DESC LIMIT 1" - ), - } - ).distinct() - ) - - - -class Plugin(models.Model): - """ - Plugins model - """ - - # dates - created_on = models.DateTimeField( - _("Created on"), auto_now_add=True, editable=False - ) - modified_on = models.DateTimeField(_("Modified on"), editable=False) - - # owners - created_by = models.ForeignKey( - User, - verbose_name=_("Created by"), - related_name="plugins_created_by", - on_delete=models.CASCADE, - ) - - # maintainer - maintainer = models.ForeignKey( - User, - verbose_name=_("Maintainer"), - related_name="plugins_maintainer", - on_delete=models.CASCADE, - blank=True, - null=True - ) - - display_created_by = models.BooleanField( - _('Display "Created by" in plugin details'), - default=False - ) - - author = models.CharField( - _("Author"), - help_text=_( - "This is the plugin's original author, if different from the uploader, this field will appear in the XML and in the web GUI" - ), - max_length=256, - ) - email = models.EmailField(_("Author email")) - homepage = models.URLField(_("Plugin homepage"), blank=True, null=True) - # Support - repository = models.URLField(_("Code repository"), blank=False, null=True) - tracker = models.URLField(_("Tracker"), blank=False, null=True) - - owners = models.ManyToManyField(User, blank=True) - - # name, desc etc. - package_name = models.CharField( - _("Package Name"), - help_text=_( - "This is the plugin's internal name, equals to the main folder name" - ), - max_length=256, - unique=True, - editable=False, - ) - name = models.CharField( - _("Name"), help_text=_("Must be unique"), max_length=256, unique=True - ) - - allow_update_name = models.BooleanField( - _("Allow update name"), - help_text=_("Allow name in metadata.txt to update the plugin name"), - default=False - ) - - description = models.TextField(_("Description")) - about = models.TextField(_("About"), blank=False, null=True) - - icon = models.ImageField( - _("Icon"), blank=True, null=True, upload_to=PLUGINS_STORAGE_PATH - ) - - # downloads (soft trigger from versions) - downloads = models.IntegerField(_("Downloads"), default=0, editable=False) - - # Flags - featured = models.BooleanField(_("Featured"), default=False, db_index=True) - deprecated = models.BooleanField(_("Deprecated"), default=False, db_index=True) - - # True if the plugin has a server interface - server = models.BooleanField(_("Server"), default=False, db_index=True) - - # Managers - objects = models.Manager() - base_objects = BasePluginManager() - approved_objects = ApprovedPlugins() - stable_objects = StablePlugins() - experimental_objects = ExperimentalPlugins() - featured_objects = FeaturedPlugins() - fresh_objects = FreshPlugins() - latest_objects = LatestPlugins() - unapproved_objects = UnapprovedPlugins() - deprecated_objects = DeprecatedPlugins() - popular_objects = PopularPlugins() - most_downloaded_objects = MostDownloadedPlugins() - most_voted_objects = MostVotedPlugins() - most_rated_objects = MostRatedPlugins() - server_objects = ServerPlugins() - feedback_completed_objects = FeedbackCompletedPlugins() - feedback_received_objects = FeedbackReceivedPlugins() - feedback_pending_objects = FeedbackPendingPlugins() - - rating = AnonymousRatingField( - range=5, use_cookies=True, can_change_vote=True, allow_delete=True - ) - - tags = TaggableManager(blank=True) - - @property - def approved(self): - """ - Returns True if the plugin has at least one approved version - """ - return self.pluginversion_set.filter(approved=True).count() > 0 - - @property - def trusted(self): - """ - Returns True if the plugin's author has plugins.can_approve permission - Purpose of this decorator is to show/hide buttons in the template - """ - return self.created_by.has_perm("plugins.can_approve") - - @property - def stable(self): - """ - Returns the latest stable and approved version - """ - try: - return self.pluginversion_set.filter( - approved=True, experimental=False - ).order_by("-version")[0] - except: - return None - - @property - def experimental(self): - """ - Returns the latest experimental and approved version - """ - try: - return self.pluginversion_set.filter( - approved=True, experimental=True - ).order_by("-version")[0] - except: - return None - - @property - def editors(self): - """ - Returns a list of users that can edit the plugin: creator and owners - """ - l = [o for o in self.owners.all()] - l.append(self.created_by) - return l - - @property - def approvers(self): - """ - Returns a list of editor users that can approve a version - """ - return [l for l in self.editors if l.has_perm("plugins.can_approve")] - - @property - def avg_vote(self): - """ - Returns the rating_score/(rating_votes+0.001) value, this - calculation is also available in manager's queries as - "average_vote". - This property is still useful when the object is not loaded - through a manager, for example in related objects. - """ - return self.rating_score / (self.rating_votes + 0.001) - - class Meta: - ordering = ("name",) - # ABP: Note: this permission should belong to the - # PluginVersion class. I left it here because it - # doesn't really matters where it is. Just be - # sure you query for it using the 'plugins' class - # instead of the 'pluginversion' class. - permissions = (("can_approve", "Can approve plugins versions"),) - - def get_absolute_url(self): - return reverse("plugin_detail", args=(self.package_name,)) - - def __unicode__(self): - return "[%s] %s" % (self.pk, self.name) - - def __str__(self): - return self.__unicode__() - - def clean(self): - """ - Validates: - - * Checks that package_name respect regexp [A-Za-z][A-Za-z0-9-_]+ - * checks for case-insensitive unique package_name - """ - from django.core.exceptions import ValidationError - - if not re.match(r"^[A-Za-z][A-Za-z0-9-_]+$", self.package_name): - raise ValidationError( - _( - "Plugin package_name (which equals to the main plugin folder inside the zip file) must start with an ASCII letter and can contain only ASCII letters, digits and the - and _ signs." - ) - ) - - if self.pk: - qs = Plugin.objects.filter(name__iexact=self.name).exclude(pk=self.pk) - else: - qs = Plugin.objects.filter(name__iexact=self.name) - if qs.count(): - raise ValidationError( - _( - "A plugin with a similar name (%s) already exists (the name only differs in case)." - ) - % qs.all()[0].name - ) - - if self.pk: - qs = Plugin.objects.filter(package_name__iexact=self.package_name).exclude( - pk=self.pk - ) - else: - qs = Plugin.objects.filter(package_name__iexact=self.package_name) - if qs.count(): - raise ValidationError( - _( - "A plugin with a similar package_name (%s) already exists (the package_name only differs in case)." - ) - % qs.all()[0].package_name - ) - - def save(self, keep_date=False, *args, **kwargs): - """ - Soft triggers: - * updates modified_on if keep_date is not set - * set maintainer to the plugin creator when not specified - """ - if self.pk and not keep_date: - import logging - - logging.debug("Updating modified_on for the Plugin instance") - self.modified_on = datetime.datetime.now() - if not self.pk: - self.modified_on = datetime.datetime.now() - if not self.maintainer: - self.maintainer = self.created_by - super(Plugin, self).save(*args, **kwargs) - - -# Plugin version managers - - -class ApprovedPluginVersions(models.Manager): - """ - Shows only public plugin versions: - """ - - def get_queryset(self): - return ( - super(ApprovedPluginVersions, self) - .get_queryset() - .filter(approved=True) - .order_by("-version") - ) - - -class StablePluginVersions(ApprovedPluginVersions): - """ - Shows only approved public plugin versions: i.e. those with "approved" flag set - and with "stable" flag - """ - - def get_queryset(self): - return ( - super(StablePluginVersions, self).get_queryset().filter(experimental=False) - ) - - -class ExperimentalPluginVersions(ApprovedPluginVersions): - """ - Shows only public plugin versions: i.e. those with "approved" flag set - and with "experimental" flag - """ - - def get_queryset(self): - return ( - super(ExperimentalPluginVersions, self) - .get_queryset() - .filter(experimental=True) - ) - - -def vjust(str, level=3, delim=".", bitsize=3, fillchar=" ", force_zero=False): - """ - Normalize a dotted version string. - - 1.12 becomes : 1. 12 - 1.1 becomes : 1. 1 - - - if force_zero=True and level=2: - - 1.12 becomes : 1. 12. 0 - 1.1 becomes : 1. 1. 0 - - - """ - if not str: - return str - nb = str.count(delim) - if nb < level: - if force_zero: - str += (level - nb) * (delim + "0") - else: - str += (level - nb) * delim - parts = [] - for v in str.split(delim)[: level + 1]: - if not v: - parts.append(v.rjust(bitsize, "#")) - else: - parts.append(v.rjust(bitsize, fillchar)) - return delim.join(parts) - - -class VersionField(models.CharField): - - description = 'Field to store version strings ("a.b.c.d") in a way it is sortable' - - def get_prep_value(self, value): - return vjust(value, fillchar="0") - - def to_python(self, value): - if not value: - return "" - return re.sub(VERSION_RE, "", value) - - def from_db_value(self, value, expression, connection): - if value is None: - return value - return self.to_python(value) - - -class QGVersionZeroForcedField(models.CharField): - - description = 'Field to store version strings ("a.b.c.d") in a way it \ - is sortable and QGIS scheme compatible (x.y.z).' - - def get_prep_value(self, value): - return vjust(value, fillchar="0", level=2, force_zero=True) - - def to_python(self, value): - if not value: - return "" - return re.sub(VERSION_RE, "", value) - - def from_db_value(self, value, expression, connection): - if value is None: - return value - return self.to_python(value) - - -class PluginOutstandingToken(models.Model): - """ - Plugin outstanding token - """ - plugin = models.ForeignKey( - Plugin, - on_delete=models.CASCADE - ) - token = models.ForeignKey( - OutstandingToken, - on_delete=models.CASCADE - ) - is_blacklisted = models.BooleanField(default=False) - is_newly_created = models.BooleanField(default=False) - description = models.CharField( - verbose_name=_("Description"), - help_text=_("Describe this token so that it's easier to remember where you're using it."), - max_length=512, - blank=True, - null=True, - ) - last_used_on = models.DateTimeField( - verbose_name=_("Last used on"), - blank=True, - null=True - ) - -class PluginVersion(models.Model): - """ - Plugin versions - """ - - # link to parent - plugin = models.ForeignKey(Plugin, on_delete=models.CASCADE) - # dates - created_on = models.DateTimeField( - _("Created on"), auto_now_add=True, editable=False - ) - # download counter - downloads = models.IntegerField(_("Downloads"), default=0, editable=False) - # owners - created_by = models.ForeignKey( - User, verbose_name=_("Created by"), on_delete=models.CASCADE, null=True, blank=True - ) - # version info, the first should be read from plugin - min_qg_version = QGVersionZeroForcedField( - _("Minimum QGIS version"), max_length=32, db_index=True - ) - max_qg_version = QGVersionZeroForcedField( - _("Maximum QGIS version"), max_length=32, null=True, blank=True, db_index=True - ) - version = VersionField(_("Version"), max_length=32, db_index=True) - changelog = models.TextField(_("Changelog"), null=True, blank=True) - - # the file! - package = models.FileField(_("Plugin package"), upload_to=PLUGINS_STORAGE_PATH) - # Flags: checks on unique current/experimental are done in save() and possibly in the views - experimental = models.BooleanField( - _("Experimental flag"), - default=False, - help_text=_( - "Check this box if this version is experimental, leave unchecked if it's stable. Please note that this field might be overridden by metadata (if present)." - ), - db_index=True, - ) - approved = models.BooleanField( - _("Approved"), - default=True, - help_text=_("Set to false if you wish to unapprove the plugin version."), - db_index=True, - ) - external_deps = models.CharField( - _("External dependencies"), - help_text=_("PIP install string"), - max_length=512, - blank=False, - null=True, - ) - is_from_token = models.BooleanField( - _("Is uploaded using token"), - default=False - ) - # Link to the token if upload is using token - token = models.ForeignKey( - PluginOutstandingToken, verbose_name=_("Token used"), on_delete=models.CASCADE, null=True, blank=True - ) - - # Managers, used in xml output - objects = models.Manager() - approved_objects = ApprovedPluginVersions() - stable_objects = StablePluginVersions() - experimental_objects = ExperimentalPluginVersions() - - @property - def file_name(self): - return os.path.basename(self.package.file.name) - - def save(self, *args, **kwargs): - """ - Soft triggers: - * updates modified_on in parent - """ - # Transforms the version... - # Need to be done here too, because clean() - # is only called in forms. - if self.version.rfind(" ") > 0: - self.version = self.version.rsplit(" ")[-1] - - # Only change modified_on when a new version is created, - # every download triggers a save to update the counter - if not self.pk: - self.plugin.modified_on = self.created_on - self.plugin.save() - - # fix Max version - if not self.max_qg_version: - self.max_qg_version = "%s.99" % tuple(self.min_qg_version.split(".")[0]) - - super(PluginVersion, self).save(*args, **kwargs) - - def clean(self): - """ - Validates: - - * checks for unique - * checks for version only digits and dots - """ - from django.core.exceptions import ValidationError - - # Transforms the version - self.version = PluginVersion.clean_version(self.version) - - versions_to_check = PluginVersion.objects.filter( - plugin=self.plugin, version=self.version - ) - if self.pk: - versions_to_check = versions_to_check.exclude(pk=self.pk) - # Checks for unique_together - if ( - versions_to_check.filter(plugin=self.plugin, version=self.version).count() - > 0 - ): - raise ValidationError( - _( - "Version value must be unique among each plugin: a version with same number already exists." - ) - ) - - @staticmethod - def clean_version(version): - """ - Strips blanks and Version string - """ - if version.rfind(" ") > 0: - version = version.rsplit(" ")[-1] - return version - - class Meta: - unique_together = ("plugin", "version") - ordering = ("plugin", "-version", "experimental") - - def get_absolute_url(self): - return reverse( - "version_detail", - args=( - self.plugin.package_name, - self.version, - ), - ) - - def get_download_url(self): - return reverse( - "version_download", - args=( - self.plugin.package_name, - self.version, - ), - ) - - def download_file_name(self): - return "%s.%s.zip" % (self.plugin.package_name, self.version) - - def __unicode__(self): - desc = "%s %s" % (self.plugin, self.version) - if self.experimental: - desc = "%s %s" % (desc, _("Experimental")) - return desc - - def __str__(self): - return self.__unicode__() - - -class PluginVersionFeedback(models.Model): - """Feedback for a plugin version.""" - - version = models.ForeignKey( - PluginVersion, - on_delete=models.CASCADE, - related_name="feedback" - ) - reviewer = models.ForeignKey( - User, - verbose_name=_("Reviewed by"), - help_text=_("The user who reviewed this plugin."), - on_delete=models.CASCADE, - ) - task = models.TextField( - verbose_name=_("Task"), - help_text=_("A feedback task. Please write your review as a task for this plugin."), - max_length=1000, - blank=False, - null=False - ) - created_on = models.DateTimeField( - verbose_name=_("Created on"), - auto_now_add=True, - editable=False - ) - modified_on = models.DateTimeField( - _("Modified on"), - editable=False, - blank=True, - null=True - ) - - completed_on = models.DateTimeField( - verbose_name=_("Completed on"), - blank=True, - null=True - ) - is_completed = models.BooleanField( - verbose_name=_("Completed"), - default=False, - db_index=True - ) - - class Meta: - ordering = ["created_on"] - - def save(self, *args, **kwargs): - if self.is_completed is True: - self.completed_on = datetime.datetime.now() - else: - self.completed_on = None - super(PluginVersionFeedback, self).save(*args, **kwargs) - - -def delete_version_package(sender, instance, **kw): - """ - Removes the zip package - """ - try: - os.remove(instance.package.path) - except: - pass - - -def delete_plugin_icon(sender, instance, **kw): - """ - Removes the plugin icon - """ - try: - os.remove(instance.icon.path) - except: - pass - - -class PluginVersionDownload(models.Model): - """ - Plugin version downloads - """ - plugin_version = models.ForeignKey( - PluginVersion, - on_delete=models.CASCADE - ) - download_date = models.DateField( - default=timezone.now - ) - country_code = models.CharField(max_length=3, default='N/D') - country_name = models.CharField(max_length=100, default='N/D') - download_count = models.IntegerField( - default=0 - ) - class Meta: - unique_together = ( - 'plugin_version', - 'download_date', - 'country_code', - 'country_name' - ) - - -models.signals.post_delete.connect(delete_version_package, sender=PluginVersion) -models.signals.post_delete.connect(delete_plugin_icon, sender=Plugin) diff --git a/qgis-app/plugins/search_indexes.py b/qgis-app/plugins/search_indexes.py deleted file mode 100644 index 889be9f1..00000000 --- a/qgis-app/plugins/search_indexes.py +++ /dev/null @@ -1,20 +0,0 @@ -from haystack import indexes -from plugins.models import Plugin - - -class PluginIndex(indexes.SearchIndex, indexes.Indexable): - text = indexes.CharField(document=True, use_template=True) - created_by = indexes.CharField(model_attr="created_by") - created_on = indexes.DateTimeField(model_attr="created_on") - # We add this for autocomplete. - name_auto = indexes.EdgeNgramField(model_attr="name") - description_auto = indexes.EdgeNgramField(model_attr="description") - about_auto = indexes.EdgeNgramField(model_attr="about", default="") - package_name_auto = indexes.EdgeNgramField(model_attr="package_name", default="") - - def get_model(self): - return Plugin - - def index_queryset(self, using=None): - """Only search in approved plugins.""" - return Plugin.approved_objects.all() diff --git a/qgis-app/plugins/tasks/__init__.py b/qgis-app/plugins/tasks/__init__.py deleted file mode 100644 index 4a4a1481..00000000 --- a/qgis-app/plugins/tasks/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from plugins.tasks.generate_plugins_xml import * # noqa -from plugins.tasks.update_feedjack import * # noqa -from plugins.tasks.update_qgis_versions import * # noqa -from plugins.tasks.rebuild_search_index import * # noqa diff --git a/qgis-app/plugins/tasks/generate_plugins_xml.py b/qgis-app/plugins/tasks/generate_plugins_xml.py deleted file mode 100644 index a7415b60..00000000 --- a/qgis-app/plugins/tasks/generate_plugins_xml.py +++ /dev/null @@ -1,91 +0,0 @@ -import os - -import requests -from celery import shared_task -from celery.utils.log import get_task_logger -from preferences import preferences -from django.conf import settings -from preferences import preferences - - -logger = get_task_logger(__name__) - - -@shared_task -def generate_plugins_xml(site=""): - """ - Fetch the xml list of plugins from the plugin site. - :param site: site domain where the plugins will be fetched, default to - http://plugins.qgis.org - """ - logger.info('generate_plugins_xml : {}'.format(site)) - - if not site: - if settings.DEFAULT_PLUGINS_SITE: - site = settings.DEFAULT_PLUGINS_SITE - else: - site = "http://plugins.qgis.org" - plugins_url = "{}/plugins/plugins_new.xml".format(site) - - versions = preferences.SitePreference.qgis_versions - - if versions: - versions = versions.split(",") - else: - versions = [ - "1.8", - "2.0", - "2.2", - "2.4", - "2.6", - "2.8", - "2.10", - "2.12", - "2.14", - "2.15", - "2.16", - "2.17", - "2.18", - "2.99", - "3.0", - "3.1", - "3.2", - "3.3", - "3.4", - "3.5", - "3.6", - "3.7", - "3.8", - "3.9", - "3.10", - "3.11", - "3.12", - "3.13", - "3.14", - "3.15", - "3.16", - "3.17", - "3.18", - "3.19", - "3.20", - "3.21", - "3.22", - "3.23", - "3.24", - "3.25", - ] - - folder_path = os.path.join(settings.MEDIA_ROOT, "cached_xmls") - - if not os.path.exists(folder_path): - os.mkdir(folder_path) - - for version in versions: - response = requests.get( - "{url}?qgis={version}".format(url=plugins_url, version=version) - ) - - if response.status_code == 200: - file_name = "plugins_{}.xml".format(version) - with open(os.path.join(folder_path, file_name), "w+") as file: - file.write(response.text) diff --git a/qgis-app/plugins/tasks/rebuild_search_index.py b/qgis-app/plugins/tasks/rebuild_search_index.py deleted file mode 100644 index 50c76d4d..00000000 --- a/qgis-app/plugins/tasks/rebuild_search_index.py +++ /dev/null @@ -1,12 +0,0 @@ -from celery import shared_task -from haystack.management.commands import update_index -from celery.utils.log import get_task_logger - -logger = get_task_logger(__name__) - -@shared_task -def rebuild_search_index(): - """ - Celery task to rebuild the search index. - """ - update_index.Command().handle() \ No newline at end of file diff --git a/qgis-app/plugins/tasks/update_feedjack.py b/qgis-app/plugins/tasks/update_feedjack.py deleted file mode 100644 index e12d14bd..00000000 --- a/qgis-app/plugins/tasks/update_feedjack.py +++ /dev/null @@ -1,10 +0,0 @@ -from celery import shared_task -from celery.utils.log import get_task_logger - -logger = get_task_logger(__name__) - - -@shared_task -def update_feedjack(): - import subprocess - subprocess.call(['python', 'manage.py', 'feedjackupdate']) diff --git a/qgis-app/plugins/tasks/update_qgis_versions.py b/qgis-app/plugins/tasks/update_qgis_versions.py deleted file mode 100644 index 890d836c..00000000 --- a/qgis-app/plugins/tasks/update_qgis_versions.py +++ /dev/null @@ -1,27 +0,0 @@ -from celery import shared_task -from base.models.site_preferences import SitePreference -from plugins.utils import get_qgis_versions - - -@shared_task -def update_qgis_versions(): - """ - This background task fetches the QGIS versions from the GitHub QGIS releases - and then updates the current QGIS version in the database. - """ - site_preference = SitePreference.objects.first() - if not site_preference: - site_preference = SitePreference.objects.create() - - qgis_versions = get_qgis_versions() - stored_qgis_versions = site_preference.qgis_versions.split(',') - for qgis_version in qgis_versions: - if qgis_version not in stored_qgis_versions: - stored_qgis_versions.append(qgis_version) - stored_qgis_versions = list(filter(None, stored_qgis_versions)) - stored_qgis_versions = [tuple(map(int, v.split('.'))) for v in stored_qgis_versions] - stored_qgis_versions.sort(reverse=True) - stored_qgis_versions = ['.'.join(map(str, v)) for v in stored_qgis_versions] - - site_preference.qgis_versions = ','.join(stored_qgis_versions) - site_preference.save() diff --git a/qgis-app/plugins/templates/plugins/form_snippet.html b/qgis-app/plugins/templates/plugins/form_snippet.html deleted file mode 100755 index ae794508..00000000 --- a/qgis-app/plugins/templates/plugins/form_snippet.html +++ /dev/null @@ -1,27 +0,0 @@ -{% load i18n plugin_utils %} -
-{% for field in form %} -
- {% if field.field.widget|klass == 'CheckboxInput' %} - - {% else %} - {{ field.label_tag }} - {% if field.errors %} -
- {{ field.errors }} -
- {% endif %} - {{ field }} - {% endif %} -
{{ field.help_text }}
-
-{% endfor %} -
diff --git a/qgis-app/plugins/templates/plugins/pagination.html b/qgis-app/plugins/templates/plugins/pagination.html deleted file mode 100644 index 99bec111..00000000 --- a/qgis-app/plugins/templates/plugins/pagination.html +++ /dev/null @@ -1,50 +0,0 @@ -{% if is_paginated %} -{% load i18n %} - -{% endif %} diff --git a/qgis-app/plugins/templates/plugins/plugin_base.html b/qgis-app/plugins/templates/plugins/plugin_base.html deleted file mode 100644 index 31badff6..00000000 --- a/qgis-app/plugins/templates/plugins/plugin_base.html +++ /dev/null @@ -1,52 +0,0 @@ -{% extends BASE_TEMPLATE %}{% load i18n plugins_tagcloud static %} -{% load resources_custom_tags plugin_utils %} -{% block title %} - {% plugin_title %} — {% trans "QGIS Python Plugins Repository"%} -{% endblock %} - -{% block app_title %} -

{% trans "QGIS Python Plugins Repository"%}

-{% endblock %} - - -{% block menu %} -{{ block.super }} - {% trans "Upload a plugin" %} -

{% trans "Plugins" %}

- - -
-{% include_plugins_tagcloud_modal 'plugins.plugin' %} - -{% endblock %} - -{% block "credits" %} - {{ block.super }} -{% endblock %} diff --git a/qgis-app/plugins/templates/plugins/plugin_delete_confirm.html b/qgis-app/plugins/templates/plugins/plugin_delete_confirm.html deleted file mode 100644 index 4dbd8164..00000000 --- a/qgis-app/plugins/templates/plugins/plugin_delete_confirm.html +++ /dev/null @@ -1,9 +0,0 @@ -{% extends 'plugins/plugin_base.html' %}{% load i18n %} -{% block content %} -

{% trans "Delete plugin"%}: {{ plugin.title }}

-
{% csrf_token %} -

{% trans "You asked to delete the plugin and all its versions. The plugin will be permanently deleted. This action cannot be undone. Please confirm."%}

-

{% trans "Cancel" %}

-
- -{% endblock %} diff --git a/qgis-app/plugins/templates/plugins/plugin_detail.html b/qgis-app/plugins/templates/plugins/plugin_detail.html deleted file mode 100644 index 2448114e..00000000 --- a/qgis-app/plugins/templates/plugins/plugin_detail.html +++ /dev/null @@ -1,392 +0,0 @@ -{% extends 'plugins/plugin_base.html' %}{% load i18n static thumbnail %} -{% load local_timezone %} -{% load plugin_utils %} -{% block extrajs %} -{{ block.super }} - - - -{% endblock %} -{% block extracss %} - -{{ block.super }} - -{% endblock %} -{% block content %} -
- {% if object.stable or object.experimental %} - -
- {% else %} -
- {% endif %} -

{{ object.name }} - {% if object.icon and object.icon.file and object.icon|is_image_valid %} - {% with image_extension=object.icon.name|file_extension %} - {% if image_extension == 'svg' %} - {% trans - {% else %} - {% thumbnail object.icon "128x128" upscale=False format="PNG" as im %} - {% trans - {% endthumbnail %} - {% endif %} - {% endwith %} - {% else %} - {% trans - {% endif %} -

-
- {% trans "Plugin ID:" %} {{ object.pk }} -
- -
-
-
-
-
- {% if not object.experimental and not object.stable %} -
-

{% trans "This plugin has no public version yet." %}

-
- {% endif %} - - {% if not object.created_by.is_active %} -
-

{% trans "The plugin maintainer has been blocked." %}

-
- {% endif %} - - {% if object.deprecated %} -
-

{% trans "This plugin is deprecated!" %}

-
- {% endif %} - -
({% firstof votes '0' %}) {% trans "votes" %} 
-
-

{{ object.description|safe|linebreaksbr }}

-
- {% comment%} - {% if object.about %} -

{{ object.about|safe|linebreaksbr }}

- {% endif %} - {% endcomment %} - - - -
- {% if object.about %} -
-

{{ object.about|safe|linebreaksbr }}

-
- {% endif %} -
- {% if object.server %} -
{% trans "This plugin provides an interface for QGIS Server." %}
- {% endif %} -
- {% if object.author %} -
{% trans "Author"%}
-
- {{ object.author }} -
- {% endif %} - {% if object.email and not user.is_anonymous %} -
{% trans "Author's email"%}
-
{{ object.email }}
- {% endif %} - {% if object.display_created_by %} -
{% trans "Created by"%}
-
- {{ object.created_by }} -
- - {% endif %} -
{% trans "Maintainer"%}
-
- {{ object.maintainer }} -
- {% if object.owners.count %} -
{% trans "Collaborators"%}
-
- {% for owner in object.owners.all %} - {{ owner.username }}{% if not forloop.last %},{% endif %} - {% endfor %} -
- {% endif %} - {% if object.tags.count %} -
- {% trans "Tags"%} -
-
- {% for tag in object.tags.all %}{% if tag.slug %}{{tag}} - {% if not forloop.last %}, {% endif %}{% endif %}{% endfor %} -
- {% endif %} - {% if object.homepage %} -
{% trans "Plugin home page"%}
-
{{ object.homepage }}
- {% endif %} - {% if object.tracker %} -
{% trans "Tracker" %}
-
{% trans "Browse and report bugs" %}
- {% endif %} - {% if object.repository %} -
{% trans "Code repository" %}
-
{{ object.repository }}
- {% endif %} - {% if object.stable %} -
{% trans "Latest stable version"%}
-
{{ object.stable.version }}
- {% endif %} - {% if object.experimental %} -
{% trans "Latest experimental version"%}:
-
{{ object.experimental.version }}
- {% endif %} - {% if object.pk %} -
{% trans "Plugin ID"%}
-
- {{ object.pk }} -
- -
-
- {% endif %} -
-
-
- {% if object.pluginversion_set.count %} - {# show all versions if user is authorized #} -
- - - - - {% if not user.is_anonymous %}{% endif %} - - - - - - - {% if user.is_staff or user in object.approvers or user in object.editors %}{% endif %} - - - - {% for version in object.pluginversion_set.all %} - {% if version.approved or not user.is_anonymous %} - - - {% if not user.is_anonymous %}{% endif %} - - - - - {% if version.is_from_token %} - - {% else %} - - {% endif %} - - {% if user.is_staff or user in version.plugin.approvers or user in version.plugin.editors %} - {% endif %} - - {% endif %} - {% endfor %} - -
{% trans "Version" %}{% trans "Approved" %}{% trans "Experimental" %}{% trans "Min QGIS version" %}{% trans "Max QGIS version" %}{% trans "Downloads" %}{% trans "Uploaded by" %}{% trans "Date" %}{% trans "Manage" %}
{{ version.version }}{{ version.approved|yesno }}{{ version.experimental|yesno }}{{ version.min_qg_version }}{{ version.max_qg_version }}{{ version.downloads }}Token {{ version.token.description|default:"" }}{{ version.created_by }}{{ version.created_on|local_timezone }}
{% csrf_token %} - {% if user.is_staff or user in version.plugin.approvers %} - {% if not version.approved %} - - {% else %} - - {% endif %} - {% endif %} - - - {% if version.feedback|feedbacks_not_completed|length >= 2 %} - {{ version.feedback|feedbacks_not_completed|length }} - {% endif %} - - {% if user.is_staff or user in version.plugin.editors %} -  {% endif %}
-
-
- {% endif %} -
- - {% if user.is_staff or user in object.editors %} -
-
{% csrf_token %} -
- {% trans "Edit" %} - {% trans "Add version" %} - {% trans "Tokens" %} - {% if user.is_staff %} - {% if object.featured %} - {% else %} - {% endif %} - {% endif %} - {% if user.is_staff or user in object.editors %} - {% trans "Delete" %} - {% endif %} -
-
-
-
- -
- {% endif %} - {# end admin #} -
-
-{% endblock %} diff --git a/qgis-app/plugins/templates/plugins/plugin_feedback.html b/qgis-app/plugins/templates/plugins/plugin_feedback.html deleted file mode 100644 index 300e5ff3..00000000 --- a/qgis-app/plugins/templates/plugins/plugin_feedback.html +++ /dev/null @@ -1,235 +0,0 @@ -{% extends 'plugins/plugin_base.html' %}{% load i18n %} -{% block content %} - {% if form.errors %} -
- -

{% trans "The form contains errors and cannot be submitted, please check the fields highlighted in red." %}

-
- {% endif %} - {% if form.non_field_errors %} -
- - {% for error in form.non_field_errors %} -

{{ error }}

- {% endfor %} -
- {% endif %} -

{% trans "Feedback Plugin" %} {{ version.plugin.name }} {{ version.version }}

- - -{% endblock %} - -{% block extrajs %} - - - -{% endblock %} \ No newline at end of file diff --git a/qgis-app/plugins/templates/plugins/plugin_form.html b/qgis-app/plugins/templates/plugins/plugin_form.html deleted file mode 100644 index 86edcd27..00000000 --- a/qgis-app/plugins/templates/plugins/plugin_form.html +++ /dev/null @@ -1,108 +0,0 @@ -{% extends 'plugins/plugin_base.html' %}{% load static i18n %} -{% block extrajs %} -{{ block.super }} - - - - - - - - -{% endblock %} -{% block content %} -

{{ form_title }} {{ plugin }}

-
-

{% trans "required field." %}

-
- {% if form.errors %} -
- -

{% trans "The form contains errors and cannot be submitted, please check the fields highlighted in red." %}

-
- {% endif %} - {% if form.non_field_errors %} -
- - {% for error in form.non_field_errors %} -

{{ error }}

- {% endfor %} -
- {% endif %} -
{% csrf_token %} - {% include "plugins/form_snippet.html" %} -
- -
-
- - -{% endblock %} diff --git a/qgis-app/plugins/templates/plugins/plugin_list.html b/qgis-app/plugins/templates/plugins/plugin_list.html deleted file mode 100644 index c0457a35..00000000 --- a/qgis-app/plugins/templates/plugins/plugin_list.html +++ /dev/null @@ -1,183 +0,0 @@ -{% extends 'plugins/plugin_base.html' %}{% load i18n bootstrap_pagination humanize static sort_anchor range_filter thumbnail %} -{% load local_timezone %} -{% load plugin_utils %} -{% block extrajs %} - - -{% endblock %} -{% block content %} -

{% if title %}{{title}}{% else %}{% trans "All plugins" %}{% endif %}

- {# Filtered views menu #} - {% if object_list.count %} -
- {% blocktrans with records_count=page_obj.paginator.count %}{{ records_count }} records found{% endblocktrans %} —  - {% trans "Click to toggle descriptions." %} -
- - - - - - - {% if not user.is_anonymous %}{% endif %} - - - - - - - - - {% if user.is_authenticated %}{% endif %} - - - - {% for object in object_list %} - - - - {% if not user.is_anonymous %}{% endif %} - - - {% if object.author %} - - {% endif %} - - - - - - {% if user.is_authenticated %}{% if user in object.editors or user.is_staff %}{% endif %} - - - - - - {% endfor %} - -
 {% anchor name %}{% trans {% anchor featured %}{% anchor downloads %}{% anchor author "Author" %}{% anchor latest_version_date "Latest Plugin Version" %}{% anchor created_on "Created on" %}{% anchor average_vote "Stars (votes)" %}{% trans "Stable" %}{% trans "Exp." %}{% trans "Manage" %}
- {% if object.icon and object.icon.file and object.icon|is_image_valid %} - {% with image_extension=object.icon.name|file_extension %} - {% if image_extension == 'svg' %} - {% trans - {% else %} - {% thumbnail object.icon "24x24" format="PNG" as im %} - {% trans - {% endthumbnail %} - {% endif %} - {% endwith %} - {% else %} - {% trans - {% endif %} - {{ object.name }}{% if object.approved %}{% else %}—{% endif %}{% if object.featured%}{% else %}—{% endif %}{{ object.downloads }}{{ object.author }}{{ object.latest_version_date|local_timezone:"SHORT_NATURAL_DAY" }}{{ object.created_on|local_timezone:"SHORT" }}
({{ object.rating_votes }})
{% if object.stable %}{{ object.stable.version }}{% else %}—{% endif %}{% if object.experimental %}{{ object.experimental.version }}{% else %}—{% endif %} - {% else %}{% endif %}
- -
- - {% trans "Deprecated plugins are printed in red." %} -
- {% else %} - {% block plugins_message %} -
- - {% trans "This list is empty!" %} -
- {% endblock %} - {% endif %} - -{% endblock %} diff --git a/qgis-app/plugins/templates/plugins/plugin_list_my.html b/qgis-app/plugins/templates/plugins/plugin_list_my.html deleted file mode 100644 index 95fd2948..00000000 --- a/qgis-app/plugins/templates/plugins/plugin_list_my.html +++ /dev/null @@ -1,7 +0,0 @@ -{% extends 'plugins/plugin_list.html' %}{% load i18n %} - -{% block plugins_message %} - {% if not object_list.count %} -

{% trans "You have not uploaded any plugin yet:" %} {% trans "Create a new plugin" %}

- {% endif %} -{% endblock %} diff --git a/qgis-app/plugins/templates/plugins/plugin_permission_deny.html b/qgis-app/plugins/templates/plugins/plugin_permission_deny.html deleted file mode 100644 index 3602935f..00000000 --- a/qgis-app/plugins/templates/plugins/plugin_permission_deny.html +++ /dev/null @@ -1,4 +0,0 @@ -{% extends 'plugins/plugin_base.html' %}{% load i18n %} -{% block content %} -
{% trans "You cannot modify this plugin." %}
-{% endblock %} diff --git a/qgis-app/plugins/templates/plugins/plugin_token_delete_confirm.html b/qgis-app/plugins/templates/plugins/plugin_token_delete_confirm.html deleted file mode 100644 index 3cebe239..00000000 --- a/qgis-app/plugins/templates/plugins/plugin_token_delete_confirm.html +++ /dev/null @@ -1,9 +0,0 @@ -{% extends 'plugins/plugin_base.html' %}{% load i18n %} -{% block content %} -

Delete token of "{{ username }}"

-
{% csrf_token %} -

{% trans "You asked to delete a token.
The token will be permanently deleted and this action cannot be undone.
Please confirm." %}

-

{% trans "Cancel" %}

-
- -{% endblock %} diff --git a/qgis-app/plugins/templates/plugins/plugin_token_detail.html b/qgis-app/plugins/templates/plugins/plugin_token_detail.html deleted file mode 100644 index 1ab1101f..00000000 --- a/qgis-app/plugins/templates/plugins/plugin_token_detail.html +++ /dev/null @@ -1,114 +0,0 @@ -{% extends 'plugins/plugin_base.html' %}{% load i18n %} -{% load local_timezone %} -{% block content %} -

{% trans "Token for" %} {{ plugin.name }}

-
- - To enhance the security of the plugin token, - it will be displayed only once. Please ensure - to save it in a secure location. If the token - is lost, you can generate a new one at any time. -
-
-
{% trans "User"%}
-
- {{ object.user }} -
-
{% trans "Jti"%}
-
- {{object.jti}} -
-
{% trans "Created at"%}
-
- {{ object.created_at|local_timezone }} -
-
{% trans "Expires at"%}
-
- {{ object.expires_at|local_timezone }} -
-
{% trans "Access token"%}
-
- -
- -
- -
- -
-
- {% trans "Back to the list" %} - {% trans "Edit description" %} -
-{% endblock %} -{% block extracss %} -{{ block.super }} - -{% endblock %} - -{% block extrajs %} - -{% endblock %} \ No newline at end of file diff --git a/qgis-app/plugins/templates/plugins/plugin_token_form.html b/qgis-app/plugins/templates/plugins/plugin_token_form.html deleted file mode 100644 index c3a4908a..00000000 --- a/qgis-app/plugins/templates/plugins/plugin_token_form.html +++ /dev/null @@ -1,26 +0,0 @@ -{% extends 'plugins/plugin_base.html' %}{% load i18n %} -{% load local_timezone %} -{% block content %} -

{% trans "Edit token description " %} {{ token.jti }}

- -{% if form.errors %} -
- -

{% trans "The form contains errors and cannot be submitted, please check the fields highlighted in red." %}

-
-{% endif %} -{% if form.non_field_errors %} -
- - {% for error in form.non_field_errors %} -

{{ error }}

- {% endfor %} -
-{% endif %} -
{% csrf_token %} - {% include "plugins/form_snippet.html" %} -
- -
-
-{% endblock %} \ No newline at end of file diff --git a/qgis-app/plugins/templates/plugins/plugin_token_invalid_or_expired.html b/qgis-app/plugins/templates/plugins/plugin_token_invalid_or_expired.html deleted file mode 100644 index 3f6b286a..00000000 --- a/qgis-app/plugins/templates/plugins/plugin_token_invalid_or_expired.html +++ /dev/null @@ -1,4 +0,0 @@ -{% extends 'plugins/plugin_base.html' %}{% load i18n %} -{% block content %} -
{% trans "Token is invalid or expired." %}
-{% endblock %} diff --git a/qgis-app/plugins/templates/plugins/plugin_token_list.html b/qgis-app/plugins/templates/plugins/plugin_token_list.html deleted file mode 100644 index 530042b4..00000000 --- a/qgis-app/plugins/templates/plugins/plugin_token_list.html +++ /dev/null @@ -1,77 +0,0 @@ -{% extends 'plugins/plugin_base.html' %}{% load i18n %} -{% load local_timezone %} -{% block content %} -

{% trans "Tokens for" %} {{ plugin.name }}

-
{% csrf_token %} -
-

- -

-
-
-{% if object_list.count %} -
- - - - - - - - - - - - - {% for plugin_token in object_list %} - - - - - - - - - {% endfor %} - -
{% trans "User" %}{% trans "Description" %}{% trans "Jti" %}{% trans "Created at" %}{% trans "Last used at" %}{% trans "Manage" %}
{{ plugin_token.token.user }}{{ plugin_token.description|default:"-" }} - - {{ plugin_token.token.jti }} - - {{ plugin_token.token.created_at|local_timezone }}{{ plugin_token.last_used_on|default:"-"|local_timezone }} -   - - -
-
-{% else %} -
- - {% trans "This list is empty!" %} -
-{% endif %} - -{% endblock %} - -{% block extracss %} -{{ block.super }} - -{% endblock %} \ No newline at end of file diff --git a/qgis-app/plugins/templates/plugins/plugin_token_permission_deny.html b/qgis-app/plugins/templates/plugins/plugin_token_permission_deny.html deleted file mode 100644 index 7850ee82..00000000 --- a/qgis-app/plugins/templates/plugins/plugin_token_permission_deny.html +++ /dev/null @@ -1,4 +0,0 @@ -{% extends 'plugins/plugin_base.html' %}{% load i18n %} -{% block content %} -
{% trans "You cannot see tokens for this plugin." %}
-{% endblock %} diff --git a/qgis-app/plugins/templates/plugins/plugin_upload.html b/qgis-app/plugins/templates/plugins/plugin_upload.html deleted file mode 100644 index e2b0f67f..00000000 --- a/qgis-app/plugins/templates/plugins/plugin_upload.html +++ /dev/null @@ -1,45 +0,0 @@ -{% extends 'plugins/plugin_base.html' %}{% load i18n %} -{% block content %} -

{% trans "Upload a plugin" %}

-

{% trans "To upload a new plugin or update an existing one, you can specify the zipped file in this form." %}

-

{% trans "Alternatively, to update an existing plugin, you can also open the plugin's details view and add a new version from there." %}

- {% if form.non_field_errors %} -
- - {% for error in form.non_field_errors %} -

{{ error }}

- {% endfor %} -
- {% endif %} -
{% csrf_token %} - {% include "plugins/form_snippet.html" %} -
- - {% blocktrans %} - Please note that by uploading a plugin to the official QGIS plugin repository, - you agree that we will use your email to contact you. We will only contact - you for matters relating to the management of plugins and will not make - your email available to third parties for marketing purposes. - {% endblocktrans %} -
-
- - {% blocktrans %} - By uploading your plugin to the QGIS plugin repository, - you agree to keep your email address current and to be - responsive to any correspondence we may send you - regarding the management of your plugin. We require - this in order to be able to provide our users assurance - that the plugins we host are well maintained and will be - fixed should serious issues arise during their use. - We reserve the right to hide or remove plugins in cases - where the plugin author is not contactable and there - are issues reported about a plugin. - {% endblocktrans %} -
-
- -
-
- -{% endblock %} diff --git a/qgis-app/plugins/templates/plugins/plugins.xml b/qgis-app/plugins/templates/plugins/plugins.xml deleted file mode 100644 index cf28e89f..00000000 --- a/qgis-app/plugins/templates/plugins/plugins.xml +++ /dev/null @@ -1,30 +0,0 @@ -{% load static %}{% load local_timezone %} - - - {% for version in object_list %} - - {% if version.plugin.about %}{% endif %} - {{ version.version }} - {{ version.is_trusted }} - {{ version.min_qg_version }} - {{ version.max_qg_version }} - - {{ version.download_file_name }} - {% if version.plugin.icon %}{{ version.plugin.icon.url }}{% endif %} - - {% if request.is_secure %}https{% else %}http{% endif %}://{{ request.get_host }}{{ version.get_download_url }} - - {{ version.plugin.created_on|local_timezone:"WITH-UTC" }} - {{ version.created_on|local_timezone:"WITH-UTC" }} - {% if version.experimental %}True{% else%}False{% endif %} - {{ version.plugin.deprecated }} - - - - {{version.plugin.downloads}} - {{version.plugin.avg_vote}} - {{version.plugin.rating_votes}} - {{version.plugin.external_deps }} - {% if version.plugin.server %}True{% else%}False{% endif %} - {% endfor %} - diff --git a/qgis-app/plugins/templates/plugins/plugins_tagcloud_include.html b/qgis-app/plugins/templates/plugins/plugins_tagcloud_include.html deleted file mode 100644 index 6a9e7fd1..00000000 --- a/qgis-app/plugins/templates/plugins/plugins_tagcloud_include.html +++ /dev/null @@ -1,10 +0,0 @@ -{% load plugins_tagcloud %} - -{% get_plugins_tagcloud as tags %} - -
-{% for tag in tags %}{% if tag.slug %} -{{tag}} -{% endif %}{% endfor %} -
-
diff --git a/qgis-app/plugins/templates/plugins/plugins_tagcloud_modal_include.html b/qgis-app/plugins/templates/plugins/plugins_tagcloud_modal_include.html deleted file mode 100644 index 36898615..00000000 --- a/qgis-app/plugins/templates/plugins/plugins_tagcloud_modal_include.html +++ /dev/null @@ -1,21 +0,0 @@ -{% load i18n plugins_tagcloud %} - -{% trans "Plugin Tags" as tags_title %} - - - - {{ tags_title }} - - - - diff --git a/qgis-app/plugins/templates/plugins/tagcloud_include.html b/qgis-app/plugins/templates/plugins/tagcloud_include.html deleted file mode 100644 index 35b35bbd..00000000 --- a/qgis-app/plugins/templates/plugins/tagcloud_include.html +++ /dev/null @@ -1,14 +0,0 @@ -{% load plugins_tagcloud %} - -{% if forvar %} -{% get_tagcloud as tags for forvar %} -{% else %} -{% get_tagcloud as tags %} -{% endif %} - -
-{% for tag in tags %}{% if tag.slug %} -{{tag}} -{% endif %}{% endfor %} -
-
diff --git a/qgis-app/plugins/templates/plugins/user.html b/qgis-app/plugins/templates/plugins/user.html deleted file mode 100644 index 0c370502..00000000 --- a/qgis-app/plugins/templates/plugins/user.html +++ /dev/null @@ -1,29 +0,0 @@ -{% extends 'plugins/plugin_list.html' %}{% load i18n %} - -{% block content %} - - {% if user.is_staff %} -

{% trans "User Details of: " %} {{ plugin_user.username }}

-
    - {% if plugin_user.first_name %}
  • {% trans "First name: " %} {{ plugin_user.first_name }}
  • {% endif %} - {% if plugin_user.last_name %}
  • {% trans "Last name: " %} {{ plugin_user.last_name }}
  • {% endif %} - {% if plugin_user.email %} -
  • {% trans "Email: " %} {{ plugin_user.email }}
  • {% endif %} -
-
{% csrf_token %} -
- {% if plugin_user.is_active %} - {% else %} - - {% endif %} - {% if plugin_user.is_active %} - {% if not user_is_trusted %}{% else %} - {% endif %} - {% endif %} -
-
- {% endif %} - - {{ block.super }} - -{% endblock %} diff --git a/qgis-app/plugins/templates/plugins/version_delete_confirm.html b/qgis-app/plugins/templates/plugins/version_delete_confirm.html deleted file mode 100644 index 00c58b2a..00000000 --- a/qgis-app/plugins/templates/plugins/version_delete_confirm.html +++ /dev/null @@ -1,9 +0,0 @@ -{% extends 'plugins/plugin_base.html' %}{% load i18n %} -{% block content %} -

{% blocktrans with version.version as version and plugin.name as plugin_name %}Delete version "{{ version }}" of "{{ plugin_name }}"{% endblocktrans %}

-
{% csrf_token %} -

{% trans "You asked to delete one version.
The version will be permanently deleted and this action cannot be undone.
Please confirm." %}

-

{% trans "Cancel" %}

-
- -{% endblock %} diff --git a/qgis-app/plugins/templates/plugins/version_detail.html b/qgis-app/plugins/templates/plugins/version_detail.html deleted file mode 100644 index b9cd4874..00000000 --- a/qgis-app/plugins/templates/plugins/version_detail.html +++ /dev/null @@ -1,77 +0,0 @@ -{% extends 'plugins/plugin_base.html' %}{% load i18n %} -{% load local_timezone %} -{% block content %} -

{% trans "Version" %}: {{ version }}

- - - {% if not version.created_by.is_active and not version.is_from_token %} -
- {% trans "The plugin author has been blocked." %} -
- {% endif %} - - - - -
-
-
- {% if version.changelog %}
{% trans "Changelog" %}
{{ version.changelog|wordwrap:80 }}
{% endif %} -
{% trans "Approved" %}
{{ version.approved|yesno }}
-
{% trans "Author" %}
- {% if version.is_from_token %} - Token {{ version.token.description|default:"" }} - {% else %} - {{ version.created_by }} - {% endif %} -
-
{% trans "Uploaded" %}
{{ version.created_on|local_timezone }}
-
{% trans "Minimum QGIS version" %}
{{ version.min_qg_version }}
-
{% trans "Maximum QGIS version" %}
{{ version.max_qg_version }}
-
{% trans "External dependencies (PIP install string)" %}
{{ version.external_deps }}
-
{% trans "Experimental" %}
{{ version.experimental|yesno }}
-
-
- -
-

{% trans "Version management"%}

- {% if user.is_staff or user in version.plugin.editors %} - {% trans "Edit" %} - {% trans "Delete" %} - {% endif %} - {% trans "Plugin details" %} - - {% if user.is_staff or user in version.plugin.approvers %} -

{% trans "Version approval"%}

-
{% csrf_token %} - {% if not version.approved %} {% else %}{% endif %} -
- {% if user.is_staff %} -

{% trans "Author management"%}

-
{% csrf_token %} - {% if version.created_by.is_active %} - {% else %} - - {% endif %} - {% if version.created_by.is_active %} - {% if not version.plugin.trusted %}{% else %} - {% endif %} - {% endif %} -
- {% endif %} -
- {% endif %} -
- - - -{% endblock %} diff --git a/qgis-app/plugins/templates/plugins/version_form.html b/qgis-app/plugins/templates/plugins/version_form.html deleted file mode 100644 index b928d645..00000000 --- a/qgis-app/plugins/templates/plugins/version_form.html +++ /dev/null @@ -1,23 +0,0 @@ -{% extends 'plugins/plugin_base.html' %}{% load i18n %} -{% block content %} -

{{ form_title }} {{ version }}

-
-

{% trans "required field." %}

-
- {% if form.non_field_errors %} -
- - {% for error in form.non_field_errors %} -

{{ error }}

- {% endfor %} -
- {% endif %} - -
{% csrf_token %} - {% include "plugins/form_snippet.html" %} -
- -
-
- -{% endblock %} diff --git a/qgis-app/plugins/templates/plugins/version_permission_deny.html b/qgis-app/plugins/templates/plugins/version_permission_deny.html deleted file mode 100644 index 81e4c94b..00000000 --- a/qgis-app/plugins/templates/plugins/version_permission_deny.html +++ /dev/null @@ -1,4 +0,0 @@ -{% extends 'plugins/plugin_base.html' %}{% load i18n %} -{% block content %} -
{% trans "You cannot create or modify versions of this plugin." %}
-{% endblock %} diff --git a/qgis-app/plugins/templatetags/__init__.py b/qgis-app/plugins/templatetags/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/qgis-app/plugins/templatetags/local_timezone.py b/qgis-app/plugins/templatetags/local_timezone.py deleted file mode 100644 index 835e7c82..00000000 --- a/qgis-app/plugins/templatetags/local_timezone.py +++ /dev/null @@ -1,22 +0,0 @@ -import pytz -from django import template -from django.utils.safestring import mark_safe - -register = template.Library() - - -@register.filter(name="local_timezone", is_safe=True) -def local_timezone(date, args="LONG"): - try: - utcdate = date.astimezone(pytz.utc).isoformat() - if args and str(args) == "SHORT": - result = '%s' % (utcdate,) - elif args and str(args) == "SHORT_NATURAL_DAY": - result = '%s' % (utcdate,) - elif args and str(args) == "WITH-UTC": - result = utcdate - else: - result = '%s' % (utcdate,) - except AttributeError: - result = date - return mark_safe(result) diff --git a/qgis-app/plugins/templatetags/plugin_utils.py b/qgis-app/plugins/templatetags/plugin_utils.py deleted file mode 100755 index 3daf1722..00000000 --- a/qgis-app/plugins/templatetags/plugin_utils.py +++ /dev/null @@ -1,62 +0,0 @@ -from django import template -from PIL import Image, UnidentifiedImageError -import xml.etree.ElementTree as ET - -register = template.Library() - - -@register.filter("klass") -def klass(ob): - return ob.__class__.__name__ - - -@register.simple_tag(takes_context=True) -def plugin_title(context): - """Returns plugin name for title""" - title = "" - - if "title" in context: - title = context["title"] - if "plugin" in context: - title = context["plugin"].name - if "version" in context: - title = "{plugin} {version}".format( - plugin=context["version"].plugin.name, version=context["version"].version - ) - if "page_title" in context: - title = context["page_title"] - return title - -@register.filter -def file_extension(value): - return value.split('.')[-1].lower() - -@register.filter -def is_image_valid(image): - if not image: - return False - # Check if the file is an SVG by extension - if image.path.lower().endswith('.svg'): - return _validate_svg(image.path) - return _validate_image(image.path) - - -def _validate_svg(file_path): - try: - # Parse the SVG file to ensure it's well-formed XML - ET.parse(file_path) - return True - except (ET.ParseError, FileNotFoundError): - return False - -def _validate_image(file_path): - try: - img = Image.open(file_path) - img.verify() - return True - except (FileNotFoundError, UnidentifiedImageError): - return False - -@register.filter -def feedbacks_not_completed(feedbacks): - return feedbacks.filter(is_completed=False) diff --git a/qgis-app/plugins/templatetags/plugins_tagcloud.py b/qgis-app/plugins/templatetags/plugins_tagcloud.py deleted file mode 100644 index 6a070c6a..00000000 --- a/qgis-app/plugins/templatetags/plugins_tagcloud.py +++ /dev/null @@ -1,106 +0,0 @@ -""" -ABP: patched version of django-taggit-templatetags to deal with -unpublished plugins: returns only approved_objects - -""" - -from django import template -from django.conf import settings as django_settings -from django.core.exceptions import FieldError -from django.db import models -from django.db.models import Count -from plugins.models import Plugin -from taggit import VERSION as TAGGIT_VERSION -from taggit.managers import TaggableManager -from taggit.models import Tag, TaggedItem -from taggit_templatetags import settings -from templatetag_sugar.parser import Constant, Model, Name, Optional, Variable -from templatetag_sugar.register import tag - -TAGCLOUD_COUNT_GTE = getattr(django_settings, "TAGCLOUD_COUNT_GTE", None) -T_MAX = getattr(settings, "TAGCLOUD_MAX", 6.0) -T_MIN = getattr(settings, "TAGCLOUD_MIN", 1.0) - -register = template.Library() - - -def get_queryset(): - applabel = "plugins" - model = "plugin" - # filter tagged items - queryset = TaggedItem.objects.filter(content_type__app_label=applabel.lower()) - queryset = queryset.filter( - content_type__model=model.lower(), - object_id__in=Plugin.approved_objects.values_list("id", flat=True), - ) - - # get tags - tag_ids = queryset.values_list("tag_id", flat=True) - queryset = Tag.objects.filter(id__in=tag_ids) - - # Retain compatibility with older versions of Django taggit - # a version check (for example taggit.VERSION <= (0,8,0)) does NOT - # work because of the version (0,8,0) of the current dev version of django-taggit - try: - qs = queryset.annotate(num_times=Count("taggeditem_items")) - except FieldError: - qs = queryset.annotate(num_times=Count("taggit_taggeditem_items")) - if TAGCLOUD_COUNT_GTE: - qs = qs.filter(num_times__gte=TAGCLOUD_COUNT_GTE) - return qs - - -def get_weight_fun(t_min, t_max, f_min, f_max): - def weight_fun(f_i, t_min=t_min, t_max=t_max, f_min=f_min, f_max=f_max): - # Prevent a division by zero here, found to occur under some - # pathological but nevertheless actually occurring circumstances. - if f_max == f_min: - mult_fac = 1.0 - else: - mult_fac = float(t_max - t_min) / float(f_max - f_min) - - return t_max - (f_max - f_i) * mult_fac - - return weight_fun - - -@tag(register, [Constant("as"), Name()]) -def get_plugins_taglist(context, asvar): - queryset = get_queryset() - queryset = queryset.order_by("-num_times") - context[asvar] = queryset - return "" - - -@tag(register, [Constant("as"), Name()]) -def get_plugins_tagcloud(context, asvar): - queryset = get_queryset() - num_times = queryset.values_list("num_times", flat=True) - if len(num_times) == 0: - context[asvar] = queryset - return "" - weight_fun = get_weight_fun(T_MIN, T_MAX, min(num_times), max(num_times)) - queryset = queryset.order_by("name") - for tag in queryset: - tag.weight = weight_fun(tag.num_times) - context[asvar] = queryset - return "" - - -def include_plugins_tagcloud(forvar=None): - pass - -def include_plugins_tagcloud_modal(forvar=None): - pass - -def include_plugins_taglist(forvar=None): - pass - - -register.inclusion_tag("plugins/plugins_taglist_include.html")(include_plugins_taglist) -register.inclusion_tag("plugins/plugins_tagcloud_include.html")( - include_plugins_tagcloud -) -register.inclusion_tag("plugins/plugins_tagcloud_modal_include.html")( - include_plugins_tagcloud_modal -) diff --git a/qgis-app/plugins/templatetags/range_filter.py b/qgis-app/plugins/templatetags/range_filter.py deleted file mode 100755 index c1090b59..00000000 --- a/qgis-app/plugins/templatetags/range_filter.py +++ /dev/null @@ -1,27 +0,0 @@ -from django.template import Library - -register = Library() - - -@register.filter -def get_range(value): - """ - Filter - returns a list containing range made from given value - Usage (in template): - -
    {% for i in 3|get_range %} -
  • {{ i }}. Do something
  • - {% endfor %}
- - Results with the HTML: -
    -
  • 0. Do something
  • -
  • 1. Do something
  • -
  • 2. Do something
  • -
- - Instead of 3 one may use the variable set in the views - """ - if not value: - value = 0 - return range(value) diff --git a/qgis-app/plugins/templatetags/smart_paginate.py b/qgis-app/plugins/templatetags/smart_paginate.py deleted file mode 100644 index 9c706a40..00000000 --- a/qgis-app/plugins/templatetags/smart_paginate.py +++ /dev/null @@ -1,257 +0,0 @@ -# Smmarter paginator -# Fix an error in try/except block and adds per_page - - -try: - set -except NameError: - from sets import Set as set - -from django import template -from django.conf import settings -from django.core.paginator import InvalidPage, Paginator -from django.http import Http404 - -register = template.Library() - -DEFAULT_PAGINATION = getattr(settings, "PAGINATION_DEFAULT_PAGINATION", 20) -DEFAULT_WINDOW = getattr(settings, "PAGINATION_DEFAULT_WINDOW", 4) -DEFAULT_ORPHANS = getattr(settings, "PAGINATION_DEFAULT_ORPHANS", 0) -INVALID_PAGE_RAISES_404 = getattr(settings, "PAGINATION_INVALID_PAGE_RAISES_404", False) - - -def do_autopaginate(parser, token): - """ - Splits the arguments to the autopaginate tag and formats them correctly. - """ - split = token.split_contents() - as_index = None - context_var = None - for i, bit in enumerate(split): - if bit == "as": - as_index = i - break - if as_index is not None: - try: - context_var = split[as_index + 1] - except IndexError: - raise template.TemplateSyntaxError( - "Context variable assignment " - + "must take the form of {%% %r object.example_set.all ... as " - + "context_var_name %%}" % split[0] - ) - del split[as_index : as_index + 2] - if len(split) == 2: - return AutoPaginateNode(split[1]) - elif len(split) == 3: - return AutoPaginateNode(split[1], paginate_by=split[2], context_var=context_var) - elif len(split) == 4: - try: - orphans = int(split[3]) - except ValueError: - raise template.TemplateSyntaxError( - u"Got %s, but expected integer." % split[3] - ) - return AutoPaginateNode( - split[1], paginate_by=split[2], orphans=orphans, context_var=context_var - ) - else: - raise template.TemplateSyntaxError( - "%r tag takes one required " - + "argument and one optional argument" % split[0] - ) - - -class AutoPaginateNode(template.Node): - """ - Emits the required objects to allow for Digg-style pagination. - - First, it looks in the current context for the variable specified, and using - that object, it emits a simple ``Paginator`` and the current page object - into the context names ``paginator`` and ``page_obj``, respectively. - - It will then replace the variable specified with only the objects for the - current page. - - .. note:: - - It is recommended to use *{% paginate %}* after using the autopaginate - tag. If you choose not to use *{% paginate %}*, make sure to display the - list of available pages, or else the application may seem to be buggy. - """ - - def __init__( - self, - queryset_var, - paginate_by=DEFAULT_PAGINATION, - orphans=DEFAULT_ORPHANS, - context_var=None, - ): - self.queryset_var = template.Variable(queryset_var) - if isinstance(paginate_by, int): - self.paginate_by = paginate_by - else: - self.paginate_by = template.Variable(paginate_by) - self.orphans = orphans - self.context_var = context_var - - def render(self, context): - key = self.queryset_var.var - value = self.queryset_var.resolve(context) - if isinstance(self.paginate_by, int): - paginate_by = self.paginate_by - else: - paginate_by = self.paginate_by.resolve(context) - paginator = Paginator(value, paginate_by, self.orphans) - try: - page_obj = paginator.page(context["request"].page) - except InvalidPage: - if INVALID_PAGE_RAISES_404: - raise Http404( - "Invalid page requested. If DEBUG were set to " - + "False, an HTTP 404 page would have been shown instead." - ) - context[key] = [] - context["invalid_page"] = True - return u"" - if self.context_var is not None: - context[self.context_var] = page_obj.object_list - else: - context[key] = page_obj.object_list - context["paginator"] = paginator - context["page_obj"] = page_obj - return u"" - - -def smart_paginate(context, window=DEFAULT_WINDOW, hashtag=""): - """ - Renders the ``pagination/pagination.html`` template, resulting in a - Digg-like display of the available pages, given the current page. If there - are too many pages to be displayed before and after the current page, then - elipses will be used to indicate the undisplayed gap between page numbers. - - Requires one argument, ``context``, which should be a dictionary-like data - structure and must contain the following keys: - - ``paginator`` - A ``Paginator`` or ``QuerySetPaginator`` object. - - ``page_obj`` - This should be the result of calling the page method on the - aforementioned ``Paginator`` or ``QuerySetPaginator`` object, given - the current page. - - This same ``context`` dictionary-like data structure may also include: - - ``getvars`` - A dictionary of all of the **GET** parameters in the current request. - This is useful to maintain certain types of state, even when requesting - a different page. - """ - try: - paginator = context["paginator"] - page_obj = context["page_obj"] - page_range = paginator.page_range - # Calculate the record range in the current page for display. - records = {"first": 1 + (page_obj.number - 1) * paginator.per_page} - records["last"] = records["first"] + paginator.per_page - 1 - if records["last"] + paginator.orphans >= paginator.count: - records["last"] = paginator.count - # First and last are simply the first *n* pages and the last *n* pages, - # where *n* is the current window size. - first = set(page_range[:window]) - last = set(page_range[-window:]) - # Now we look around our current page, making sure that we don't wrap - # around. - current_start = page_obj.number - 1 - window - if current_start < 0: - current_start = 0 - current_end = page_obj.number - 1 + window - if current_end < 0: - current_end = 0 - current = set(page_range[current_start:current_end]) - pages = [] - # If there's no overlap between the first set of pages and the current - # set of pages, then there's a possible need for elusion. - if len(first.intersection(current)) == 0: - first_list = list(first) - first_list.sort() - second_list = list(current) - second_list.sort() - pages.extend(first_list) - diff = second_list[0] - first_list[-1] - # If there is a gap of two, between the last page of the first - # set and the first page of the current set, then we're missing a - # page. - if diff == 2: - pages.append(second_list[0] - 1) - # If the difference is just one, then there's nothing to be done, - # as the pages need no elusion and are correct. - elif diff == 1: - pass - # Otherwise, there's a bigger gap which needs to be signaled for - # elusion, by pushing a None value to the page list. - else: - pages.append(None) - pages.extend(second_list) - else: - unioned = list(first.union(current)) - unioned.sort() - pages.extend(unioned) - # If there's no overlap between the current set of pages and the last - # set of pages, then there's a possible need for elusion. - if len(current.intersection(last)) == 0: - second_list = list(last) - second_list.sort() - diff = second_list[0] - pages[-1] - # If there is a gap of two, between the last page of the current - # set and the first page of the last set, then we're missing a - # page. - if diff == 2: - pages.append(second_list[0] - 1) - # If the difference is just one, then there's nothing to be done, - # as the pages need no elusion and are correct. - elif diff == 1: - pass - # Otherwise, there's a bigger gap which needs to be signaled for - # elusion, by pushing a None value to the page list. - else: - pages.append(None) - pages.extend(second_list) - else: - differenced = list(last.difference(current)) - differenced.sort() - pages.extend(differenced) - - # ABP: per_page - if paginator.count > DEFAULT_PAGINATION: - per_page_list = range(0, paginator.count + 1, DEFAULT_PAGINATION) - per_page_list = per_page_list[1:] - per_page_list.append(paginator.count) - else: - per_page_list = [] - to_return = { - "MEDIA_URL": settings.MEDIA_URL, - "pages": pages, - "records": records, - "page_obj": page_obj, - "per_page_list": per_page_list, - "paginator": paginator, - "hashtag": hashtag, - "is_paginated": paginator.count > paginator.per_page, - } - if "request" in context: - getvars = context["request"].GET.copy() - if "page" in getvars: - del getvars["page"] - if len(getvars.keys()) > 0: - to_return["getvars"] = "&%s" % getvars.urlencode() - else: - to_return["getvars"] = "" - return to_return - # ABP: turned exceptions into a tuple - except (KeyError, AttributeError): - return {} - - -register.inclusion_tag("plugins/pagination.html", takes_context=True)(smart_paginate) diff --git a/qgis-app/plugins/tests/HelloWorld/1.0-spaced/Hello World/HelloWorld.py b/qgis-app/plugins/tests/HelloWorld/1.0-spaced/Hello World/HelloWorld.py deleted file mode 100644 index 45bdac04..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.0-spaced/Hello World/HelloWorld.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -class HelloWorld: - def __init__(self, iface): - # Save reference to the QGIS interface - self.iface = iface - self.canvas = iface.mapCanvas() - - def initGui(self): - # Create action that will start plugin - self.action = QAction( - QIcon(":/plugins/"), "&HelloWorld", self.iface.mainWindow() - ) - # connect the action to the run method - QObject.connect(self.action, SIGNAL("activated()"), self.hello_world) - - # Add toolbar button and menu item - self.iface.addPluginToMenu("HelloWorld", self.action) - - def unload(self): - # Remove the plugin menu item and icon - self.iface.removePluginMenu("HelloWorld", self.action) - - # run - def hello_world(self): - QMessageBox.information( - self.iface.mainWindow(), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - ) - return - - -if __name__ == "__main__": - pass diff --git a/qgis-app/plugins/tests/HelloWorld/1.0-spaced/Hello World/__init__.py b/qgis-app/plugins/tests/HelloWorld/1.0-spaced/Hello World/__init__.py deleted file mode 100644 index 0e3e06b7..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.0-spaced/Hello World/__init__.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This script initializes the plugin, making it known to QGIS. -""" - - -def name(): - return "Hello World" - - -def description(): - return "HelloWorld" - - -def version(): - return "Version 1.0" - - -def qgisMinimumVersion(): - return "1.0" - - -def classFactory(iface): - from HelloWorld import HelloWorld - - return HelloWorld(iface) - - -def icon(): - """ - Icon - """ - return "icon.png" diff --git a/qgis-app/plugins/tests/HelloWorld/1.0-spaced/Hello World/icon.png b/qgis-app/plugins/tests/HelloWorld/1.0-spaced/Hello World/icon.png deleted file mode 100644 index c043d921d34a37a211b8a1aa8acec81febb32327..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1304 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 O0000 - very - very - very - very long multiline changelog - - -tags=wkt,raster,hello world - -tracker=http://bugs.itopen.it -homepage=http://www.itopen.it -repository=http://www.itopen.it/repo - -icon=icon.png - -experimental=True -; test numeric value flag -deprecated=1 diff --git a/qgis-app/plugins/tests/HelloWorld/1.2-md-txt-incomplete/HelloWorld/HelloWorld.py b/qgis-app/plugins/tests/HelloWorld/1.2-md-txt-incomplete/HelloWorld/HelloWorld.py deleted file mode 100644 index 45bdac04..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.2-md-txt-incomplete/HelloWorld/HelloWorld.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -class HelloWorld: - def __init__(self, iface): - # Save reference to the QGIS interface - self.iface = iface - self.canvas = iface.mapCanvas() - - def initGui(self): - # Create action that will start plugin - self.action = QAction( - QIcon(":/plugins/"), "&HelloWorld", self.iface.mainWindow() - ) - # connect the action to the run method - QObject.connect(self.action, SIGNAL("activated()"), self.hello_world) - - # Add toolbar button and menu item - self.iface.addPluginToMenu("HelloWorld", self.action) - - def unload(self): - # Remove the plugin menu item and icon - self.iface.removePluginMenu("HelloWorld", self.action) - - # run - def hello_world(self): - QMessageBox.information( - self.iface.mainWindow(), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - ) - return - - -if __name__ == "__main__": - pass diff --git a/qgis-app/plugins/tests/HelloWorld/1.2-md-txt-incomplete/HelloWorld/__init__.py b/qgis-app/plugins/tests/HelloWorld/1.2-md-txt-incomplete/HelloWorld/__init__.py deleted file mode 100644 index f70b70c6..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.2-md-txt-incomplete/HelloWorld/__init__.py +++ /dev/null @@ -1,41 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This script initializes the plugin, making it known to QGIS. -""" - - -def name(): - return "HelloWorld" - - -def description(): - return "HelloWorld" - - -def version(): - return "Version 1.1" - - -def qgisMinimumVersion(): - return "1.0" - - -def classFactory(iface): - from HelloWorld import HelloWorld - - return HelloWorld(iface) - - -def icon(): - """ - Icon - """ - return "icon.png" - - -def author(): - return "Alessandro Secondo" - - -def email(): - return "email2@email.com" diff --git a/qgis-app/plugins/tests/HelloWorld/1.2-md-txt-incomplete/HelloWorld/icon.png b/qgis-app/plugins/tests/HelloWorld/1.2-md-txt-incomplete/HelloWorld/icon.png deleted file mode 100644 index c043d921d34a37a211b8a1aa8acec81febb32327..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1304 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 O0000 - very - very - very - very long multiline changelog - - -tags=wkt,raster,hello world - -tracker=http://bugs.itopen.it -homepage=http://www.itopen.it -repository=http://www.itopen.it/repo diff --git a/qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-md-init/HelloWorld/HelloWorld.py b/qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-md-init/HelloWorld/HelloWorld.py deleted file mode 100644 index 45bdac04..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-md-init/HelloWorld/HelloWorld.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -class HelloWorld: - def __init__(self, iface): - # Save reference to the QGIS interface - self.iface = iface - self.canvas = iface.mapCanvas() - - def initGui(self): - # Create action that will start plugin - self.action = QAction( - QIcon(":/plugins/"), "&HelloWorld", self.iface.mainWindow() - ) - # connect the action to the run method - QObject.connect(self.action, SIGNAL("activated()"), self.hello_world) - - # Add toolbar button and menu item - self.iface.addPluginToMenu("HelloWorld", self.action) - - def unload(self): - # Remove the plugin menu item and icon - self.iface.removePluginMenu("HelloWorld", self.action) - - # run - def hello_world(self): - QMessageBox.information( - self.iface.mainWindow(), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - ) - return - - -if __name__ == "__main__": - pass diff --git a/qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-md-init/HelloWorld/__init__.py b/qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-md-init/HelloWorld/__init__.py deleted file mode 100644 index 5f7dc3df..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-md-init/HelloWorld/__init__.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This script initializes the plugin, making it known to QGIS. -""" - - -def name(): - return "HelloWorld" - - -def description(): - return "HelloWorld" - - -def version(): - return "Version 1.1" - - -def qgisMinimumVersion(): - return "1.0" - - -def classFactory(iface): - from HelloWorld import HelloWorld - - return HelloWorld(iface) - - -def icon(): - """ - Icon - """ - return "icon.png" diff --git a/qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-md-init/HelloWorld/icon.png b/qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-md-init/HelloWorld/icon.png deleted file mode 100644 index c043d921d34a37a211b8a1aa8acec81febb32327..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1304 zcmV+z1?T#SP)Px#24YJ`L;y|zng9j|XT?MS000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2igN3 z6b3n(XY02B000?uMObu0Z*6U5Zgc=ca%Ew3Wn>_CX>@2HM@dakSAh-}000DYNkl%$>F~Q)j4Yu|R1Nx&Vb@QB0*x2~Z)X@BjoKR73(X8iFQ< zH(yMQK8f;R4U1ukpfn1B(pXATPzWtcnHE~6(^)%rmOIPc=UyM22f$7tp5)}qIVayQ zC*N5<0)>UDsj2_4qN-+7Nr_{L(WoyIMR_`rVD9-QJUt%I@b>_eLYIk|oKC0v^*7(@ zJleK?le>0lgVAC!!)VYWlNG?kS&WU`76$wKB7K*C-5HIB23`p4aJklQ-?{gX4O@1a zWzvLulSx>L068bCyeKMa4q9!6gQ_eoB2OOg=DUCVc0Cgg_dm-apEk|zx8K>(|H0l* zXkS8uC%~ZY6^lBzSg+VElYX_gsfwB^3xp<%nj#BEJ(N&rI-cYd3WQF#xO!pH_}E=76!i5z z$IeMyI5s-e8ois(e{1 zuUOFbpUOXJ*l6n_lgWf>)(4uB(xS_#=475zM3IvvQBY(>nhb_x3dd$(kQJ8I>B{UY z%WSl@W>zrAi=jj+J->)lxlAU-PIyJ3*sPaTMdDJaWYQl9hS+2>4N7mIRD!V5rS^D| z6?s7jJ_~;8DZ-(tffy6f>=sHQR4MyfT~(>Qq=>pda({Gq==PA1N-`a-YZ`WSZFnQ6 zYNV^gO8WhNAsP-`pEcu=JA>ccx_WtA`{phx7MYSXsJW)4iz?SP)h_@6XsW7aIi4RM zpP1As>2OC&+4EzbJKsI0kAZ;y@fDNFx@F0$wN5s}M|7Gh zj(SE%BSF89&~lP{uB*&sve{H6=QwrdB-4BGT-QvaP0lcl>ZI4(Yq1u8w5DYZ2CiN8 zsEVjCOvGn0=}SEi9!%|eclVBCCr(&?IrVe9qKMHM7tKsgL0~6hOfRP=I zhpq3w_woK4gF{kvbzO{!g@tG&%m@NIQD_N~g#v6c{+On1yBixC&C8apBuk6u5tZ|5 zDLrMPe>;DH?&&$ZC&eZ_1y^ICKs+rl<5LM@bgU0tTKcy)bwGF;2!*^l|K#{N%(in2 z1^86tMoXgzi{Y-;(();x*K1%HA7p6+^D7BFPsrJVfdIo*Rn@dp1F{(w*9Pw3 z`0>k!rl*rv|1X$4aA4a&bF&K#%Q`{Z$^hs=>hzk^X>RT9z4a9UEw3;1gBSsr0hnoe z&eDDRHXVETFz0YMZ3qS;nboV^mV*b6Gy{kM-~n&|vH+y#f#>Aa44@Q%bL-Zwi-ACJ zth{_~_36`RwgX@QL;)o7;P|scQm6_#00sa>`BR_QTpl3jvlR+`sQ&=^6)Ws|9!-e= O0000 - very - very - very - very long multiline changelog - - -tags=wkt,raster,hello world - -tracker=http://bugs.itopen.it -homepage=http://www.itopen.it -repository=http://www.itopen.it/repo diff --git a/qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-no-init/HelloWorld/HelloWorld.py b/qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-no-init/HelloWorld/HelloWorld.py deleted file mode 100644 index 45bdac04..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-no-init/HelloWorld/HelloWorld.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -class HelloWorld: - def __init__(self, iface): - # Save reference to the QGIS interface - self.iface = iface - self.canvas = iface.mapCanvas() - - def initGui(self): - # Create action that will start plugin - self.action = QAction( - QIcon(":/plugins/"), "&HelloWorld", self.iface.mainWindow() - ) - # connect the action to the run method - QObject.connect(self.action, SIGNAL("activated()"), self.hello_world) - - # Add toolbar button and menu item - self.iface.addPluginToMenu("HelloWorld", self.action) - - def unload(self): - # Remove the plugin menu item and icon - self.iface.removePluginMenu("HelloWorld", self.action) - - # run - def hello_world(self): - QMessageBox.information( - self.iface.mainWindow(), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - ) - return - - -if __name__ == "__main__": - pass diff --git a/qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-no-init/HelloWorld/__init__.py b/qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-no-init/HelloWorld/__init__.py deleted file mode 100644 index e9feb7d9..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-no-init/HelloWorld/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This script initializes the plugin, making it known to QGIS. -""" diff --git a/qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-no-init/HelloWorld/icon.png b/qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-no-init/HelloWorld/icon.png deleted file mode 100644 index c043d921d34a37a211b8a1aa8acec81febb32327..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1304 zcmV+z1?T#SP)Px#24YJ`L;y|zng9j|XT?MS000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2igN3 z6b3n(XY02B000?uMObu0Z*6U5Zgc=ca%Ew3Wn>_CX>@2HM@dakSAh-}000DYNkl%$>F~Q)j4Yu|R1Nx&Vb@QB0*x2~Z)X@BjoKR73(X8iFQ< zH(yMQK8f;R4U1ukpfn1B(pXATPzWtcnHE~6(^)%rmOIPc=UyM22f$7tp5)}qIVayQ zC*N5<0)>UDsj2_4qN-+7Nr_{L(WoyIMR_`rVD9-QJUt%I@b>_eLYIk|oKC0v^*7(@ zJleK?le>0lgVAC!!)VYWlNG?kS&WU`76$wKB7K*C-5HIB23`p4aJklQ-?{gX4O@1a zWzvLulSx>L068bCyeKMa4q9!6gQ_eoB2OOg=DUCVc0Cgg_dm-apEk|zx8K>(|H0l* zXkS8uC%~ZY6^lBzSg+VElYX_gsfwB^3xp<%nj#BEJ(N&rI-cYd3WQF#xO!pH_}E=76!i5z z$IeMyI5s-e8ois(e{1 zuUOFbpUOXJ*l6n_lgWf>)(4uB(xS_#=475zM3IvvQBY(>nhb_x3dd$(kQJ8I>B{UY z%WSl@W>zrAi=jj+J->)lxlAU-PIyJ3*sPaTMdDJaWYQl9hS+2>4N7mIRD!V5rS^D| z6?s7jJ_~;8DZ-(tffy6f>=sHQR4MyfT~(>Qq=>pda({Gq==PA1N-`a-YZ`WSZFnQ6 zYNV^gO8WhNAsP-`pEcu=JA>ccx_WtA`{phx7MYSXsJW)4iz?SP)h_@6XsW7aIi4RM zpP1As>2OC&+4EzbJKsI0kAZ;y@fDNFx@F0$wN5s}M|7Gh zj(SE%BSF89&~lP{uB*&sve{H6=QwrdB-4BGT-QvaP0lcl>ZI4(Yq1u8w5DYZ2CiN8 zsEVjCOvGn0=}SEi9!%|eclVBCCr(&?IrVe9qKMHM7tKsgL0~6hOfRP=I zhpq3w_woK4gF{kvbzO{!g@tG&%m@NIQD_N~g#v6c{+On1yBixC&C8apBuk6u5tZ|5 zDLrMPe>;DH?&&$ZC&eZ_1y^ICKs+rl<5LM@bgU0tTKcy)bwGF;2!*^l|K#{N%(in2 z1^86tMoXgzi{Y-;(();x*K1%HA7p6+^D7BFPsrJVfdIo*Rn@dp1F{(w*9Pw3 z`0>k!rl*rv|1X$4aA4a&bF&K#%Q`{Z$^hs=>hzk^X>RT9z4a9UEw3;1gBSsr0hnoe z&eDDRHXVETFz0YMZ3qS;nboV^mV*b6Gy{kM-~n&|vH+y#f#>Aa44@Q%bL-Zwi-ACJ zth{_~_36`RwgX@QL;)o7;P|scQm6_#00sa>`BR_QTpl3jvlR+`sQ&=^6)Ws|9!-e= O0000 - very - very - very - very long multiline changelog - - -tags=wkt,raster,hello world - -tracker=http://bugs.itopen.it -homepage=http://www.itopen.it -repository=http://www.itopen.it/repo diff --git a/qgis-app/plugins/tests/HelloWorld/1.2-wierdname/HelloWorld/HelloWorld.py b/qgis-app/plugins/tests/HelloWorld/1.2-wierdname/HelloWorld/HelloWorld.py deleted file mode 100644 index 45bdac04..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.2-wierdname/HelloWorld/HelloWorld.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -class HelloWorld: - def __init__(self, iface): - # Save reference to the QGIS interface - self.iface = iface - self.canvas = iface.mapCanvas() - - def initGui(self): - # Create action that will start plugin - self.action = QAction( - QIcon(":/plugins/"), "&HelloWorld", self.iface.mainWindow() - ) - # connect the action to the run method - QObject.connect(self.action, SIGNAL("activated()"), self.hello_world) - - # Add toolbar button and menu item - self.iface.addPluginToMenu("HelloWorld", self.action) - - def unload(self): - # Remove the plugin menu item and icon - self.iface.removePluginMenu("HelloWorld", self.action) - - # run - def hello_world(self): - QMessageBox.information( - self.iface.mainWindow(), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - ) - return - - -if __name__ == "__main__": - pass diff --git a/qgis-app/plugins/tests/HelloWorld/1.2-wierdname/HelloWorld/__init__.py b/qgis-app/plugins/tests/HelloWorld/1.2-wierdname/HelloWorld/__init__.py deleted file mode 100644 index 5f7dc3df..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.2-wierdname/HelloWorld/__init__.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This script initializes the plugin, making it known to QGIS. -""" - - -def name(): - return "HelloWorld" - - -def description(): - return "HelloWorld" - - -def version(): - return "Version 1.1" - - -def qgisMinimumVersion(): - return "1.0" - - -def classFactory(iface): - from HelloWorld import HelloWorld - - return HelloWorld(iface) - - -def icon(): - """ - Icon - """ - return "icon.png" diff --git a/qgis-app/plugins/tests/HelloWorld/1.2-wierdname/HelloWorld/icon.png b/qgis-app/plugins/tests/HelloWorld/1.2-wierdname/HelloWorld/icon.png deleted file mode 100644 index c043d921d34a37a211b8a1aa8acec81febb32327..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1304 zcmV+z1?T#SP)Px#24YJ`L;y|zng9j|XT?MS000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2igN3 z6b3n(XY02B000?uMObu0Z*6U5Zgc=ca%Ew3Wn>_CX>@2HM@dakSAh-}000DYNkl%$>F~Q)j4Yu|R1Nx&Vb@QB0*x2~Z)X@BjoKR73(X8iFQ< zH(yMQK8f;R4U1ukpfn1B(pXATPzWtcnHE~6(^)%rmOIPc=UyM22f$7tp5)}qIVayQ zC*N5<0)>UDsj2_4qN-+7Nr_{L(WoyIMR_`rVD9-QJUt%I@b>_eLYIk|oKC0v^*7(@ zJleK?le>0lgVAC!!)VYWlNG?kS&WU`76$wKB7K*C-5HIB23`p4aJklQ-?{gX4O@1a zWzvLulSx>L068bCyeKMa4q9!6gQ_eoB2OOg=DUCVc0Cgg_dm-apEk|zx8K>(|H0l* zXkS8uC%~ZY6^lBzSg+VElYX_gsfwB^3xp<%nj#BEJ(N&rI-cYd3WQF#xO!pH_}E=76!i5z z$IeMyI5s-e8ois(e{1 zuUOFbpUOXJ*l6n_lgWf>)(4uB(xS_#=475zM3IvvQBY(>nhb_x3dd$(kQJ8I>B{UY z%WSl@W>zrAi=jj+J->)lxlAU-PIyJ3*sPaTMdDJaWYQl9hS+2>4N7mIRD!V5rS^D| z6?s7jJ_~;8DZ-(tffy6f>=sHQR4MyfT~(>Qq=>pda({Gq==PA1N-`a-YZ`WSZFnQ6 zYNV^gO8WhNAsP-`pEcu=JA>ccx_WtA`{phx7MYSXsJW)4iz?SP)h_@6XsW7aIi4RM zpP1As>2OC&+4EzbJKsI0kAZ;y@fDNFx@F0$wN5s}M|7Gh zj(SE%BSF89&~lP{uB*&sve{H6=QwrdB-4BGT-QvaP0lcl>ZI4(Yq1u8w5DYZ2CiN8 zsEVjCOvGn0=}SEi9!%|eclVBCCr(&?IrVe9qKMHM7tKsgL0~6hOfRP=I zhpq3w_woK4gF{kvbzO{!g@tG&%m@NIQD_N~g#v6c{+On1yBixC&C8apBuk6u5tZ|5 zDLrMPe>;DH?&&$ZC&eZ_1y^ICKs+rl<5LM@bgU0tTKcy)bwGF;2!*^l|K#{N%(in2 z1^86tMoXgzi{Y-;(();x*K1%HA7p6+^D7BFPsrJVfdIo*Rn@dp1F{(w*9Pw3 z`0>k!rl*rv|1X$4aA4a&bF&K#%Q`{Z$^hs=>hzk^X>RT9z4a9UEw3;1gBSsr0hnoe z&eDDRHXVETFz0YMZ3qS;nboV^mV*b6Gy{kM-~n&|vH+y#f#>Aa44@Q%bL-Zwi-ACJ zth{_~_36`RwgX@QL;)o7;P|scQm6_#00sa>`BR_QTpl3jvlR+`sQ&=^6)Ws|9!-e= O0000 - very - very - very - very long multiline changelog - - -tags=wkt,raster - -tracker=http://bugs.itopen.it -homepage=http://www.itopen.it -repository=http://www.itopen.it/repo - -icon=icon.png diff --git a/qgis-app/plugins/tests/HelloWorld/1.2/HelloWorld/HelloWorld.py b/qgis-app/plugins/tests/HelloWorld/1.2/HelloWorld/HelloWorld.py deleted file mode 100644 index 45bdac04..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.2/HelloWorld/HelloWorld.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -class HelloWorld: - def __init__(self, iface): - # Save reference to the QGIS interface - self.iface = iface - self.canvas = iface.mapCanvas() - - def initGui(self): - # Create action that will start plugin - self.action = QAction( - QIcon(":/plugins/"), "&HelloWorld", self.iface.mainWindow() - ) - # connect the action to the run method - QObject.connect(self.action, SIGNAL("activated()"), self.hello_world) - - # Add toolbar button and menu item - self.iface.addPluginToMenu("HelloWorld", self.action) - - def unload(self): - # Remove the plugin menu item and icon - self.iface.removePluginMenu("HelloWorld", self.action) - - # run - def hello_world(self): - QMessageBox.information( - self.iface.mainWindow(), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - ) - return - - -if __name__ == "__main__": - pass diff --git a/qgis-app/plugins/tests/HelloWorld/1.2/HelloWorld/__init__.py b/qgis-app/plugins/tests/HelloWorld/1.2/HelloWorld/__init__.py deleted file mode 100644 index 5f7dc3df..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.2/HelloWorld/__init__.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This script initializes the plugin, making it known to QGIS. -""" - - -def name(): - return "HelloWorld" - - -def description(): - return "HelloWorld" - - -def version(): - return "Version 1.1" - - -def qgisMinimumVersion(): - return "1.0" - - -def classFactory(iface): - from HelloWorld import HelloWorld - - return HelloWorld(iface) - - -def icon(): - """ - Icon - """ - return "icon.png" diff --git a/qgis-app/plugins/tests/HelloWorld/1.2/HelloWorld/icon.png b/qgis-app/plugins/tests/HelloWorld/1.2/HelloWorld/icon.png deleted file mode 100644 index c043d921d34a37a211b8a1aa8acec81febb32327..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1304 zcmV+z1?T#SP)Px#24YJ`L;y|zng9j|XT?MS000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2igN3 z6b3n(XY02B000?uMObu0Z*6U5Zgc=ca%Ew3Wn>_CX>@2HM@dakSAh-}000DYNkl%$>F~Q)j4Yu|R1Nx&Vb@QB0*x2~Z)X@BjoKR73(X8iFQ< zH(yMQK8f;R4U1ukpfn1B(pXATPzWtcnHE~6(^)%rmOIPc=UyM22f$7tp5)}qIVayQ zC*N5<0)>UDsj2_4qN-+7Nr_{L(WoyIMR_`rVD9-QJUt%I@b>_eLYIk|oKC0v^*7(@ zJleK?le>0lgVAC!!)VYWlNG?kS&WU`76$wKB7K*C-5HIB23`p4aJklQ-?{gX4O@1a zWzvLulSx>L068bCyeKMa4q9!6gQ_eoB2OOg=DUCVc0Cgg_dm-apEk|zx8K>(|H0l* zXkS8uC%~ZY6^lBzSg+VElYX_gsfwB^3xp<%nj#BEJ(N&rI-cYd3WQF#xO!pH_}E=76!i5z z$IeMyI5s-e8ois(e{1 zuUOFbpUOXJ*l6n_lgWf>)(4uB(xS_#=475zM3IvvQBY(>nhb_x3dd$(kQJ8I>B{UY z%WSl@W>zrAi=jj+J->)lxlAU-PIyJ3*sPaTMdDJaWYQl9hS+2>4N7mIRD!V5rS^D| z6?s7jJ_~;8DZ-(tffy6f>=sHQR4MyfT~(>Qq=>pda({Gq==PA1N-`a-YZ`WSZFnQ6 zYNV^gO8WhNAsP-`pEcu=JA>ccx_WtA`{phx7MYSXsJW)4iz?SP)h_@6XsW7aIi4RM zpP1As>2OC&+4EzbJKsI0kAZ;y@fDNFx@F0$wN5s}M|7Gh zj(SE%BSF89&~lP{uB*&sve{H6=QwrdB-4BGT-QvaP0lcl>ZI4(Yq1u8w5DYZ2CiN8 zsEVjCOvGn0=}SEi9!%|eclVBCCr(&?IrVe9qKMHM7tKsgL0~6hOfRP=I zhpq3w_woK4gF{kvbzO{!g@tG&%m@NIQD_N~g#v6c{+On1yBixC&C8apBuk6u5tZ|5 zDLrMPe>;DH?&&$ZC&eZ_1y^ICKs+rl<5LM@bgU0tTKcy)bwGF;2!*^l|K#{N%(in2 z1^86tMoXgzi{Y-;(();x*K1%HA7p6+^D7BFPsrJVfdIo*Rn@dp1F{(w*9Pw3 z`0>k!rl*rv|1X$4aA4a&bF&K#%Q`{Z$^hs=>hzk^X>RT9z4a9UEw3;1gBSsr0hnoe z&eDDRHXVETFz0YMZ3qS;nboV^mV*b6Gy{kM-~n&|vH+y#f#>Aa44@Q%bL-Zwi-ACJ zth{_~_36`RwgX@QL;)o7;P|scQm6_#00sa>`BR_QTpl3jvlR+`sQ&=^6)Ws|9!-e= O0000 - very - very - very - very long multiline changelog - - -tags=wkt,raster,hello world - -tracker=http://bugs.itopen.it -homepage=http://www.itopen.it -repository=http://www.itopen.it/repo - -icon=icon.png diff --git a/qgis-app/plugins/tests/HelloWorld/1.3-full-md-no-init/HelloWorld/HelloWorld.py b/qgis-app/plugins/tests/HelloWorld/1.3-full-md-no-init/HelloWorld/HelloWorld.py deleted file mode 100644 index 45bdac04..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.3-full-md-no-init/HelloWorld/HelloWorld.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -class HelloWorld: - def __init__(self, iface): - # Save reference to the QGIS interface - self.iface = iface - self.canvas = iface.mapCanvas() - - def initGui(self): - # Create action that will start plugin - self.action = QAction( - QIcon(":/plugins/"), "&HelloWorld", self.iface.mainWindow() - ) - # connect the action to the run method - QObject.connect(self.action, SIGNAL("activated()"), self.hello_world) - - # Add toolbar button and menu item - self.iface.addPluginToMenu("HelloWorld", self.action) - - def unload(self): - # Remove the plugin menu item and icon - self.iface.removePluginMenu("HelloWorld", self.action) - - # run - def hello_world(self): - QMessageBox.information( - self.iface.mainWindow(), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - ) - return - - -if __name__ == "__main__": - pass diff --git a/qgis-app/plugins/tests/HelloWorld/1.3-full-md-no-init/HelloWorld/__init__.py b/qgis-app/plugins/tests/HelloWorld/1.3-full-md-no-init/HelloWorld/__init__.py deleted file mode 100644 index e9feb7d9..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.3-full-md-no-init/HelloWorld/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This script initializes the plugin, making it known to QGIS. -""" diff --git a/qgis-app/plugins/tests/HelloWorld/1.3-full-md-no-init/HelloWorld/icon.png b/qgis-app/plugins/tests/HelloWorld/1.3-full-md-no-init/HelloWorld/icon.png deleted file mode 100644 index 67d47930b2262a7087631bc8c94f7104801b8c83..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 325 zcmV-L0lNN)P)X z2RtthAdf*$TAfhpw|7z;*;(eo4j>Ib_!G9~yn6r>rN8yd2mrW$cwYD;{T5kpZ`Jb& X2?uL6iclM400000NkvXXu0mjf+}4F+ diff --git a/qgis-app/plugins/tests/HelloWorld/1.3-full-md-no-init/HelloWorld/metadata.txt b/qgis-app/plugins/tests/HelloWorld/1.3-full-md-no-init/HelloWorld/metadata.txt deleted file mode 100644 index 9e35f413..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.3-full-md-no-init/HelloWorld/metadata.txt +++ /dev/null @@ -1,26 +0,0 @@ -[general] -name=HelloWorld -qgisMinimumVersion=1.8 -description=This is a plugin for greeting the - (going multiline) world, version is 1.3 -version=version 1.3 -author=Alessandro Secondo -email=email2@email.com - - -changelog=this is a very - very
- very - very - very - very long multiline changelog - - -tags=wkt,raster,hello world - -tracker=http://bugs.itopen.it -homepage=http://www.itopen.it -repository=http://www.itopen.it/repo - -; change icon... -icon=icon.png diff --git a/qgis-app/plugins/tests/HelloWorld/1.5-full-md-no-init-experimental/HelloWorld/HelloWorld.py b/qgis-app/plugins/tests/HelloWorld/1.5-full-md-no-init-experimental/HelloWorld/HelloWorld.py deleted file mode 100644 index 45bdac04..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.5-full-md-no-init-experimental/HelloWorld/HelloWorld.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -class HelloWorld: - def __init__(self, iface): - # Save reference to the QGIS interface - self.iface = iface - self.canvas = iface.mapCanvas() - - def initGui(self): - # Create action that will start plugin - self.action = QAction( - QIcon(":/plugins/"), "&HelloWorld", self.iface.mainWindow() - ) - # connect the action to the run method - QObject.connect(self.action, SIGNAL("activated()"), self.hello_world) - - # Add toolbar button and menu item - self.iface.addPluginToMenu("HelloWorld", self.action) - - def unload(self): - # Remove the plugin menu item and icon - self.iface.removePluginMenu("HelloWorld", self.action) - - # run - def hello_world(self): - QMessageBox.information( - self.iface.mainWindow(), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - ) - return - - -if __name__ == "__main__": - pass diff --git a/qgis-app/plugins/tests/HelloWorld/1.5-full-md-no-init-experimental/HelloWorld/__init__.py b/qgis-app/plugins/tests/HelloWorld/1.5-full-md-no-init-experimental/HelloWorld/__init__.py deleted file mode 100644 index eff9426a..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.5-full-md-no-init-experimental/HelloWorld/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This script initializes the plugin, making it known to QGIS. -""" - - -def classFactory(iface): - from HelloWorld import HelloWorld - - return HelloWorld(iface) diff --git a/qgis-app/plugins/tests/HelloWorld/1.5-full-md-no-init-experimental/HelloWorld/icon.png b/qgis-app/plugins/tests/HelloWorld/1.5-full-md-no-init-experimental/HelloWorld/icon.png deleted file mode 100644 index 67d47930b2262a7087631bc8c94f7104801b8c83..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 325 zcmV-L0lNN)P)X z2RtthAdf*$TAfhpw|7z;*;(eo4j>Ib_!G9~yn6r>rN8yd2mrW$cwYD;{T5kpZ`Jb& X2?uL6iclM400000NkvXXu0mjf+}4F+ diff --git a/qgis-app/plugins/tests/HelloWorld/1.5-full-md-no-init-experimental/HelloWorld/metadata.txt b/qgis-app/plugins/tests/HelloWorld/1.5-full-md-no-init-experimental/HelloWorld/metadata.txt deleted file mode 100644 index 778919e0..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.5-full-md-no-init-experimental/HelloWorld/metadata.txt +++ /dev/null @@ -1,31 +0,0 @@ -[general] -name=HelloWorld -qgisMinimumVersion=1.8 -description=This is a test plugin for greeting the - (going multiline) world, version is 1.5 -version=version 1.5 -author=Alessandro Pasotti -email=apasotti@gmail.com - - -changelog=this is a very - very
- very - very - very - very long multiline changelog - - -tags= wkt, raster,hello world, spaced tag , tag - -tracker=http://bugs.itopen.it -homepage=http://www.itopen.it -repository=http://www.itopen.it/repo - - - -experimental=True - - -; change icon... -icon=icon.png diff --git a/qgis-app/plugins/tests/HelloWorld/1.6-full-md-no-init-unicode-error/HelloWorld/HelloWorld.py b/qgis-app/plugins/tests/HelloWorld/1.6-full-md-no-init-unicode-error/HelloWorld/HelloWorld.py deleted file mode 100644 index 45bdac04..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.6-full-md-no-init-unicode-error/HelloWorld/HelloWorld.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -class HelloWorld: - def __init__(self, iface): - # Save reference to the QGIS interface - self.iface = iface - self.canvas = iface.mapCanvas() - - def initGui(self): - # Create action that will start plugin - self.action = QAction( - QIcon(":/plugins/"), "&HelloWorld", self.iface.mainWindow() - ) - # connect the action to the run method - QObject.connect(self.action, SIGNAL("activated()"), self.hello_world) - - # Add toolbar button and menu item - self.iface.addPluginToMenu("HelloWorld", self.action) - - def unload(self): - # Remove the plugin menu item and icon - self.iface.removePluginMenu("HelloWorld", self.action) - - # run - def hello_world(self): - QMessageBox.information( - self.iface.mainWindow(), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - ) - return - - -if __name__ == "__main__": - pass diff --git a/qgis-app/plugins/tests/HelloWorld/1.6-full-md-no-init-unicode-error/HelloWorld/__init__.py b/qgis-app/plugins/tests/HelloWorld/1.6-full-md-no-init-unicode-error/HelloWorld/__init__.py deleted file mode 100644 index eff9426a..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.6-full-md-no-init-unicode-error/HelloWorld/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This script initializes the plugin, making it known to QGIS. -""" - - -def classFactory(iface): - from HelloWorld import HelloWorld - - return HelloWorld(iface) diff --git a/qgis-app/plugins/tests/HelloWorld/1.6-full-md-no-init-unicode-error/HelloWorld/icon.png b/qgis-app/plugins/tests/HelloWorld/1.6-full-md-no-init-unicode-error/HelloWorld/icon.png deleted file mode 100644 index 67d47930b2262a7087631bc8c94f7104801b8c83..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 325 zcmV-L0lNN)P)X z2RtthAdf*$TAfhpw|7z;*;(eo4j>Ib_!G9~yn6r>rN8yd2mrW$cwYD;{T5kpZ`Jb& X2?uL6iclM400000NkvXXu0mjf+}4F+ diff --git a/qgis-app/plugins/tests/HelloWorld/1.6-full-md-no-init-unicode-error/HelloWorld/metadata.txt b/qgis-app/plugins/tests/HelloWorld/1.6-full-md-no-init-unicode-error/HelloWorld/metadata.txt deleted file mode 100644 index 1fb4daf4..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.6-full-md-no-init-unicode-error/HelloWorld/metadata.txt +++ /dev/null @@ -1,31 +0,0 @@ -[general] -name=HelloWorld -qgisMinimumVersion=1.8 -description=This is a test plugin for greeting the - (going multiline) world, version is 1.6 -version=version 1.6 -author=Alessandrò Pasottì -email=apasotti@gmail.com - - -changelog=this is a very - very
- very - very - very - very long multiline changelog - - -tags= wkt, raster,hello world, spaced tag , tag - -tracker=http://bugs.itopen.it -homepage=http://www.itopen.it -repository=http://www.itopen.it/repo - - - -experimental=True - - -; change icon... -icon=icon.png diff --git a/qgis-app/plugins/tests/HelloWorld/1.7-full-md-no-init-subfolder-icon/HelloWorld/HelloWorld.py b/qgis-app/plugins/tests/HelloWorld/1.7-full-md-no-init-subfolder-icon/HelloWorld/HelloWorld.py deleted file mode 100644 index 45bdac04..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.7-full-md-no-init-subfolder-icon/HelloWorld/HelloWorld.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -class HelloWorld: - def __init__(self, iface): - # Save reference to the QGIS interface - self.iface = iface - self.canvas = iface.mapCanvas() - - def initGui(self): - # Create action that will start plugin - self.action = QAction( - QIcon(":/plugins/"), "&HelloWorld", self.iface.mainWindow() - ) - # connect the action to the run method - QObject.connect(self.action, SIGNAL("activated()"), self.hello_world) - - # Add toolbar button and menu item - self.iface.addPluginToMenu("HelloWorld", self.action) - - def unload(self): - # Remove the plugin menu item and icon - self.iface.removePluginMenu("HelloWorld", self.action) - - # run - def hello_world(self): - QMessageBox.information( - self.iface.mainWindow(), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - ) - return - - -if __name__ == "__main__": - pass diff --git a/qgis-app/plugins/tests/HelloWorld/1.7-full-md-no-init-subfolder-icon/HelloWorld/__init__.py b/qgis-app/plugins/tests/HelloWorld/1.7-full-md-no-init-subfolder-icon/HelloWorld/__init__.py deleted file mode 100644 index eff9426a..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.7-full-md-no-init-subfolder-icon/HelloWorld/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This script initializes the plugin, making it known to QGIS. -""" - - -def classFactory(iface): - from HelloWorld import HelloWorld - - return HelloWorld(iface) diff --git a/qgis-app/plugins/tests/HelloWorld/1.7-full-md-no-init-subfolder-icon/HelloWorld/icons/icon.png b/qgis-app/plugins/tests/HelloWorld/1.7-full-md-no-init-subfolder-icon/HelloWorld/icons/icon.png deleted file mode 100644 index 681cf0cbc30d28f10f220135c21869754c5598ff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1319 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD~={f$?I1Pl)UP|Nns^qhQ#CK=JFZ zM}eN1RTAVE%)rRR%)-jX&cVsW&BM#bFCZu+EFvlgM6?7Z4a679JTD8<&`roSK%Nk(rfKP*_x4Qd(YFRa4*G(%RnL)7v*; z;?!x=XUv?paM8+DtJiJVxOwZ&-G`1IJAUHisWay31oNF_GV`0w$jle$uzTlx<bFrsy4jY6Ums2U^*>K_*TL$C_x|4b68mW@hmXg(ZO?;aG>qS$57T}Z zZ+hZ$ddmJw|DN^l37Et!{@H48_kRk5N_nXN z6ZShFSzH;SWgh=zy#8_`$AXig3C|RIkKkI@oHw(5ECORzVYEb_B`eN6mrUr+q z!<#%$@_8oDnK`q6@@|C%SsgQH{+`pdO|$vq`}{pAEE)c7Jq)W}%U^dpzp}djO=P4? zWP{+RbjFTp>ovpoCEDscD4|$`;50CPXxwGoFgvLszv{Vx>}iH|9*i;TznpWtWw% - very - very - very - very long multiline changelog - - -tags= wkt, raster,hello world, spaced tag , tag - -tracker=http://bugs.itopen.it -homepage=http://www.itopen.it -repository=http://www.itopen.it/repo - - - -experimental=True - - -; change icon... -icon=icons/icon.png diff --git a/qgis-app/plugins/tests/HelloWorld/1.8-author-slashes-error/HelloWorld/HelloWorld.py b/qgis-app/plugins/tests/HelloWorld/1.8-author-slashes-error/HelloWorld/HelloWorld.py deleted file mode 100644 index 45bdac04..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.8-author-slashes-error/HelloWorld/HelloWorld.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -class HelloWorld: - def __init__(self, iface): - # Save reference to the QGIS interface - self.iface = iface - self.canvas = iface.mapCanvas() - - def initGui(self): - # Create action that will start plugin - self.action = QAction( - QIcon(":/plugins/"), "&HelloWorld", self.iface.mainWindow() - ) - # connect the action to the run method - QObject.connect(self.action, SIGNAL("activated()"), self.hello_world) - - # Add toolbar button and menu item - self.iface.addPluginToMenu("HelloWorld", self.action) - - def unload(self): - # Remove the plugin menu item and icon - self.iface.removePluginMenu("HelloWorld", self.action) - - # run - def hello_world(self): - QMessageBox.information( - self.iface.mainWindow(), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - ) - return - - -if __name__ == "__main__": - pass diff --git a/qgis-app/plugins/tests/HelloWorld/1.8-author-slashes-error/HelloWorld/__init__.py b/qgis-app/plugins/tests/HelloWorld/1.8-author-slashes-error/HelloWorld/__init__.py deleted file mode 100644 index e9feb7d9..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.8-author-slashes-error/HelloWorld/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This script initializes the plugin, making it known to QGIS. -""" diff --git a/qgis-app/plugins/tests/HelloWorld/1.8-author-slashes-error/HelloWorld/icons/icon.png b/qgis-app/plugins/tests/HelloWorld/1.8-author-slashes-error/HelloWorld/icons/icon.png deleted file mode 100644 index 681cf0cbc30d28f10f220135c21869754c5598ff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1319 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD~={f$?I1Pl)UP|Nns^qhQ#CK=JFZ zM}eN1RTAVE%)rRR%)-jX&cVsW&BM#bFCZu+EFvlgM6?7Z4a679JTD8<&`roSK%Nk(rfKP*_x4Qd(YFRa4*G(%RnL)7v*; z;?!x=XUv?paM8+DtJiJVxOwZ&-G`1IJAUHisWay31oNF_GV`0w$jle$uzTlx<bFrsy4jY6Ums2U^*>K_*TL$C_x|4b68mW@hmXg(ZO?;aG>qS$57T}Z zZ+hZ$ddmJw|DN^l37Et!{@H48_kRk5N_nXN z6ZShFSzH;SWgh=zy#8_`$AXig3C|RIkKkI@oHw(5ECORzVYEb_B`eN6mrUr+q z!<#%$@_8oDnK`q6@@|C%SsgQH{+`pdO|$vq`}{pAEE)c7Jq)W}%U^dpzp}djO=P4? zWP{+RbjFTp>ovpoCEDscD4|$`;50CPXxwGoFgvLszv{Vx>}iH|9*i;TznpWtWw% - very - very - very - very long multiline changelog - - -tags= wkt, raster,hello world, spaced tag , tag - -tracker=http://bugs.itopen.it -homepage=http://www.itopen.it -repository=http://www.itopen.it/repo - - - -experimental=True - - -; change icon... -icon=icons/icon.png diff --git a/qgis-app/plugins/tests/HelloWorld/1.9-full-md-max_qgs_version/HelloWorld/HelloWorld.py b/qgis-app/plugins/tests/HelloWorld/1.9-full-md-max_qgs_version/HelloWorld/HelloWorld.py deleted file mode 100644 index 45bdac04..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.9-full-md-max_qgs_version/HelloWorld/HelloWorld.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -class HelloWorld: - def __init__(self, iface): - # Save reference to the QGIS interface - self.iface = iface - self.canvas = iface.mapCanvas() - - def initGui(self): - # Create action that will start plugin - self.action = QAction( - QIcon(":/plugins/"), "&HelloWorld", self.iface.mainWindow() - ) - # connect the action to the run method - QObject.connect(self.action, SIGNAL("activated()"), self.hello_world) - - # Add toolbar button and menu item - self.iface.addPluginToMenu("HelloWorld", self.action) - - def unload(self): - # Remove the plugin menu item and icon - self.iface.removePluginMenu("HelloWorld", self.action) - - # run - def hello_world(self): - QMessageBox.information( - self.iface.mainWindow(), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - ) - return - - -if __name__ == "__main__": - pass diff --git a/qgis-app/plugins/tests/HelloWorld/1.9-full-md-max_qgs_version/HelloWorld/__init__.py b/qgis-app/plugins/tests/HelloWorld/1.9-full-md-max_qgs_version/HelloWorld/__init__.py deleted file mode 100644 index eff9426a..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.9-full-md-max_qgs_version/HelloWorld/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This script initializes the plugin, making it known to QGIS. -""" - - -def classFactory(iface): - from HelloWorld import HelloWorld - - return HelloWorld(iface) diff --git a/qgis-app/plugins/tests/HelloWorld/1.9-full-md-max_qgs_version/HelloWorld/icons/icon.png b/qgis-app/plugins/tests/HelloWorld/1.9-full-md-max_qgs_version/HelloWorld/icons/icon.png deleted file mode 100644 index 681cf0cbc30d28f10f220135c21869754c5598ff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1319 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD~={f$?I1Pl)UP|Nns^qhQ#CK=JFZ zM}eN1RTAVE%)rRR%)-jX&cVsW&BM#bFCZu+EFvlgM6?7Z4a679JTD8<&`roSK%Nk(rfKP*_x4Qd(YFRa4*G(%RnL)7v*; z;?!x=XUv?paM8+DtJiJVxOwZ&-G`1IJAUHisWay31oNF_GV`0w$jle$uzTlx<bFrsy4jY6Ums2U^*>K_*TL$C_x|4b68mW@hmXg(ZO?;aG>qS$57T}Z zZ+hZ$ddmJw|DN^l37Et!{@H48_kRk5N_nXN z6ZShFSzH;SWgh=zy#8_`$AXig3C|RIkKkI@oHw(5ECORzVYEb_B`eN6mrUr+q z!<#%$@_8oDnK`q6@@|C%SsgQH{+`pdO|$vq`}{pAEE)c7Jq)W}%U^dpzp}djO=P4? zWP{+RbjFTp>ovpoCEDscD4|$`;50CPXxwGoFgvLszv{Vx>}iH|9*i;TznpWtWw% - very - very - very - very long multiline changelog - - -tags= wkt, raster,hello world, spaced tag , tag - -tracker=http://bugs.itopen.it -homepage=http://www.itopen.it -repository=http://www.itopen.it/repo - - - -experimental=False - - -; change icon... -icon=icons/icon.png diff --git a/qgis-app/plugins/tests/HelloWorld/2.0-full-md-empty-max_qgs_version/HelloWorld/HelloWorld.py b/qgis-app/plugins/tests/HelloWorld/2.0-full-md-empty-max_qgs_version/HelloWorld/HelloWorld.py deleted file mode 100644 index 45bdac04..00000000 --- a/qgis-app/plugins/tests/HelloWorld/2.0-full-md-empty-max_qgs_version/HelloWorld/HelloWorld.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -class HelloWorld: - def __init__(self, iface): - # Save reference to the QGIS interface - self.iface = iface - self.canvas = iface.mapCanvas() - - def initGui(self): - # Create action that will start plugin - self.action = QAction( - QIcon(":/plugins/"), "&HelloWorld", self.iface.mainWindow() - ) - # connect the action to the run method - QObject.connect(self.action, SIGNAL("activated()"), self.hello_world) - - # Add toolbar button and menu item - self.iface.addPluginToMenu("HelloWorld", self.action) - - def unload(self): - # Remove the plugin menu item and icon - self.iface.removePluginMenu("HelloWorld", self.action) - - # run - def hello_world(self): - QMessageBox.information( - self.iface.mainWindow(), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - ) - return - - -if __name__ == "__main__": - pass diff --git a/qgis-app/plugins/tests/HelloWorld/2.0-full-md-empty-max_qgs_version/HelloWorld/__init__.py b/qgis-app/plugins/tests/HelloWorld/2.0-full-md-empty-max_qgs_version/HelloWorld/__init__.py deleted file mode 100644 index eff9426a..00000000 --- a/qgis-app/plugins/tests/HelloWorld/2.0-full-md-empty-max_qgs_version/HelloWorld/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This script initializes the plugin, making it known to QGIS. -""" - - -def classFactory(iface): - from HelloWorld import HelloWorld - - return HelloWorld(iface) diff --git a/qgis-app/plugins/tests/HelloWorld/2.0-full-md-empty-max_qgs_version/HelloWorld/icons/icon.png b/qgis-app/plugins/tests/HelloWorld/2.0-full-md-empty-max_qgs_version/HelloWorld/icons/icon.png deleted file mode 100644 index 681cf0cbc30d28f10f220135c21869754c5598ff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1319 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD~={f$?I1Pl)UP|Nns^qhQ#CK=JFZ zM}eN1RTAVE%)rRR%)-jX&cVsW&BM#bFCZu+EFvlgM6?7Z4a679JTD8<&`roSK%Nk(rfKP*_x4Qd(YFRa4*G(%RnL)7v*; z;?!x=XUv?paM8+DtJiJVxOwZ&-G`1IJAUHisWay31oNF_GV`0w$jle$uzTlx<bFrsy4jY6Ums2U^*>K_*TL$C_x|4b68mW@hmXg(ZO?;aG>qS$57T}Z zZ+hZ$ddmJw|DN^l37Et!{@H48_kRk5N_nXN z6ZShFSzH;SWgh=zy#8_`$AXig3C|RIkKkI@oHw(5ECORzVYEb_B`eN6mrUr+q z!<#%$@_8oDnK`q6@@|C%SsgQH{+`pdO|$vq`}{pAEE)c7Jq)W}%U^dpzp}djO=P4? zWP{+RbjFTp>ovpoCEDscD4|$`;50CPXxwGoFgvLszv{Vx>}iH|9*i;TznpWtWw% - very - very - very - very long multiline changelog - - -tags= wkt, raster,hello world, spaced tag , tag - -tracker=http://bugs.itopen.it -homepage=http://www.itopen.it -repository=http://www.itopen.it/repo - - - -experimental=False - - -; change icon... -icon=icons/icon.png diff --git a/qgis-app/plugins/tests/HelloWorld/2.1-full-md-max_qgs_version_2.999/HelloWorld/HelloWorld.py b/qgis-app/plugins/tests/HelloWorld/2.1-full-md-max_qgs_version_2.999/HelloWorld/HelloWorld.py deleted file mode 100644 index 45bdac04..00000000 --- a/qgis-app/plugins/tests/HelloWorld/2.1-full-md-max_qgs_version_2.999/HelloWorld/HelloWorld.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -class HelloWorld: - def __init__(self, iface): - # Save reference to the QGIS interface - self.iface = iface - self.canvas = iface.mapCanvas() - - def initGui(self): - # Create action that will start plugin - self.action = QAction( - QIcon(":/plugins/"), "&HelloWorld", self.iface.mainWindow() - ) - # connect the action to the run method - QObject.connect(self.action, SIGNAL("activated()"), self.hello_world) - - # Add toolbar button and menu item - self.iface.addPluginToMenu("HelloWorld", self.action) - - def unload(self): - # Remove the plugin menu item and icon - self.iface.removePluginMenu("HelloWorld", self.action) - - # run - def hello_world(self): - QMessageBox.information( - self.iface.mainWindow(), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - ) - return - - -if __name__ == "__main__": - pass diff --git a/qgis-app/plugins/tests/HelloWorld/2.1-full-md-max_qgs_version_2.999/HelloWorld/__init__.py b/qgis-app/plugins/tests/HelloWorld/2.1-full-md-max_qgs_version_2.999/HelloWorld/__init__.py deleted file mode 100644 index eff9426a..00000000 --- a/qgis-app/plugins/tests/HelloWorld/2.1-full-md-max_qgs_version_2.999/HelloWorld/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This script initializes the plugin, making it known to QGIS. -""" - - -def classFactory(iface): - from HelloWorld import HelloWorld - - return HelloWorld(iface) diff --git a/qgis-app/plugins/tests/HelloWorld/2.1-full-md-max_qgs_version_2.999/HelloWorld/icons/icon.png b/qgis-app/plugins/tests/HelloWorld/2.1-full-md-max_qgs_version_2.999/HelloWorld/icons/icon.png deleted file mode 100644 index 681cf0cbc30d28f10f220135c21869754c5598ff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1319 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD~={f$?I1Pl)UP|Nns^qhQ#CK=JFZ zM}eN1RTAVE%)rRR%)-jX&cVsW&BM#bFCZu+EFvlgM6?7Z4a679JTD8<&`roSK%Nk(rfKP*_x4Qd(YFRa4*G(%RnL)7v*; z;?!x=XUv?paM8+DtJiJVxOwZ&-G`1IJAUHisWay31oNF_GV`0w$jle$uzTlx<bFrsy4jY6Ums2U^*>K_*TL$C_x|4b68mW@hmXg(ZO?;aG>qS$57T}Z zZ+hZ$ddmJw|DN^l37Et!{@H48_kRk5N_nXN z6ZShFSzH;SWgh=zy#8_`$AXig3C|RIkKkI@oHw(5ECORzVYEb_B`eN6mrUr+q z!<#%$@_8oDnK`q6@@|C%SsgQH{+`pdO|$vq`}{pAEE)c7Jq)W}%U^dpzp}djO=P4? zWP{+RbjFTp>ovpoCEDscD4|$`;50CPXxwGoFgvLszv{Vx>}iH|9*i;TznpWtWw% - very - very - very - very long multiline changelog - - -tags= wkt, raster,hello world, spaced tag , tag - -tracker=http://bugs.itopen.it -homepage=http://www.itopen.it -repository=http://www.itopen.it/repo - - - -experimental=False - - -; change icon... -icon=icons/icon.png diff --git a/qgis-app/plugins/tests/HelloWorld/2.2-full-md-empty-max_qgs_category_web/HelloWorld/HelloWorld.py b/qgis-app/plugins/tests/HelloWorld/2.2-full-md-empty-max_qgs_category_web/HelloWorld/HelloWorld.py deleted file mode 100644 index 45bdac04..00000000 --- a/qgis-app/plugins/tests/HelloWorld/2.2-full-md-empty-max_qgs_category_web/HelloWorld/HelloWorld.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -class HelloWorld: - def __init__(self, iface): - # Save reference to the QGIS interface - self.iface = iface - self.canvas = iface.mapCanvas() - - def initGui(self): - # Create action that will start plugin - self.action = QAction( - QIcon(":/plugins/"), "&HelloWorld", self.iface.mainWindow() - ) - # connect the action to the run method - QObject.connect(self.action, SIGNAL("activated()"), self.hello_world) - - # Add toolbar button and menu item - self.iface.addPluginToMenu("HelloWorld", self.action) - - def unload(self): - # Remove the plugin menu item and icon - self.iface.removePluginMenu("HelloWorld", self.action) - - # run - def hello_world(self): - QMessageBox.information( - self.iface.mainWindow(), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - ) - return - - -if __name__ == "__main__": - pass diff --git a/qgis-app/plugins/tests/HelloWorld/2.2-full-md-empty-max_qgs_category_web/HelloWorld/__init__.py b/qgis-app/plugins/tests/HelloWorld/2.2-full-md-empty-max_qgs_category_web/HelloWorld/__init__.py deleted file mode 100644 index eff9426a..00000000 --- a/qgis-app/plugins/tests/HelloWorld/2.2-full-md-empty-max_qgs_category_web/HelloWorld/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This script initializes the plugin, making it known to QGIS. -""" - - -def classFactory(iface): - from HelloWorld import HelloWorld - - return HelloWorld(iface) diff --git a/qgis-app/plugins/tests/HelloWorld/2.2-full-md-empty-max_qgs_category_web/HelloWorld/icons/icon.png b/qgis-app/plugins/tests/HelloWorld/2.2-full-md-empty-max_qgs_category_web/HelloWorld/icons/icon.png deleted file mode 100644 index 681cf0cbc30d28f10f220135c21869754c5598ff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1319 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD~={f$?I1Pl)UP|Nns^qhQ#CK=JFZ zM}eN1RTAVE%)rRR%)-jX&cVsW&BM#bFCZu+EFvlgM6?7Z4a679JTD8<&`roSK%Nk(rfKP*_x4Qd(YFRa4*G(%RnL)7v*; z;?!x=XUv?paM8+DtJiJVxOwZ&-G`1IJAUHisWay31oNF_GV`0w$jle$uzTlx<bFrsy4jY6Ums2U^*>K_*TL$C_x|4b68mW@hmXg(ZO?;aG>qS$57T}Z zZ+hZ$ddmJw|DN^l37Et!{@H48_kRk5N_nXN z6ZShFSzH;SWgh=zy#8_`$AXig3C|RIkKkI@oHw(5ECORzVYEb_B`eN6mrUr+q z!<#%$@_8oDnK`q6@@|C%SsgQH{+`pdO|$vq`}{pAEE)c7Jq)W}%U^dpzp}djO=P4? zWP{+RbjFTp>ovpoCEDscD4|$`;50CPXxwGoFgvLszv{Vx>}iH|9*i;TznpWtWw% - very - very - very - very long multiline changelog - - -tags= wkt, raster,hello world, spaced tag , tag - -tracker=http://bugs.itopen.it -homepage=http://www.itopen.it -repository=http://www.itopen.it/repo - -category=web - -experimental=False - - -; change icon... -icon=icons/icon.png diff --git a/qgis-app/plugins/tests/HelloWorld/2.3-full-changed-repository/HelloWorld/HelloWorld.py b/qgis-app/plugins/tests/HelloWorld/2.3-full-changed-repository/HelloWorld/HelloWorld.py deleted file mode 100644 index 45bdac04..00000000 --- a/qgis-app/plugins/tests/HelloWorld/2.3-full-changed-repository/HelloWorld/HelloWorld.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -class HelloWorld: - def __init__(self, iface): - # Save reference to the QGIS interface - self.iface = iface - self.canvas = iface.mapCanvas() - - def initGui(self): - # Create action that will start plugin - self.action = QAction( - QIcon(":/plugins/"), "&HelloWorld", self.iface.mainWindow() - ) - # connect the action to the run method - QObject.connect(self.action, SIGNAL("activated()"), self.hello_world) - - # Add toolbar button and menu item - self.iface.addPluginToMenu("HelloWorld", self.action) - - def unload(self): - # Remove the plugin menu item and icon - self.iface.removePluginMenu("HelloWorld", self.action) - - # run - def hello_world(self): - QMessageBox.information( - self.iface.mainWindow(), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - ) - return - - -if __name__ == "__main__": - pass diff --git a/qgis-app/plugins/tests/HelloWorld/2.3-full-changed-repository/HelloWorld/__init__.py b/qgis-app/plugins/tests/HelloWorld/2.3-full-changed-repository/HelloWorld/__init__.py deleted file mode 100644 index eff9426a..00000000 --- a/qgis-app/plugins/tests/HelloWorld/2.3-full-changed-repository/HelloWorld/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This script initializes the plugin, making it known to QGIS. -""" - - -def classFactory(iface): - from HelloWorld import HelloWorld - - return HelloWorld(iface) diff --git a/qgis-app/plugins/tests/HelloWorld/2.3-full-changed-repository/HelloWorld/icons/icon.png b/qgis-app/plugins/tests/HelloWorld/2.3-full-changed-repository/HelloWorld/icons/icon.png deleted file mode 100644 index 681cf0cbc30d28f10f220135c21869754c5598ff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1319 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD~={f$?I1Pl)UP|Nns^qhQ#CK=JFZ zM}eN1RTAVE%)rRR%)-jX&cVsW&BM#bFCZu+EFvlgM6?7Z4a679JTD8<&`roSK%Nk(rfKP*_x4Qd(YFRa4*G(%RnL)7v*; z;?!x=XUv?paM8+DtJiJVxOwZ&-G`1IJAUHisWay31oNF_GV`0w$jle$uzTlx<bFrsy4jY6Ums2U^*>K_*TL$C_x|4b68mW@hmXg(ZO?;aG>qS$57T}Z zZ+hZ$ddmJw|DN^l37Et!{@H48_kRk5N_nXN z6ZShFSzH;SWgh=zy#8_`$AXig3C|RIkKkI@oHw(5ECORzVYEb_B`eN6mrUr+q z!<#%$@_8oDnK`q6@@|C%SsgQH{+`pdO|$vq`}{pAEE)c7Jq)W}%U^dpzp}djO=P4? zWP{+RbjFTp>ovpoCEDscD4|$`;50CPXxwGoFgvLszv{Vx>}iH|9*i;TznpWtWw% - very - very - very - very long multiline changelog - - -tags= wkt, raster,hello world, spaced tag , tag - -tracker=http://bugs.itopen.it/changed -homepage=http://www.itopen.it/changed -repository=http://www.itopen.it/changedrepo - -category=web - -experimental=False - -about=Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. - - -; change icon... -icon=icons/icon.png diff --git a/qgis-app/plugins/tests/HelloWorld/Makefile b/qgis-app/plugins/tests/HelloWorld/Makefile deleted file mode 100644 index 6c425d8f..00000000 --- a/qgis-app/plugins/tests/HelloWorld/Makefile +++ /dev/null @@ -1,3 +0,0 @@ -all: - @if [ find -name HelloWorld_*.zip ]; then rm HelloWorld_*.zip; fi - @for i in $$(find . -maxdepth 1 -type d|egrep './.+'|sed -e 's/.\///' ); do cd $$i && zip -r HelloWorld_$$i.zip * && mv *.zip .. && cd ..; done; diff --git a/qgis-app/plugins/tests/HelloWorld/README b/qgis-app/plugins/tests/HelloWorld/README deleted file mode 100644 index c5d6f28a..00000000 --- a/qgis-app/plugins/tests/HelloWorld/README +++ /dev/null @@ -1,13 +0,0 @@ -Test plugins. - -Some can contain errors: - -1.0_spaced (space in folder name) -1.2_md_txt_incomplete (metadata.txt with missing fields) -1.2_wierdname to test for package name whoos search -1.8_author_slashes_error (slashes in author name: invalid) -1.9_full-md-max_qgs_version test max qgs version -2.0_full-md-empty-max_qgs_version test max qgs version empty -2.1_full-md-empty-max_qgs_version_2.999 test max qgs version -2.2_full-md-empty-max_qgs_category_web test category metadata -2.3_full-changed-repository test repository changed, also tests about metadata diff --git a/qgis-app/plugins/tests/__init__.py b/qgis-app/plugins/tests/__init__.py deleted file mode 100644 index 53d131f4..00000000 --- a/qgis-app/plugins/tests/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from plugins.tests import ws_test - -__test__ = { - "ws_test": ws_test, -} diff --git a/qgis-app/plugins/tests/test_change_maintainer.py b/qgis-app/plugins/tests/test_change_maintainer.py deleted file mode 100644 index 515b5c46..00000000 --- a/qgis-app/plugins/tests/test_change_maintainer.py +++ /dev/null @@ -1,101 +0,0 @@ -import os -from unittest.mock import patch - -from django.urls import reverse -from django.test import Client, TestCase, override_settings -from django.contrib.auth.models import User -from django.core.files.uploadedfile import SimpleUploadedFile -from plugins.models import Plugin, PluginVersion -from plugins.forms import PluginForm - -def do_nothing(*args, **kwargs): - pass - -TESTFILE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "testfiles")) - -class PluginRenameTestCase(TestCase): - fixtures = [ - "fixtures/styles.json", - "fixtures/auth.json", - "fixtures/simplemenu.json", - ] - - @override_settings(MEDIA_ROOT="api/tests") - def setUp(self): - self.client = Client() - self.url_upload = reverse('plugin_upload') - - # Create a test user - self.user = User.objects.create_user( - username='testuser', - password='testpassword', - email='test@example.com' - ) - - # Log in the test user - self.client.login(username='testuser', password='testpassword') - - # Upload a plugin for renaming test. - # This process is already tested in test_plugin_upload - valid_plugin = os.path.join(TESTFILE_DIR, "valid_plugin.zip_") - with open(valid_plugin, "rb") as file: - uploaded_file = SimpleUploadedFile( - "valid_plugin.zip_", file.read(), - content_type="application/zip") - - self.client.post(self.url_upload, { - 'package': uploaded_file, - }) - - self.plugin = Plugin.objects.get(name='Test Plugin') - self.plugin.save() - - @patch("plugins.tasks.generate_plugins_xml", new=do_nothing) - @patch("plugins.validator._check_url_link", new=do_nothing) - def test_change_maintainer(self): - """ - Test change maintainer for plugin update - """ - package_name = self.plugin.package_name - self.url_plugin_update = reverse('plugin_update', args=[package_name]) - self.url_add_version = reverse('version_create', args=[package_name]) - - # Test GET request - response = self.client.get(self.url_plugin_update) - self.assertEqual(response.status_code, 200) - self.assertIsInstance(response.context['form'], PluginForm) - self.assertEqual(response.context['form']['maintainer'].value(), self.user.pk) - - - # Test POST request to change maintainer - - response = self.client.post(self.url_plugin_update, { - 'description': self.plugin.description, - 'about': self.plugin.about, - 'author': self.plugin.author, - 'email': self.plugin.email, - 'tracker': self.plugin.tracker, - 'repository': self.plugin.repository, - 'maintainer': 1, - }) - self.assertEqual(response.status_code, 302) - self.assertEqual(Plugin.objects.get(name='Test Plugin').maintainer.pk, 1) - - # Test POST request with new version - - valid_plugin = os.path.join(TESTFILE_DIR, "valid_plugin_0.0.2.zip_") - with open(valid_plugin, "rb") as file: - uploaded_file = SimpleUploadedFile( - "valid_plugin_0.0.2.zip_", file.read(), - content_type="application/zip_") - - response = self.client.post(self.url_add_version, { - 'package': uploaded_file, - 'experimental': False, - 'changelog': '' - }) - self.assertEqual(response.status_code, 302) - self.assertEqual(Plugin.objects.get(name='Test Plugin').maintainer.pk, 1) - - def tearDown(self): - self.client.logout() diff --git a/qgis-app/plugins/tests/test_download.py b/qgis-app/plugins/tests/test_download.py deleted file mode 100644 index 6ebc097a..00000000 --- a/qgis-app/plugins/tests/test_download.py +++ /dev/null @@ -1,85 +0,0 @@ -from django.test import Client, TestCase, RequestFactory -from django.contrib.auth.models import User -from django.utils import timezone -from django.core.files.uploadedfile import SimpleUploadedFile - -from plugins.models import Plugin, PluginVersion, PluginVersionDownload -from plugins.views import version_download -from django.urls import reverse - -class TestVersionDownloadView(TestCase): - def setUp(self): - self.factory = RequestFactory() - - self.user = User.objects.create_user( - username='testuser', - password='12345' - ) - - self.plugin = Plugin.objects.create( - package_name="test-package", - created_by=self.user, - ) - - self.version = PluginVersion.objects.create( - plugin=self.plugin, - version="1.0.0", - downloads=0, - created_by=self.user, - package=SimpleUploadedFile("test.zip", b"file_content"), - min_qg_version='3.1.1', - max_qg_version='3.3.0' - ) - - def test_version_download(self): - request = self.factory.get('/') - - response = version_download(request, self.plugin.package_name, self.version.version) - - self.version.refresh_from_db() - self.plugin.refresh_from_db() - download_record = PluginVersionDownload.objects.get( - plugin_version=self.version, - download_date=timezone.now().date() - ) - - self.assertEqual(response.status_code, 200) - self.assertEqual(response['Content-Type'], 'application/zip') - self.assertEqual(response.content, b'file_content') - - self.assertEqual(self.version.downloads, 1) - self.assertEqual(self.plugin.downloads, 1) - self.assertEqual(download_record.download_count, 1) - - def test_version_download_per_country(self): - download_url = reverse('version_download', args=[self.plugin.package_name, self.version.version]) - c = Client(REMOTE_ADDR='180.247.213.170') - response = c.get(download_url) - - self.version.refresh_from_db() - self.plugin.refresh_from_db() - download_record = PluginVersionDownload.objects.get( - plugin_version=self.version, - download_date=timezone.now().date() - ) - - self.assertEqual(response.status_code, 200) - self.assertTrue(download_record.country_code == 'ID') - self.assertTrue(download_record.country_name == 'Indonesia') - - - def test_download_per_country_with_invalid_ip(self): - download_url = reverse('version_download', args=[self.plugin.package_name, self.version.version]) - c = Client(REMOTE_ADDR='123.456.789.100') - response = c.get(download_url) - - self.version.refresh_from_db() - self.plugin.refresh_from_db() - download_record = PluginVersionDownload.objects.get( - plugin_version=self.version, - download_date=timezone.now().date() - ) - - self.assertEqual(response.status_code, 200) - self.assertTrue(download_record.country_code == 'N/D') - self.assertTrue(download_record.country_name == 'N/D') \ No newline at end of file diff --git a/qgis-app/plugins/tests/test_filter_template.py b/qgis-app/plugins/tests/test_filter_template.py deleted file mode 100644 index 8b2419f0..00000000 --- a/qgis-app/plugins/tests/test_filter_template.py +++ /dev/null @@ -1,56 +0,0 @@ -from datetime import datetime - -import pytz -from django.contrib.auth.models import User -from django.test import TestCase -from django.urls import reverse -from plugins.models import Plugin, PluginVersion - - -class TestPluginFilterTemplate(TestCase): - fixtures = ["fixtures/simplemenu.json"] - - def setUp(self) -> None: - self.creator = User.objects.create( - username="creator", email="creator@email.com" - ) - # set creator password to password - self.creator.set_password("password") - self.creator.save() - self.plugin_name = "plugin_name_test" - self.plugin = Plugin.objects.create( - created_by=self.creator, - name=self.plugin_name, - package_name=self.plugin_name, - ) - self.version = PluginVersion.objects.create( - plugin=self.plugin, - created_by=self.creator, - version="1.1.0", - min_qg_version="0.0.1", - max_qg_version="2.2.0", - ) - self.created_on = datetime(2022, 1, 1, 1, 0, 0) - self.version.created_on = self.created_on - self.version.save() - - def tearDown(self) -> None: - self.plugin.delete() - self.creator.delete() - self.version.delete() - - def test_detail_plugin_version_tab_displaying_local_timezone(self): - url = reverse( - "plugin_detail", kwargs={"package_name": self.plugin.package_name} - ) - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - self.assertTrue( - bytes( - '{}'.format( - self.created_on.astimezone(pytz.utc).isoformat() - ), - "utf-8", - ) - in response.content - ) diff --git a/qgis-app/plugins/tests/test_models.py b/qgis-app/plugins/tests/test_models.py deleted file mode 100644 index 4c0a26c6..00000000 --- a/qgis-app/plugins/tests/test_models.py +++ /dev/null @@ -1,129 +0,0 @@ -from datetime import datetime - -from freezegun import freeze_time - -from django.contrib.auth.models import User -from django.test import TestCase -from plugins.models import Plugin, PluginVersion, PluginVersionFeedback - - -class PluginVersionFeedbackTest(TestCase): - fixtures = ["fixtures/auth.json", ] - - def setUp(self): - self.creator = User.objects.get(id=2) - self.admin = User.objects.get(id=1) - self.staff = User.objects.get(id=3) - self.plugin = Plugin.objects.create( - created_by=self.creator, - repository="http://example.com", - tracker="http://example.com", - package_name="test-feedback", - name="test feedback", - about="this is a test for plugin feedbacks" - ) - self.version = PluginVersion.objects.create( - plugin=self.plugin, - created_by=self.creator, - min_qg_version="0.0.0", - max_qg_version="99.99.99", - version="0.2", - approved=False, - external_deps="test" - ) - - def test_create_feedback_success(self): - feedback = PluginVersionFeedback.objects.create( - version=self.version, - reviewer=self.staff, - task="test comment in a feedback." - ) - self.assertIsNotNone(feedback.created_on) - self.assertFalse(feedback.is_completed) - self.assertIsNone(feedback.completed_on) - - @freeze_time("2023-06-30 10:00:00") - def test_update_feedback_is_completed(self): - feedback = PluginVersionFeedback.objects.create( - version=self.version, - reviewer=self.staff, - task="test comment in a feedback.", - is_completed=True - ) - self.assertEqual(feedback.completed_on, datetime(2023, 6, 30, 10, 0, 0)) - feedback.is_completed = False - feedback.save() - self.assertIsNone(feedback.completed_on) - - -class PluginVersionFeedbackManagerTest(TestCase): - fixtures = ["fixtures/auth.json", ] - - def setUp(self): - self.creator = User.objects.get(id=2) - self.staff = User.objects.get(id=3) - self.plugin_1 = Plugin.objects.create( - created_by=self.creator, - repository="http://example.com", - tracker="http://example.com", - package_name="plugin-test-1", - name="plugin test 1", - about="this is a test for plugin feedbacks" - ) - self.version_1 = PluginVersion.objects.create( - plugin=self.plugin_1, - created_by=self.creator, - min_qg_version="0.0.0", - max_qg_version="99.99.99", - version="1.0", - approved=False, - external_deps="test" - ) - self.feedback_1 = PluginVersionFeedback.objects.create( - version=self.version_1, - reviewer=self.staff, - task="test comment in a feedback." - ) - self.plugin_2 = Plugin.objects.create( - created_by=self.creator, - repository="http://example.com", - tracker="http://example.com", - package_name="plugin-test-2", - name="plugin test 2", - about="this is a test for plugin feedbacks" - ) - self.version_2 = PluginVersion.objects.create( - plugin=self.plugin_2, - created_by=self.creator, - min_qg_version="0.0.0", - max_qg_version="99.99.99", - version="2.0", - approved=False, - external_deps="test" - ) - - def test_query_plugins_objects_all(self): - plugins = Plugin.objects.all() - self.assertEqual(len(plugins), 2) - - def test_query_plugins_feedback_received_objects(self): - plugins = Plugin.feedback_received_objects.all() - self.assertEqual(len(plugins), 1) - self.assertEqual(plugins[0], self.plugin_1) - - PluginVersionFeedback.objects.create( - version=self.version_2, - reviewer=self.staff, - task="test comment in a feedback for plugin 2." - ) - plugins = Plugin.feedback_received_objects.all() - self.assertEqual(len(plugins), 2) - self.assertListEqual(list(plugins), [self.plugin_1, self.plugin_2]) - - - def test_query_plugins_feedback_pending_objects(self): - plugins = Plugin.feedback_pending_objects.all() - self.assertEqual(len(plugins), 1) - self.assertEqual(plugins[0], self.plugin_2) - - diff --git a/qgis-app/plugins/tests/test_plugin_list.py b/qgis-app/plugins/tests/test_plugin_list.py deleted file mode 100644 index 48972e11..00000000 --- a/qgis-app/plugins/tests/test_plugin_list.py +++ /dev/null @@ -1,57 +0,0 @@ -from django.test import TestCase -from django.urls import reverse -from plugins.models import Plugin - -class PluginsListViewTestCase(TestCase): - fixtures = [ - "fixtures/styles.json", - "fixtures/auth.json", - "fixtures/simplemenu.json", - "fixtures/plugins.json", - ] - - def setUp(self): - pass - - def test_plugins_list_view(self): - # Test the main plugins list view without any parameters - response = self.client.get(reverse('approved_plugins')) - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, 'plugins/plugin_list.html') - self.assertTrue('current_sort_query' in response.context) - self.assertTrue('current_querystring' in response.context) - self.assertTrue('per_page_list' in response.context) - self.assertTrue('show_more_items_number' in response.context) - - def test_plugins_list_pagination(self): - # Test the plugins list view with pagination - response = self.client.get(reverse('approved_plugins'), {'per_page': 20}) - self.assertEqual(response.status_code, 200) - self.assertTrue('current_sort_query' in response.context) - self.assertTrue('current_querystring' in response.context) - self.assertTrue('per_page_list' in response.context) - self.assertTrue('show_more_items_number' in response.context) - - show_more_items_number = response.context['show_more_items_number'] - self.assertEqual(show_more_items_number, 50) - - response = self.client.get(reverse('approved_plugins'), {'per_page': 110}) - self.assertEqual(response.status_code, 200) - self.assertTrue('current_sort_query' in response.context) - self.assertTrue('current_querystring' in response.context) - self.assertTrue('per_page_list' in response.context) - self.assertTrue('show_more_items_number' in response.context) - - show_more_items_number = response.context['show_more_items_number'] - records_count = Plugin.approved_objects.count() - self.assertEqual(show_more_items_number, records_count + 1) - - def test_plugins_list_sorting(self): - # Test the plugins list view with sorting - response = self.client.get(reverse('approved_plugins'), {'sort': 'name'}) - self.assertEqual(response.status_code, 200) - self.assertTrue('current_sort_query' in response.context) - self.assertTrue('current_querystring' in response.context) - self.assertTrue('per_page_list' in response.context) - self.assertTrue('show_more_items_number' in response.context) - diff --git a/qgis-app/plugins/tests/test_plugin_update.py b/qgis-app/plugins/tests/test_plugin_update.py deleted file mode 100644 index 118b12d8..00000000 --- a/qgis-app/plugins/tests/test_plugin_update.py +++ /dev/null @@ -1,169 +0,0 @@ -import os -from unittest.mock import patch - -from django.urls import reverse -from django.test import Client, TestCase, override_settings -from django.contrib.auth.models import User -from django.core.files.uploadedfile import SimpleUploadedFile -from plugins.models import Plugin, PluginVersion -from plugins.forms import PluginVersionForm -from django.core import mail -from django.conf import settings - -def do_nothing(*args, **kwargs): - pass - -TESTFILE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "testfiles")) - -class PluginUpdateTestCase(TestCase): - fixtures = [ - "fixtures/styles.json", - "fixtures/auth.json", - "fixtures/simplemenu.json", - ] - - @override_settings(MEDIA_ROOT="api/tests") - def setUp(self): - self.client = Client() - self.url_upload = reverse('plugin_upload') - - # Create a test user - self.user = User.objects.create_user( - username='testuser', - password='testpassword', - email='test@example.com' - ) - - # Log in the test user - self.client.login(username='testuser', password='testpassword') - - # Upload a plugin for renaming test. - # This process is already tested in test_plugin_upload - valid_plugin = os.path.join(TESTFILE_DIR, "valid_plugin.zip_") - with open(valid_plugin, "rb") as file: - uploaded_file = SimpleUploadedFile( - "valid_plugin.zip_", file.read(), - content_type="application/zip") - - self.client.post(self.url_upload, { - 'package': uploaded_file, - }) - - self.plugin = Plugin.objects.get(name='Test Plugin') - - @patch("plugins.tasks.generate_plugins_xml", new=do_nothing) - @patch("plugins.validator._check_url_link", new=do_nothing) - def test_plugin_new_version(self): - """ - Test upload a new plugin version with a modified metadata - """ - package_name = self.plugin.package_name - self.assertEqual(self.plugin.homepage, "https://example.net/") - self.assertEqual(self.plugin.tracker, "https://example.net/") - self.assertEqual(self.plugin.repository, "https://example.net/") - self.url_add_version = reverse('version_create', args=[package_name]) - - # Test POST request without allowing name from metadata - valid_plugin = os.path.join(TESTFILE_DIR, "change_metadata.zip_") - with open(valid_plugin, "rb") as file: - uploaded_file = SimpleUploadedFile( - "change_metadata.zip_", file.read(), - content_type="application/zip_") - - response = self.client.post(self.url_add_version, { - 'package': uploaded_file, - 'experimental': False, - 'changelog': '' - }) - self.assertEqual(response.status_code, 302) - - # The old version should always exist when creating a new version - self.assertTrue(PluginVersion.objects.filter( - plugin__name='Test Plugin', - version='0.0.1').exists() - ) - self.assertTrue(PluginVersion.objects.filter( - plugin__name='Test Plugin', - version='0.0.2').exists() - ) - - self.plugin = Plugin.objects.get(name='Test Plugin') - self.assertEqual(self.plugin.homepage, "https://github.com/") - self.assertEqual(self.plugin.tracker, "https://github.com/") - self.assertEqual(self.plugin.repository, "https://github.com/") - - self.assertIn( - 'admin@admin.it', - mail.outbox[0].recipients(), - ) - self.assertIn( - 'staff@staff.it', - mail.outbox[0].recipients() - ) - - # Should use the new email - self.assertEqual( - mail.outbox[0].from_email, - settings.EMAIL_HOST_USER - ) - - @patch("plugins.tasks.generate_plugins_xml", new=do_nothing) - @patch("plugins.validator._check_url_link", new=do_nothing) - def test_plugin_version_update(self): - """ - Test update a plugin version with a modified metadata - """ - package_name = self.plugin.package_name - self.assertEqual(self.plugin.homepage, "https://example.net/") - self.assertEqual(self.plugin.tracker, "https://example.net/") - self.assertEqual(self.plugin.repository, "https://example.net/") - self.url_add_version = reverse('version_update', args=[package_name, '0.0.1']) - - # Test POST request without allowing name from metadata - valid_plugin = os.path.join(TESTFILE_DIR, "change_metadata.zip_") - with open(valid_plugin, "rb") as file: - uploaded_file = SimpleUploadedFile( - "change_metadata.zip_", file.read(), - content_type="application/zip_") - - response = self.client.post(self.url_add_version, { - 'package': uploaded_file, - 'experimental': False, - 'changelog': '' - }) - self.assertEqual(response.status_code, 302) - - # The old version should not exist anymore - # TODO: The old version still exist, not sure why - # self.assertFalse(PluginVersion.objects.filter( - # plugin__name='Test Plugin', - # version='0.0.1').exists() - # ) - self.assertTrue(PluginVersion.objects.filter( - plugin__name='Test Plugin', - version='0.0.2').exists() - ) - - self.plugin = Plugin.objects.get(name='Test Plugin') - self.assertEqual(self.plugin.homepage, "https://github.com/") - self.assertEqual(self.plugin.tracker, "https://github.com/") - self.assertEqual(self.plugin.repository, "https://github.com/") - - self.assertIn( - 'admin@admin.it', - mail.outbox[0].recipients(), - ) - self.assertIn( - 'staff@staff.it', - mail.outbox[0].recipients() - ) - - # Should use the new email - self.assertEqual( - mail.outbox[0].from_email, - settings.EMAIL_HOST_USER - ) - - - def tearDown(self): - self.client.logout() diff --git a/qgis-app/plugins/tests/test_plugin_upload.py b/qgis-app/plugins/tests/test_plugin_upload.py deleted file mode 100644 index f2819ea9..00000000 --- a/qgis-app/plugins/tests/test_plugin_upload.py +++ /dev/null @@ -1,87 +0,0 @@ -import os -from unittest.mock import patch - -from django.urls import reverse -from django.test import Client, TestCase, override_settings -from django.contrib.auth.models import User -from django.core.files.uploadedfile import SimpleUploadedFile -from plugins.models import Plugin, PluginVersion -from plugins.forms import PackageUploadForm -from django.core import mail -from django.conf import settings - -def do_nothing(*args, **kwargs): - pass - -TESTFILE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "testfiles")) - -class PluginUploadTestCase(TestCase): - fixtures = [ - "fixtures/styles.json", - "fixtures/auth.json", - "fixtures/simplemenu.json", - ] - - @override_settings(MEDIA_ROOT="api/tests") - def setUp(self): - self.client = Client() - self.url = reverse('plugin_upload') - - # Create a test user - self.user = User.objects.create_user( - username='testuser', - password='testpassword', - email='test@example.com' - ) - - @patch("plugins.tasks.generate_plugins_xml", new=do_nothing) - @patch("plugins.validator._check_url_link", new=do_nothing) - def test_plugin_upload_form(self): - # Log in the test user - self.client.login(username='testuser', password='testpassword') - - # Test GET request - response = self.client.get(self.url) - self.assertEqual(response.status_code, 200) - self.assertIsInstance(response.context['form'], PackageUploadForm) - - # Test POST request with invalid form data - response = self.client.post(self.url, {}) - self.assertEqual(response.status_code, 200) - self.assertFalse(response.context['form'].is_valid()) - - valid_plugin = os.path.join(TESTFILE_DIR, "valid_plugin.zip_") - with open(valid_plugin, "rb") as file: - uploaded_file = SimpleUploadedFile( - "valid_plugin.zip_", file.read(), - content_type="application/zip") - - # Test POST request with valid form data - response = self.client.post(self.url, { - 'package': uploaded_file, - }) - - self.assertEqual(response.status_code, 302) - self.assertTrue(Plugin.objects.filter(name='Test Plugin').exists()) - self.assertEqual( - Plugin.objects.get(name='Test Plugin').tags.filter( - name__in=['python', 'example', 'test']).count(), - 3) - self.assertTrue(PluginVersion.objects.filter(plugin__name='Test Plugin', version='0.0.1').exists()) - - self.assertIn( - 'admin@admin.it', - mail.outbox[0].recipients(), - ) - self.assertIn( - 'staff@staff.it', - mail.outbox[0].recipients() - ) - - # Should use the new email - self.assertEqual( - mail.outbox[0].from_email, - settings.EMAIL_HOST_USER - ) - def tearDown(self): - self.client.logout() diff --git a/qgis-app/plugins/tests/test_plugin_version_feedback.py b/qgis-app/plugins/tests/test_plugin_version_feedback.py deleted file mode 100644 index 278454ac..00000000 --- a/qgis-app/plugins/tests/test_plugin_version_feedback.py +++ /dev/null @@ -1,502 +0,0 @@ -import datetime - -from django.contrib.auth.models import User -from django.core import mail -from django.test import TestCase -from django.urls import reverse - -from freezegun import freeze_time - -from plugins.models import Plugin, PluginVersion, PluginVersionFeedback -from plugins.views import version_feedback_notify -from django.conf import settings -from django.utils.dateformat import format -import json - -class SetupMixin: - fixtures = ["fixtures/auth.json", "fixtures/simplemenu.json"] - - def setUp(self) -> None: - self.creator = User.objects.get(id=2) - self.staff = User.objects.get(id=3) - self.plugin_1 = Plugin.objects.create( - created_by=self.creator, - repository="http://example.com", - tracker="http://example.com", - package_name="test-feedback", - name="test plugin 1", - about="this is a test for plugin feedbacks", - author="author plugin" - ) - self.version_1 = PluginVersion.objects.create( - plugin=self.plugin_1, - created_by=self.creator, - min_qg_version="0.0.0", - max_qg_version="99.99.99", - version="0.1", - approved=False, - external_deps="test" - ) - self.feedback_1 = PluginVersionFeedback.objects.create( - version=self.version_1, - reviewer=self.staff, - task="test comment in a feedback." - ) - self.plugin_2 = Plugin.objects.create( - created_by=self.creator, - repository="http://example.com", - tracker="http://example.com", - package_name="plugin-test-2", - name="test plugin 2", - about="this is a test for plugin feedbacks", - author="author plugin 2" - ) - self.version_2 = PluginVersion.objects.create( - plugin=self.plugin_2, - created_by=self.creator, - min_qg_version="0.0.0", - max_qg_version="99.99.99", - version="2.0", - approved=False, - external_deps="test" - ) - - -class TestFeedbackNotify(SetupMixin, TestCase): - def test_version_feedback_notify_no_email(self): - self.assertFalse(self.creator.email) - with self.assertLogs(level='WARNING'): - version_feedback_notify(self.version_1, self.creator) - - def test_version_feedback_notify_sent(self): - self.creator.email = 'email@example.com' - self.creator.save() - with self.assertLogs(level='DEBUG'): - version_feedback_notify(self.version_1, self.staff) - self.assertEqual(len(mail.outbox), 1) - self.assertEqual( - mail.outbox[0].subject, - f"Plugin {self.plugin_1} feedback notification." - ) - self.assertIn( - ( - "\nPlugin test plugin 1 reviewed by staff and received a " - "feedback.\nLink: http://example.com/plugins/test-feedback/" - "version/0.1/feedback/\n" - ), - mail.outbox[0].body - ) - self.assertEqual( - mail.outbox[0].recipients(), - ['email@example.com'] - ) - - # Should use the new email - self.assertEqual( - mail.outbox[0].from_email, - settings.EMAIL_HOST_USER - ) - - def test_add_recipient_in_email_notification(self): - self.creator.email = 'email@example.com' - self.creator.save() - new_recipient = User.objects.create( - username="new-recipient", - email="new@example.com" - ) - self.plugin_1.owners.add(new_recipient) - self.assertListEqual( - list(self.plugin_1.editors), - [new_recipient, self.creator] - ) - with self.assertLogs(level='DEBUG'): - version_feedback_notify(self.version_1, self.staff) - self.assertEqual( - mail.outbox[0].recipients(), - ['new@example.com', 'email@example.com'] - ) - - # Should use the new email - self.assertEqual( - mail.outbox[0].from_email, - settings.EMAIL_HOST_USER - ) - -class TestPluginFeedbackCompletedList(SetupMixin, TestCase): - fixtures = ["fixtures/simplemenu.json", "fixtures/auth.json"] - - def setUp(self): - super().setUp() - self.feedback_1.is_completed = True - self.feedback_1.save() - self.url = reverse("feedback_completed_plugins") - - def test_non_staff_should_not_see_plugin_feedback_completed_list(self): - response = self.client.get(self.url) - self.assertEqual(response.status_code, 404) - - self.client.force_login(user=self.creator) - response = self.client.get(self.url) - self.assertEqual(response.status_code, 404) - - def test_staff_should_see_plugin_feedback_completed(self): - self.client.force_login(user=self.staff) - response = self.client.get(self.url) - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed( - response, 'plugins/plugin_list.html' - ) - self.assertEqual( - list(response.context['object_list']), - [self.plugin_1] - ) - self.assertContains(response, "test plugin 1") - self.assertNotContains(response, "test plugin 2") - - # add feedback for plugin 2 - PluginVersionFeedback.objects.create( - version=self.version_2, - reviewer=self.staff, - task="test comment in a feedback for plugin 2." - ) - response = self.client.get(self.url) - - # The plugin should not appear in the feedback completed list - self.assertEqual( - list(response.context['object_list']), - [self.plugin_1] - ) - self.assertNotContains(response, "test plugin 2") - - def test_approved_plugin_should_not_show_in_feedback_completed_list(self): - self.client.force_login(user=self.staff) - response = self.client.get(self.url) - self.assertEqual( - list(response.context['object_list']), - [self.plugin_1] - ) - self.version_1.approved = True - self.version_1.save() - response = self.client.get(self.url) - self.assertEqual( - list(response.context['object_list']), - [] - ) - -class TestPluginFeedbackReceivedList(SetupMixin, TestCase): - fixtures = ["fixtures/simplemenu.json", "fixtures/auth.json"] - - def setUp(self): - super().setUp() - self.url = reverse("feedback_received_plugins") - - def test_non_staff_should_not_see_plugin_feedback_received_list(self): - response = self.client.get(self.url) - self.assertEqual(response.status_code, 404) - - self.client.force_login(user=self.creator) - response = self.client.get(self.url) - self.assertEqual(response.status_code, 404) - - def test_staff_should_see_plugin_feedback_received(self): - self.client.force_login(user=self.staff) - response = self.client.get(self.url) - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed( - response, 'plugins/plugin_list.html' - ) - self.assertEqual( - list(response.context['object_list']), - [self.plugin_1] - ) - self.assertContains(response, "test plugin 1") - self.assertNotContains(response, "test plugin 2") - - # add feedback for plugin 2 - PluginVersionFeedback.objects.create( - version=self.version_2, - reviewer=self.staff, - task="test comment in a feedback for plugin 2." - ) - response = self.client.get(self.url) - object_list = set(response.context['object_list']) - expected_objects = {self.plugin_1, self.plugin_2} - self.assertEqual(object_list, expected_objects) - self.assertContains(response, "test plugin 2") - - def test_approved_plugin_should_not_show_in_feedback_received_list(self): - self.client.force_login(user=self.staff) - response = self.client.get(self.url) - self.assertEqual( - list(response.context['object_list']), - [self.plugin_1] - ) - self.version_1.approved = True - self.version_1.save() - response = self.client.get(self.url) - self.assertEqual( - list(response.context['object_list']), - [] - ) - - -class TestPluginFeedbackPendingList(SetupMixin, TestCase): - fixtures = ["fixtures/simplemenu.json", "fixtures/auth.json"] - - def setUp(self): - super().setUp() - self.url = reverse("feedback_pending_plugins") - - def test_non_staff_should_not_see_plugin_feedback_pending_list(self): - response = self.client.get(self.url) - self.assertEqual(response.status_code, 404) - - self.client.force_login(user=self.creator) - response = self.client.get(self.url) - self.assertEqual(response.status_code, 404) - - def test_staff_should_see_plugin_feedback_pending_list(self): - self.client.force_login(user=self.staff) - response = self.client.get(self.url) - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed( - response, 'plugins/plugin_list.html' - ) - self.assertEqual( - list(response.context['object_list']), - [self.plugin_2] - ) - self.assertContains(response, "test plugin 2") - self.assertNotContains(response, "test plugin 1") - - # add feedback for plugin 2 - PluginVersionFeedback.objects.create( - version=self.version_2, - reviewer=self.staff, - task="test comment in a feedback for plugin 2." - ) - response = self.client.get(self.url) - self.assertEqual( - list(response.context['object_list']), - [] - ) - - -class TestCreateVersionFeedback(SetupMixin, TestCase): - def setUp(self) -> None: - super().setUp() - self.url = reverse( - "version_feedback", - kwargs={ - "package_name": self.plugin_2.package_name, - "version": self.version_2.version - } - ) - self.new_user = User.objects.create( - username="new-user", - is_staff=False - ) - - def test_version_feedback_required_login(self): - response = self.client.get(self.url) - self.assertEqual(response.status_code, 302) - self.assertRedirects( - response, - f"/accounts/login/?next={self.url}" - ) - - def test_only_plugin_editor_and_staff_can_see_version_feedback_page(self): - self.client.force_login(self.new_user) - response = self.client.get(self.url) - self.assertEqual(response.status_code, 403) - self.client.force_login(user=self.staff) - response = self.client.get(self.url) - self.assertEqual(response.status_code, 200) - - def test_only_staff_can_see_new_feedback_form(self): - self.client.force_login(user=self.creator) - response = self.client.get(self.url) - self.assertEqual(response.status_code, 200) - self.assertNotContains(response, '
') - self.client.force_login(user=self.staff) - response = self.client.get(self.url) - self.assertEqual(response.status_code, 200) - self.assertContains(response, '
') - - def test_post_create_single_task_feedback(self): - self.client.force_login(self.staff) - response = self.client.post( - self.url, - data={ - "feedback": "single line feedback" - } - ) - self.assertEqual(response.status_code, 200) - feedbacks = PluginVersionFeedback.objects.filter( - version=self.version_2).all() - self.assertEqual(len(feedbacks), 1) - self.assertEqual(feedbacks[0].task, "single line feedback") - - def test_post_create_multiple_task_feedback(self): - self.client.force_login(self.staff) - response = self.client.post( - self.url, - data={ - "feedback": "- [ ] task one\n - [ ] task two" - } - ) - self.assertEqual(response.status_code, 200) - feedbacks = PluginVersionFeedback.objects.filter( - version=self.version_2).all() - self.assertEqual(len(feedbacks), 2) - self.assertEqual(feedbacks[0].task, "task one") - self.assertEqual(feedbacks[1].task, "task two") - - def test_post_create_invalid_bullet_point_1(self): - self.client.force_login(self.staff) - self.client.post( - self.url, - data={ - "feedback": "-[ ] invalid bullet point \n -[ ] invalid two" - } - ) - feedbacks = PluginVersionFeedback.objects.filter( - version=self.version_2).all() - self.assertEqual(len(feedbacks), 1) - self.assertEqual( - feedbacks[0].task, - "-[ ] invalid bullet point \n -[ ] invalid two" - ) - - def test_post_create_invalid_bullet_point_2(self): - self.client.force_login(self.staff) - self.client.post( - self.url, - data={ - "feedback": ("-[ ] invalid bullet point\n" - " - [ ] only save valid bullet point") - } - ) - feedbacks = PluginVersionFeedback.objects.filter( - version=self.version_2).all() - self.assertEqual(len(feedbacks), 1) - self.assertEqual(feedbacks[0].task, "only save valid bullet point") - - -class TestDeleteVersionFeedback(SetupMixin, TestCase): - def setUp(self) -> None: - super().setUp() - self.url = reverse( - "version_feedback_delete", - kwargs={ - "package_name": self.plugin_1.package_name, - "version": self.version_1.version, - "feedback": self.feedback_1.id - } - ) - - def test_only_the_reviewer_can_delete_a_feedback(self): - self.client.force_login(user=self.creator) - response = self.client.post( - self.url, - data={ - "status_feedback": "deleted" - } - ) - self.assertEqual(response.status_code, 200) - self.assertTrue(self.version_1.feedback.exists()) - self.client.force_login(user=self.staff) - response = self.client.post( - self.url, - data={ - "status_feedback": "deleted" - } - ) - - self.assertEqual(response.status_code, 200) - self.assertFalse(self.version_1.feedback.exists()) - - -class TestUpdateVersionFeedback(SetupMixin, TestCase): - def setUp(self) -> None: - super().setUp() - self.url = reverse( - "version_feedback_update", - kwargs={ - "package_name": self.plugin_1.package_name, - "version": self.version_1.version - } - ) - - @freeze_time("2023-06-30 10:00:00") - def test_staff_and_editor_can_update_feedback(self): - feedbacks = self.version_1.feedback.all() - self.assertEqual(len(feedbacks), 1) - self.assertFalse(feedbacks[0].is_completed) - self.client.force_login(user=self.creator) - response = self.client.post( - self.url, - data={ - "completed_tasks": [feedbacks[0].id] - } - ) - self.assertEqual(response.status_code, 201) - feedbacks = self.version_1.feedback.all() - self.assertEqual(len(feedbacks), 1) - self.assertTrue(feedbacks[0].is_completed) - self.assertEqual( - feedbacks[0].completed_on, datetime.datetime(2023, 6, 30, 10, 0, 0)) - - def test_non_staff_and_non_editor_cannot_update_feedback(self): - feedback = self.version_1.feedback.first() - new_user = User.objects.create(username="new-user") - self.client.force_login(user=new_user) - self.client.post( - self.url, - data={ - "status_feedback": [feedback.id] - } - ) - feedback = self.version_1.feedback.first() - self.assertFalse(feedback.is_completed) - self.assertIsNone(feedback.completed_on) - -class VersionFeedbackEditViewTests(SetupMixin, TestCase): - - def setUp(self): - super().setUp() - - def test_version_feedback_edit_not_logged_in(self): - url = reverse('version_feedback_edit', args=[self.plugin_1.package_name, self.version_1.version, self.feedback_1.pk]) - response = self.client.post(url, {'task': 'updated task'}) - self.assertEqual(response.status_code, 302) - self.assertIn('/accounts/login/', response.url) - - def test_version_feedback_edit_logged_in_no_permission(self): - self.user2 = User.objects.create_user(username='otheruser', password='password') - self.client.login(username='otheruser', password='password') - url = reverse('version_feedback_edit', args=[self.plugin_1.package_name, self.version_1.version, self.feedback_1.pk]) - response = self.client.post(url, {'task': 'updated task'}) - self.assertEqual(response.status_code, 401) - self.assertJSONEqual(response.content, {'success': False}) - - def test_version_feedback_edit_logged_in_with_permission(self): - self.client.force_login(user=self.creator) - url = reverse('version_feedback_edit', args=[self.plugin_1.package_name, self.version_1.version, self.feedback_1.pk]) - response = self.client.post(url, {'task': 'updated task'}) - self.assertEqual(response.status_code, 201) - self.feedback_1.refresh_from_db() - self.assertEqual(self.feedback_1.task, 'updated task') - self.assertIn('modified_on', response.json()) - - response_modified_on = response.json()['modified_on'] - expected_modified_on = self.feedback_1.modified_on.isoformat() - self.assertEqual(str(response_modified_on)[:20], expected_modified_on[:20]) - - - def test_version_feedback_edit_invalid_feedback(self): - self.client.force_login(user=self.creator) - invalid_feedback_id = self.feedback_1.pk + 1 - url = reverse('version_feedback_edit', args=[self.plugin_1.package_name, self.version_1.version, invalid_feedback_id]) - response = self.client.post(url, {'task': 'updated task'}) - self.assertEqual(response.status_code, 404) \ No newline at end of file diff --git a/qgis-app/plugins/tests/test_rename_plugin.py b/qgis-app/plugins/tests/test_rename_plugin.py deleted file mode 100644 index 78ff7d69..00000000 --- a/qgis-app/plugins/tests/test_rename_plugin.py +++ /dev/null @@ -1,111 +0,0 @@ -import os -from unittest.mock import patch - -from django.urls import reverse -from django.test import Client, TestCase, override_settings -from django.contrib.auth.models import User -from django.core.files.uploadedfile import SimpleUploadedFile -from plugins.models import Plugin, PluginVersion -from plugins.forms import PluginVersionForm - -def do_nothing(*args, **kwargs): - pass - -TESTFILE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "testfiles")) - -class PluginRenameTestCase(TestCase): - fixtures = [ - "fixtures/styles.json", - "fixtures/auth.json", - "fixtures/simplemenu.json", - ] - - @override_settings(MEDIA_ROOT="api/tests") - def setUp(self): - self.client = Client() - self.url_upload = reverse('plugin_upload') - - # Create a test user - self.user = User.objects.create_user( - username='testuser', - password='testpassword', - email='test@example.com' - ) - - # Log in the test user - self.client.login(username='testuser', password='testpassword') - - # Upload a plugin for renaming test. - # This process is already tested in test_plugin_upload - valid_plugin = os.path.join(TESTFILE_DIR, "valid_plugin.zip_") - with open(valid_plugin, "rb") as file: - uploaded_file = SimpleUploadedFile( - "valid_plugin.zip_", file.read(), - content_type="application/zip") - - self.client.post(self.url_upload, { - 'package': uploaded_file, - }) - - self.plugin = Plugin.objects.get(name='Test Plugin') - self.plugin.name = "New name Test Plugin" - self.plugin.save() - - @patch("plugins.tasks.generate_plugins_xml", new=do_nothing) - @patch("plugins.validator._check_url_link", new=do_nothing) - def test_plugin_rename(self): - """ - Test rename from a new plugin version - """ - package_name = self.plugin.package_name - self.url_add_version = reverse('version_create', args=[package_name]) - # Test GET request - response = self.client.get(self.url_add_version) - self.assertEqual(response.status_code, 200) - self.assertIsInstance(response.context['form'], PluginVersionForm) - - # Test POST request with invalid form data - response = self.client.post(self.url_add_version, {}) - self.assertEqual(response.status_code, 200) - self.assertFalse(response.context['form'].is_valid()) - - # Test POST request without allowing name from metadata - valid_plugin = os.path.join(TESTFILE_DIR, "valid_plugin_0.0.2.zip_") - with open(valid_plugin, "rb") as file: - uploaded_file = SimpleUploadedFile( - "valid_plugin_0.0.2.zip_", file.read(), - content_type="application/zip_") - - response = self.client.post(self.url_add_version, { - 'package': uploaded_file, - 'experimental': False, - 'changelog': '' - }) - self.assertEqual(response.status_code, 302) - self.assertTrue(PluginVersion.objects.filter( - plugin__name='New name Test Plugin', - version='0.0.2').exists() - ) - - # Test POST request with allowing name from metadata - self.plugin.allow_update_name = True - self.plugin.save() - - valid_plugin = os.path.join(TESTFILE_DIR, "valid_plugin_0.0.3.zip_") - with open(valid_plugin, "rb") as file: - uploaded_file = SimpleUploadedFile( - "valid_plugin_0.0.3.zip_", file.read(), - content_type="application/zip_") - response = self.client.post(self.url_add_version, { - 'package': uploaded_file, - 'experimental': False, - 'changelog': '' - }) - self.assertEqual(response.status_code, 302) - self.assertTrue(PluginVersion.objects.filter( - plugin__name='Test Plugin', - version='0.0.3').exists() - ) - - def tearDown(self): - self.client.logout() diff --git a/qgis-app/plugins/tests/test_simple_tag.py b/qgis-app/plugins/tests/test_simple_tag.py deleted file mode 100644 index b09d4f5b..00000000 --- a/qgis-app/plugins/tests/test_simple_tag.py +++ /dev/null @@ -1,67 +0,0 @@ -from django.contrib.auth.models import User -from django.test import TestCase -from django.urls import reverse -from plugins.models import Plugin, PluginVersion - - -class TestPluginSimpleTag(TestCase): - fixtures = ["fixtures/simplemenu.json"] - - def setUp(self) -> None: - self.creator = User.objects.create( - username="creator", email="creator@email.com" - ) - # set creator password to password - self.creator.set_password("password") - self.creator.save() - self.plugin_name = "plugin_name_test" - self.plugin = Plugin.objects.create( - created_by=self.creator, - name=self.plugin_name, - package_name=self.plugin_name, - ) - self.version = PluginVersion.objects.create( - plugin=self.plugin, - created_by=self.creator, - version="1.1.0", - min_qg_version="0.0.1", - max_qg_version="2.2.0", - ) - - def tearDown(self) -> None: - self.plugin.delete() - self.creator.delete() - self.version.delete() - - def test_return_plugin_name(self): - url = reverse( - "plugin_detail", kwargs={"package_name": self.plugin.package_name} - ) - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - self.assertTrue( - bytes( - "{} — QGIS Python Plugins Repository".format(self.plugin.name), "utf-8" - ) - in response.content - ) - - def test_return_plugin_name_in_version_view(self): - url = reverse( - "version_detail", - kwargs={ - "package_name": self.plugin.package_name, - "version": self.version.version, - }, - ) - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - self.assertTrue( - bytes( - "{plugin} {version} — QGIS Python Plugins Repository".format( - plugin=self.plugin.name, version=self.version.version - ), - "utf-8", - ) - in response.content - ) diff --git a/qgis-app/plugins/tests/test_task.py b/qgis-app/plugins/tests/test_task.py deleted file mode 100644 index 7cf22545..00000000 --- a/qgis-app/plugins/tests/test_task.py +++ /dev/null @@ -1,94 +0,0 @@ -import os - -from django.test import TestCase, override_settings -from django.conf import settings - -from preferences import preferences -from unittest.mock import patch, MagicMock - -from base.models.site_preferences import SitePreference -from plugins.tasks.generate_plugins_xml import generate_plugins_xml -from plugins.tasks.update_qgis_versions import update_qgis_versions - - -class TestPluginTask(TestCase): - @patch.object(SitePreference.objects, 'first') - @patch.object(SitePreference.objects, 'create') - @patch('plugins.tasks.update_qgis_versions.get_qgis_versions') - def test_update_qgis_versions(self, mock_get_qgis_versions, mock_create, mock_first): - mock_create.return_value = MagicMock() - mock_get_qgis_versions.return_value = ['3.16', '3.11', '3.12'] - site_preference = MagicMock() - site_preference.qgis_versions = '3.16,3.17' - mock_first.return_value = site_preference - - update_qgis_versions() - - self.assertEqual(site_preference.qgis_versions, '3.17,3.16,3.12,3.11') - site_preference.save.assert_called_once() - - @patch.object(SitePreference.objects, 'first') - @patch.object(SitePreference.objects, 'create') - @patch('plugins.tasks.update_qgis_versions.get_qgis_versions') - def test_update_qgis_versions_no_site_preference(self, mock_get_qgis_versions, mock_create, mock_first): - mock_get_qgis_versions.return_value = ['3.16', '3.16', '3.16'] - mock_first.return_value = None - mock_create.return_value = MagicMock() - update_qgis_versions() - mock_create.assert_called_once() - - @override_settings(DEFAULT_PLUGINS_SITE='http://test_plugins_site') - @patch('requests.get') - @patch('os.path.exists', return_value=False) - @patch('os.mkdir') - @patch('builtins.open', new_callable=MagicMock) - def test_generate_plugins_xml(self, mock_open, mock_mkdir, mock_exists, mock_get): - # Given - mock_response = MagicMock() - mock_response.status_code = 200 - mock_response.text = '' - mock_get.return_value = mock_response - preferences.SitePreference.qgis_versions = '3.24,3.25' - - expected_folder_path = os.path.join(settings.MEDIA_ROOT, 'cached_xmls') - - # When - generate_plugins_xml() - - # Then - mock_mkdir.assert_called_once_with(expected_folder_path) - expected_calls = [ - ((f'{settings.DEFAULT_PLUGINS_SITE}/plugins/plugins_new.xml?qgis=3.24',),), - ((f'{settings.DEFAULT_PLUGINS_SITE}/plugins/plugins_new.xml?qgis=3.25',),) - ] - mock_get.assert_has_calls(expected_calls, any_order=True) - mock_open.assert_any_call(os.path.join(expected_folder_path, 'plugins_3.24.xml'), 'w+') - mock_open.assert_any_call(os.path.join(expected_folder_path, 'plugins_3.25.xml'), 'w+') - - @override_settings(DEFAULT_PLUGINS_SITE='http://test_plugins_site') - @patch('requests.get') - @patch('os.path.exists', return_value=False) - @patch('os.mkdir') - @patch('builtins.open', new_callable=MagicMock) - def test_generate_plugins_xml_with_custom_site(self, mock_open, mock_mkdir, mock_exists, mock_get): - # Given - mock_response = MagicMock() - mock_response.status_code = 200 - mock_response.text = '' - mock_get.return_value = mock_response - preferences.SitePreference.qgis_versions = '3.24,3.25' - - expected_folder_path = os.path.join(settings.MEDIA_ROOT, 'cached_xmls') - - # When - generate_plugins_xml('http://custom_plugins_site') - - # Then - mock_mkdir.assert_called_once_with(expected_folder_path) - expected_calls = [ - (('http://custom_plugins_site/plugins/plugins_new.xml?qgis=3.24',),), - (('http://custom_plugins_site/plugins/plugins_new.xml?qgis=3.25',),) - ] - mock_get.assert_has_calls(expected_calls, any_order=True) - mock_open.assert_any_call(os.path.join(expected_folder_path, 'plugins_3.24.xml'), 'w+') - mock_open.assert_any_call(os.path.join(expected_folder_path, 'plugins_3.25.xml'), 'w+') diff --git a/qgis-app/plugins/tests/test_token_auth.py b/qgis-app/plugins/tests/test_token_auth.py deleted file mode 100644 index cfbca6d8..00000000 --- a/qgis-app/plugins/tests/test_token_auth.py +++ /dev/null @@ -1,163 +0,0 @@ -import os -from unittest.mock import patch - -from django.urls import reverse -from django.test import Client, TestCase, override_settings -from django.contrib.auth.models import User -from django.core.files.uploadedfile import SimpleUploadedFile -from plugins.models import Plugin, PluginVersion -from plugins.forms import PackageUploadForm -from rest_framework_simplejwt.token_blacklist.models import OutstandingToken -from rest_framework_simplejwt.tokens import RefreshToken - -def do_nothing(*args, **kwargs): - pass - -TESTFILE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "testfiles")) - -class UploadWithTokenTestCase(TestCase): - fixtures = [ - "fixtures/styles.json", - "fixtures/auth.json", - "fixtures/simplemenu.json", - ] - - @override_settings(MEDIA_ROOT="api/tests") - def setUp(self): - self.client = Client() - self.url_upload = reverse('plugin_upload') - - # Create a test user - self.user = User.objects.create_user( - username='testuser', - password='testpassword', - email='test@example.com' - ) - - # Log in the test user - self.client.login(username='testuser', password='testpassword') - - # Upload a plugin for renaming test. - # This process is already tested in test_plugin_upload - valid_plugin = os.path.join(TESTFILE_DIR, "valid_plugin.zip_") - with open(valid_plugin, "rb") as file: - uploaded_file = SimpleUploadedFile( - "valid_plugin.zip_", file.read(), - content_type="application/zip") - - self.client.post(self.url_upload, { - 'package': uploaded_file, - }) - - self.plugin = Plugin.objects.get(name='Test Plugin') - - package_name = self.plugin.package_name - version = '0.0.1' - self.url_add_version = reverse('version_create_api', args=[package_name]) - self.url_update_version = reverse('version_update_api', args=[package_name, version]) - self.url_token_list = reverse('plugin_token_list', args=[package_name]) - self.url_token_create = reverse('plugin_token_create', args=[package_name]) - - def test_token_create(self): - # Test token create - response = self.client.post(self.url_token_create, {}) - self.assertEqual(response.status_code, 302) - tokens = OutstandingToken.objects.all() - self.assertEqual(tokens.count(), 1) - - def test_upload_new_version_with_valid_token(self): - # Generate a token for the authenticated user - self.client.post(self.url_token_create, {}) - outstanding_token = OutstandingToken.objects.last().token - refresh = RefreshToken(outstanding_token) - refresh['plugin_id'] = self.plugin.pk - refresh['refresh_jti'] = refresh['jti'] - access_token = str(refresh.access_token) - - # Log out the user and use the token - self.client.logout() - - valid_plugin = os.path.join(TESTFILE_DIR, "valid_plugin_0.0.2.zip_") - with open(valid_plugin, "rb") as file: - uploaded_file = SimpleUploadedFile( - "valid_plugin_0.0.2.zip_", file.read(), - content_type="application/zip_") - - c = Client(HTTP_AUTHORIZATION=f"Bearer {access_token}") - - # Test POST request with access token - response = c.post(self.url_add_version, { - 'package': uploaded_file, - }) - self.assertEqual(response.status_code, 302) - self.assertTrue(PluginVersion.objects.filter(plugin__name='Test Plugin', version='0.0.2').exists()) - - def test_upload_new_version_with_invalid_token(self): - # Log out the user and use the token - self.client.logout() - - access_token = 'invalid_token' - valid_plugin = os.path.join(TESTFILE_DIR, "valid_plugin_0.0.2.zip_") - with open(valid_plugin, "rb") as file: - uploaded_file = SimpleUploadedFile( - "valid_plugin_0.0.2.zip_", file.read(), - content_type="application/zip_") - - c = Client(HTTP_AUTHORIZATION=f"Bearer {access_token}") - - # Test POST request with access token - response = c.post(self.url_add_version, { - 'package': uploaded_file, - }) - self.assertEqual(response.status_code, 403) - self.assertFalse(PluginVersion.objects.filter(plugin__name='Test Plugin', version='0.0.2').exists()) - - def test_update_version_with_valid_token(self): - # Generate a token for the authenticated user - self.client.post(self.url_token_create, {}) - outstanding_token = OutstandingToken.objects.last().token - refresh = RefreshToken(outstanding_token) - refresh['plugin_id'] = self.plugin.pk - refresh['refresh_jti'] = refresh['jti'] - access_token = str(refresh.access_token) - - # Log out the user and use the token - self.client.logout() - - valid_plugin = os.path.join(TESTFILE_DIR, "valid_plugin_0.0.2.zip_") - with open(valid_plugin, "rb") as file: - uploaded_file = SimpleUploadedFile( - "valid_plugin_0.0.2.zip_", file.read(), - content_type="application/zip_") - - c = Client(HTTP_AUTHORIZATION=f"Bearer {access_token}") - - # Test POST request with access token - response = c.post(self.url_update_version, { - 'package': uploaded_file, - }) - self.assertEqual(response.status_code, 302) - # This will create a new version because this one is using token and doesn't have a created_by column - self.assertTrue(PluginVersion.objects.filter(plugin__name='Test Plugin', version='0.0.1').exists()) - self.assertTrue(PluginVersion.objects.filter(plugin__name='Test Plugin', version='0.0.2').exists()) - - def test_update_version_with_invalid_token(self): - # Log out the user and use the token - self.client.logout() - access_token = 'invalid_token' - - valid_plugin = os.path.join(TESTFILE_DIR, "valid_plugin_0.0.2.zip_") - with open(valid_plugin, "rb") as file: - uploaded_file = SimpleUploadedFile( - "valid_plugin_0.0.2.zip_", file.read(), - content_type="application/zip_") - - c = Client(HTTP_AUTHORIZATION=f"Bearer {access_token}") - - # Test POST request with access token - response = c.post(self.url_update_version, { - 'package': uploaded_file, - }) - self.assertEqual(response.status_code, 403) - self.assertTrue(PluginVersion.objects.filter(plugin__name='Test Plugin', version='0.0.1').exists()) - self.assertFalse(PluginVersion.objects.filter(plugin__name='Test Plugin', version='0.0.2').exists()) \ No newline at end of file diff --git a/qgis-app/plugins/tests/test_utils.py b/qgis-app/plugins/tests/test_utils.py deleted file mode 100644 index c41cd8a2..00000000 --- a/qgis-app/plugins/tests/test_utils.py +++ /dev/null @@ -1,37 +0,0 @@ -from unittest.mock import patch, Mock -from django.test import TestCase -from plugins.utils import ( - get_qgis_versions, - extract_version -) - - -class TestQGISGitHubReleases(TestCase): - - @patch('requests.get') - def test_get_qgis_versions(self, mock_get): - mock_response = Mock() - mock_response.status_code = 200 - mock_response.json.return_value = [ - {'tag_name': 'final_3_22_10', 'html_url': 'https://github.com/qgis/QGIS/releases/tag/final-3_22_10'}, - {'tag_name': 'beta_3_23_0', 'html_url': 'https://github.com/qgis/QGIS/releases/tag/beta-3_23_0'} - ] - mock_get.return_value = mock_response - - versions = get_qgis_versions() - self.assertIn('3.22', versions) - - @patch('requests.get') - def test_get_github_releases_failed_request(self, mock_get): - mock_response = Mock() - mock_response.status_code = 404 - mock_get.return_value = mock_response - - with self.assertRaises(Exception) as context: - get_qgis_versions() - self.assertTrue('Request failed' in str(context.exception)) - - def test_extract_version(self): - self.assertEqual(extract_version('final-3.22.10'), '3.22') - self.assertEqual(extract_version('beta-3.23.0'), '3.23') - self.assertIsNone(extract_version('invalid-tag')) diff --git a/qgis-app/plugins/tests/test_validator.py b/qgis-app/plugins/tests/test_validator.py deleted file mode 100644 index c96ba947..00000000 --- a/qgis-app/plugins/tests/test_validator.py +++ /dev/null @@ -1,283 +0,0 @@ -import os -from unittest import mock - -import requests -from django.core.exceptions import ValidationError -from django.core.files.uploadedfile import InMemoryUploadedFile -from django.test import TestCase -from plugins.validator import _check_url_link, validator - -TESTFILE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "testfiles")) - - -class TestValidatorMetadataPlugins(TestCase): - def setUp(self): - invalid_plugins = os.path.join(TESTFILE_DIR, "invalid_metadata_link.zip") - invalid_url_scheme_plugins = os.path.join( - TESTFILE_DIR, "invalid_url_scheme.zip" - ) - web_not_exist_plugins = os.path.join(TESTFILE_DIR, "web_not_exist.zip") - valid_plugins = os.path.join(TESTFILE_DIR, "valid_metadata_link.zip") - self.valid_metadata_link = open(valid_plugins, "rb") - self.invalid_metadata_link = open(invalid_plugins, "rb") - self.web_not_exist = open(web_not_exist_plugins, "rb") - self.invalid_url_scheme = open(invalid_url_scheme_plugins, "rb") - - def tearDown(self): - self.valid_metadata_link.close() - self.invalid_metadata_link.close() - self.invalid_url_scheme.close() - self.web_not_exist.close() - - def test_valid_metadata(self): - self.assertTrue( - validator( - InMemoryUploadedFile( - self.valid_metadata_link, - field_name="tempfile", - name="testfile.zip", - content_type="application/zip", - size=39889, - charset="utf8", - ) - ) - ) - - def test_invalid_metadata_link_tracker_repo_homepage(self): - """ - The invalid_metadata_link.zip contains metadata file with default link - value. - - bug tracker : http://bugs - repo : http://repo - homepage : http://homepage - """ - - self.assertRaises( - ValidationError, - validator, - InMemoryUploadedFile( - self.invalid_metadata_link, - field_name="tempfile", - name="testfile.zip", - content_type="application/zip", - size=39889, - charset="utf8", - ), - ) - - def test_invalid_metadata_url_scheme(self): - """ - The invalid_url_scheme.zip contains metadata file with - invalid scheme. - - bug tracker : https ://www.example.com/invalid-url-scheme - repo : https://plugins.qgis.org/ - homepage: https://plugins.qgis.org/ - """ - - self.assertRaises( - ValidationError, - validator, - InMemoryUploadedFile( - self.invalid_url_scheme, - field_name="tempfile", - name="testfile.zip", - content_type="application/zip", - size=39889, - charset="utf8", - ), - ) - - def test_invalid_metadata_web_does_not_exist(self): - """ - The invalid_url_scheme.zip contains metadata file with - invalid scheme. - - bug tracker : http://www.example.com - repo : http://www.example.com/this-not-exist - homepage: http://www.example.com - """ - - self.assertRaises( - ValidationError, - validator, - InMemoryUploadedFile( - self.web_not_exist, - field_name="tempfile", - name="testfile.zip", - content_type="application/zip", - size=39889, - charset="utf8", - ), - ) - - @mock.patch("requests.get", side_effect=requests.exceptions.SSLError()) - def test_check_url_link_ssl_error(self, mock_request): - urls = [{'url': "http://example.com/", 'forbidden_url': "forbidden_url", 'metadata_attr': "metadata attribute"}] - self.assertIsNone(_check_url_link(urls)) - - @mock.patch("requests.get", side_effect=requests.exceptions.HTTPError()) - def test_check_url_link_does_not_exist(self, mock_request): - urls = [{'url': "http://example.com/", 'forbidden_url': "forbidden_url", 'metadata_attr': "metadata attribute"}] - self.assertIsNone(_check_url_link(urls)) - - -class TestValidatorForbiddenFileFolder(TestCase): - """Test if zipfile is not containing forbidden folders and files """ - - def setUp(self) -> None: - valid_plugins = os.path.join(TESTFILE_DIR, "valid_metadata_link.zip") - self.valid_metadata_link = open(valid_plugins, "rb") - self.package = InMemoryUploadedFile( - self.valid_metadata_link, - field_name="tempfile", - name="testfile.zip", - content_type="application/zip", - size=1234, - charset="utf8", - ) - - def tearDown(self): - self.valid_metadata_link.close() - - @mock.patch("zipfile.ZipFile.namelist") - def test_zipfile_with_pyc_file(self, mock_namelist): - mock_namelist.return_value = [".pyc"] - with self.assertRaisesMessage( - Exception, "For security reasons, zip file cannot contain .pyc file" - ): - validator(self.package) - - @mock.patch("zipfile.ZipFile.namelist") - def test_zipfile_with_MACOSX(self, mock_namelist): - mock_namelist.return_value = ["__MACOSX/"] - with self.assertRaisesMessage( - Exception, - ( - "For security reasons, zip file cannot contain '__MACOSX' directory. " - "However, there is one present at the root of the archive." - ), - ): - validator(self.package) - - @mock.patch("zipfile.ZipFile.namelist") - def test_zipfile_with_pycache(self, mock_namelist): - mock_namelist.return_value = ["__pycache__/"] - with self.assertRaisesMessage( - Exception, - ( - "For security reasons, zip file cannot contain '__pycache__' directory. " - "However, there is one present at the root of the archive." - ), - ): - validator(self.package) - - @mock.patch("zipfile.ZipFile.namelist") - def test_zipfile_with_pycache_in_children(self, mock_namelist): - mock_namelist.return_value = ["path/to/__pycache__/"] - with self.assertRaisesMessage( - Exception, - ( - "For security reasons, zip file cannot contain '__pycache__' directory. " - "However, it has been found at 'path/to/__pycache__/' ." - ), - ): - validator(self.package) - - @mock.patch("zipfile.ZipFile.namelist") - def test_zipfile_with_git(self, mock_namelist): - mock_namelist.return_value = [".git"] - with self.assertRaisesMessage( - Exception, - ( - "For security reasons, zip file cannot contain '.git' directory. " - "However, there is one present at the root of the archive." - ), - ): - validator(self.package) - - @mock.patch("zipfile.ZipFile.namelist") - def test_zipfile_with_gitignore(self, mock_namelist): - """test if .gitignore will not raise ValidationError""" - mock_namelist.return_value = [".gitignore"] - with self.assertRaises(ValidationError) as cm: - validator(self.package) - exception = cm.exception - self.assertNotEqual( - exception.message, - "For security reasons, zip file cannot contain '.git' directory. ", - "However, there is one present at the root of the archive." - ) - - -class TestLicenseValidator(TestCase): - """Test if zipfile contains LICENSE file """ - - def setUp(self) -> None: - plugin_without_license = os.path.join(TESTFILE_DIR, "plugin_without_license.zip_") - self.plugin_package = open(plugin_without_license, "rb") - - def tearDown(self): - self.plugin_package.close() - - # License file is required - def test_new_plugin_without_license(self): - self.assertRaises( - ValidationError, - validator, - InMemoryUploadedFile( - self.plugin_package, - field_name="tempfile", - name="testfile.zip", - content_type="application/zip", - size=39889, - charset="utf8", - ) - ) - -class TestMultipleParentFoldersValidator(TestCase): - """Test if zipfile contains multiple parent folders """ - - def setUp(self) -> None: - multi_parents_plugin = os.path.join(TESTFILE_DIR, "multi_parents_plugin.zip_") - self.multi_parents_plugin_package = open(multi_parents_plugin, "rb") - valid_plugin = os.path.join(TESTFILE_DIR, "valid_plugin.zip_") - self.single_parent_plugin_package = open(valid_plugin, "rb") - - def tearDown(self): - self.multi_parents_plugin_package.close() - self.single_parent_plugin_package.close() - - def _get_value_by_attribute(self, attribute, data): - for key, value in data: - if key == attribute: - return value - return None - def test_plugin_with_multiple_parents(self): - result = validator( - InMemoryUploadedFile( - self.multi_parents_plugin_package, - field_name="tempfile", - name="testfile.zip", - content_type="application/zip", - size=39889, - charset="utf8", - ) - ) - multiple_parent_folders = self._get_value_by_attribute('multiple_parent_folders', result) - self.assertIsNotNone(multiple_parent_folders) - - def test_plugin_with_single_parent(self): - result = validator( - InMemoryUploadedFile( - self.single_parent_plugin_package, - field_name="tempfile", - name="testfile.zip", - content_type="application/zip", - size=39889, - charset="utf8", - ) - ) - multiple_parent_folders = self._get_value_by_attribute('multiple_parent_folders', result) - self.assertIsNone(multiple_parent_folders) diff --git a/qgis-app/plugins/tests/test_view.py b/qgis-app/plugins/tests/test_view.py deleted file mode 100644 index 67b9fc32..00000000 --- a/qgis-app/plugins/tests/test_view.py +++ /dev/null @@ -1,27 +0,0 @@ -from django.test import TestCase - -from plugins.views import _add_patch_version - - -class TestTruncateVersion(TestCase): - """Test _add_patch_version function""" - - def test__add_patch_version_with_3_segment_version_number(self): - version = '1.2.3' - self.assertEqual(_add_patch_version(version, '99'), '1.2.3') - - def test__add_patch_version_with_2_segment_version_number(self): - version = '1.2' - self.assertEqual(_add_patch_version(version, '00'), '1.2.00') - - def test__add_patch_version_with_1_segment_version_number(self): - version = '1' - self.assertEqual(_add_patch_version(version, '99'), '1') - - def test__add_patch_version_with_None(self): - version = None - self.assertEqual(_add_patch_version(version, '99'), None) - - def test__add_patch_version_with_empty_string(self): - version = '' - self.assertEqual(_add_patch_version(version, '99'), '') diff --git a/qgis-app/plugins/tests/testfiles/change_metadata.zip_ b/qgis-app/plugins/tests/testfiles/change_metadata.zip_ deleted file mode 100644 index 4b4dd6d55ffeb010ad37d7f21ca0e47b2f1fbbf7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45562 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 TKDHc?j}JZq!aWEK0>b|QKNTex diff --git a/qgis-app/plugins/tests/testfiles/invalid_metadata_link.zip b/qgis-app/plugins/tests/testfiles/invalid_metadata_link.zip deleted file mode 100644 index 76f021d6147c5003969b3eba9cc05c499d13bf89..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 39889 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 diff --git a/qgis-app/plugins/tests/testfiles/invalid_url_scheme.zip b/qgis-app/plugins/tests/testfiles/invalid_url_scheme.zip deleted file mode 100644 index e48a79d085cff7b7eddf25f693ae09f37a7bb0cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 39918 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 W72ppJQXqQ3R|>%7Ar|@Y^#1?_kB0{U diff --git a/qgis-app/plugins/tests/testfiles/multi_parents_plugin.zip_ b/qgis-app/plugins/tests/testfiles/multi_parents_plugin.zip_ deleted file mode 100644 index ace51b2c4a08190f936152db8fb07580a8a1a2de..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45713 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

Ln&4b%&7atl;-20) zm3`V-z3aOC?BnHwdp6MX+Zl;_f2QL59ER7WwHqjmCT44T(Gy6Q!l)CRQR*-oY_U}} zIg5U@ey^6l`aA9Luj?ugZojU?f7*TjA+-G;9f1GKjkQYI<}X$1vs%xA7^g9>dEJ|m zHpnK#*r2Ge@r@hh%^I(cgkX&3vSwSt<4p6Ib<{`P`X&2l3wIMeQ35E0R$__czFV@o zKLR~QlrRH(2828Rj;+dBws-rSYGdXHy#87@DTzK{eGnLpU=fY`;jVGt{jzx&&|&GU zWE@E)n7Sy8_gNXu+~KUd-7R$m@(4(lvV~0di5kVpc{gdA^?Op4-=l-=nAbY6D*9e` z9d_}Lg^53g>Ds!NJkT~^QZD;n_uO3^QCoSDc=Mhggii?P#7$RYIy0y#^YdvBVDV_} z>p`uG_A%bjoWCq=gJ<}B_z>*}AG_ zYApgv5sJs=%dAB&&~!5fWQAVkwr&XcW$Jn&QDMcON)7HQc=n7_+`+H4?6h;Po8m#~ zQ#usL3g|Zs3Nw2Px~f$8wbif>yW>9G!W=t~ni>tR_(!7oK9IKAq=9uhFX^n7%w689 zCfx)g#sk&`@P^2cew}goBA5l?>Q2ZC$HwT2q-Qb+e zwDMl({d4djuI;xq8du+rof|)FKci7*>q$N8?QYQ1=;qHU+U8@g3b?R!%OSh}mV5r% zm|_>0@D<;3kNr2N`VSjZQA9vkMugVf`9G=Tzc%K-pprvWY`&)nzDgNBnK2ZCB`XfU&*ZM^ z;d=Mr$bEC79jHyK74mc_#1`d?rebUAwXZ)#HIn&O~R^ zAUsVIOi)3MeheT+45JRzWO~f4$K=Yt3VtFPP$b7vJfYFzYzt5}*qU)Y~#lW)@WqQ489$h#}O9C5;ZDb2GVIYJD zf%HR(ZvVR%^Am?@p{S@F;3Gv-<+s)~I)q(No;_qv9U?YOeuOa4q*(C99ecI%>4QFSw2Kj6*Ya$nWHyViDtRovw&w(sKV(ajKxsk$WW@4z&AQK!Mn~+hKUG zTtD{Iki~RErUMH5FfboNCvf}`-Hqa)u_wr(+X4yb;Q4ieBbSfI7NKw{DUe#N@k}J1 za!nz|#PQlQV^jMz1tc3rkM=b(Ktx+=kr2$LEV#N$R|;y@2WbV9Dvn^uPQFJ;&v=|y zU`=_V*984q zFy#f9HIjYhGRoOC<2T}c+@zQm*+}S1rU@q$eXeVXu7wsqDC1IedN+e_c}djEvy$>x z6>ZDMK`0lF>qx3(C?B^QL1kc7tF70a8By$Q1c|G7!IGDL<&r(4NVK5@cnPSG4|FQ5 zrlvtZhf`GWin`H)#02R@79)=EdSJsXGx2)jC=Nw^)e^bXM&u_O=ya7l>Q~0OOWqro z>z^{XeBs(!mKCQJ@1tCKrlaEa#1=jRsImDSd?e96&|DFn=ks$?uKyB3j8rS8u%6NOTQt0+Qo=2L!2 zectW}5IekD#$$rK|K)vIxz6!mWTYjD&#m+G#j2#=ln>zvH8B^}rIQD@1!Np2e(vlp zlyr7Tj<4I@K{QUyKIAbb>iTk%By+B^VSXqO3Y2T$z!dc*|KsnR-hY+8h$9pg`M34d z4fda`>GswxW)`-3F80=T2FCv-qWmu}rWmwu5ry@-!*@tU2fxhek1JKEpuc~!MIh?2 zWuL?y2{edEzKvmhX@W8)-KgL8O;Ol|tpY++`%{$L)9p|vvbV2T28YFE-sTvrRcD%f zeI)ByV^YO}TlUqP8Wtsga%MA9d$XwsNv?nM<#J;{Gx-8E64vbPx0*rsc}x_WcmUhx&s(ONZuXAPNvH3N1wUw4;f$yCR^Ux#7=O zET|0oXT1|>6I3A{Z;!uwxxQX0w7fsd0iC3{^i0iqCV}BeytTEniB9uNL2tNg2&q=k z8X@O`45f&74Z%S5+_aqKgd<{!L=WK$0YPqX8lzbmADKz#tm)~=j>lZ1aD`;B7zGE7 zUUmC$J>cWAbU$dzBasPat-tCHr0Wxx2RlMVy3=C1RLqpA@qKqbzM zd29s@%h?)Jn0;4yX7M_%u`dc0lZAn76j5Z3e1cQ!P={gvbO1149nw?!J3B^*(ryzzJ!aw1&Fg_!<=EOm-SiDG-20HL{HYT?(ZdEfqtb&mdmb= z`!DusFrpcrFc2qom-l&`6&6Zrbmq&Tf=TFa#_gE7@2}E2XFmGq?@hZn_wpg0%7u8g zwu<{CNk44D!NL)N)Zk~Dvu`rdrOO1zSR`qs2)74vt6gx+5DY0K99h;LwAsyp_LK&jJLLi$-KePJhXuTTm4e9{ekhB-9EanCK4?D0XU0n zC%#$idsq$%o+!oDE*Ost&hMaiz35kzb5wd2?a7wq`2egh_rVr5d*G+@>qnS>5gsX^YqHW88T-nu!kyDeGUhX zU7gS1k;~}4P+|Sk8Es9*P@XHK;KbB{d$`=uVYHMfIdF|4j-f3d6J4*oThKj#YLq8{ zrg=Ar1nOXwg>$$iFjQ=~0S-l>p1EKebt7}fMOvBZq=jxNVjcjfBdQeK4Ruia~!W=t}Q@-n*u~ zE1BoNCH<-I1NdLG+5E>$w*Pee>lqq2nb5je{5wLsDv9U22^Asq=^aW?sU!JQKC?HM zgB_>639uf#(K&u~zEB|Bvf?<7j*oaEGB~%iV{=ogo12%rUNbRAKa1Hj=^)1xW7jPh zRg!%~uXk4|oL4GOx(O8yT_#b_DbWN}+^~Brdm%mA+`(UlwT^`o*|qTy0xiSU0Q3;R zJfjfFG_MyO(e;o?bjD^rxX=RGkNMJr_pS(~_!if=Nvs>@fi*U}k;$Zy%cndgX|af- zOI&%@Wq(#uM+D(dMmbd(hK-d`hXnpQ=G<`=hnul$-E}*3iu353aCHI*=>)E1ZvChi zEzVC9UOfMkF={aV>e0@&iBbw95_6Q}kw5c4j$eCo9J)UDmh^@vBInfKp@qT()A~~* z;yQkE@_(XjdVUZ)1D-yqKfey}K}hQmAeH7j<7nC!H-oZc9&MOkKt5}=wU*4RKY{B6 z9(-hkEErOZ)z1q$D4^I2lk<7sJ5stdz|8wqpTs#UB&9H}V2WhKiyF|ewA=RjNY`?J z`QD(_NS^b%T=%R26W^VJrz~Yg@D*Ys%Cm8lCwA}63 zzQ-#iEyyUPJ*prGUOHzjj~ZFBQLa&{VbYZHefpa%>%ZIAz8xU{=kpKqb)1YGE$p40 z{-gc*FHhsYZGZmDQ!=+%-rW5CxVP^w$^Z0}{MW;b>}>46H53{rXGaTLGbdUn^MB{a zmx-H#>gR_EzIsC`Ke#>_SEL32cbWDq)KWJu__>Mr-)-q_Zwz z0liSz#Vq6PbIEap+wpWtk9bNipd@^hCboY!_E74FAi~2=0v(xZObs&u=14H+KUYgU z+URMNN6XX*GOQ{{1G6ZpAUUj0VvH;zX12e2^5^+&Y*C;)rrP$O>PTje4q8*b%JBxQ3)9zNb4D7@?ryx-6!C`kHo;w1;8mj0-86Ok z@Q*G!uv~@jcZAg_F6^C=gOsHt%Dw`JW14!wft-cFBPHlvlZ2k~Vne<-KX(y}8rf7CQcIjgNKWxZ zSWTrj-2^b{=X8ngB;mwf4=&X8b)I{%dIj+QYr!2$TYQ>Z3`$?o;IL#MdY5*Js30Xo&uK~;t9=OQG zu#xz((FkiBBl;>%a)hJxs6_a3$7S9$ok$jgY^?*VH2nwl`u@e6wRWUz-n3&}%clj=aUJp=l5vjvSW+k2)A*B=A1m4Xy8T9ib3P*5>fQ&s|2&!Di- z5^It>FW|y4ssMgpE+sT|qy+Y5IE8+^scwN%HCrh+(nlEgkdMf-&#YK+o(-4hCxptF z<8jv~3x@1%J>f8#DXo0|_v@uuHhz#KU1d=90drqq=UZ0^iy-o5Yw^CsQnG_vk6v*> z(+?81+!)4g1r9w#`@>T8l%*`=NGTDG0>1Tx=V}&?_fL~QKsjDbvDw{1GYPwWj%J&0)xr+!87AOF!Csy8}822dp0}wQbpBUFF#n zgHx>V%HWU*E?kZVta&{$H}3_Og^KPHGEMw$^9mS6Jy9Ph2;Q9J#zaG6USn!W2M8&z zVC$e_NPt+Q4)fHNQ5Xh=JO81B@K=yEYt|DzG%f5C4JkdCg+ z`-%WS8U6W-d++o4f0RQ4^A{T@p|nUo0yF3DCp!&45*POppEur<=dnpZ!!2uL8z(cB zJ0}0Ub9V)HI%E0J|1eDD-Js{9#qz1e`MWl$zhEZdp1B6yH^kH>{3oT}-s5|-Isf~g z*Q8c|LekAKDwAi4GnENIaMEarM^j z1rS(|;)PQK4>=<3CWlu8-^t05nH@Ky`{e1i=xk%yu|i(8N2s@AqH{Wq)~30#DBQ(m zBK?C%^Tk`K>Y9)isi0i&7ZvjF6iDR@%dBFoCUVIGR$9$7%Ob|Cb(btC8nqFsA{_Hd zwuw_2<@A;&8W}$TFxD28R?0}W{sQW7y}dFcr$w!a1_j;O$N^%5)l8rc5aqpcavEC# z#U@6}?9*o%OC8j*yOdS(7Rpm*1*gur=~8X8VNgms%0#fl_ENJn4~6u(xfvz2KEs|q zh6R*H0?B-;sjvYH>4+bznrX|Z9L?1-C%qGYmduT?ZTN<(DA6plmZdVdmVU=J8*6d| z`J|KU9yb;P>!%zuQzfRy4$i|KlUw8^0sO%{*i*1b9V}Pol%OsXEuqx?^$cqq?%7Ci zFP%5ORgrY#?D_oRULGr(la-T&+t4)9mc4IZNN(@w3Hg>{s*|jeYdG3WJq}B;yr7bX z)?BzGI!?x8o+zrzc=(ac^9K5#D|U$=Asi& z8-lj%j5cfeq{hmWkd9g4d^%X}W$;s7tp4f+)*8{Ni0Qod<_et`gKG|1;{`(&(vbZq zO!kC#?`Q6xgDnoy_kvLaU2d|^P8K<3OQ!+Za_hT%|kjMX@`BnK5M$I#Nce*1YGw+O9s=-iJ|BJ-VqRT$(_&PGbW{TYT3K&HOEAzw9Cq49! z{caJ&IY45Xfl6?-ZjDqp&R(rXO+tPV@sor-GdH@g2)jM2udk;oFKR6t>RD+im7{d9 zd@*2h*bmHaYt=Y%2{U#Dqgwb6_GYGgT6}Sk#cm=Tlgq%y*`C^9xp5U2!ze(m$@1}e zG{V6dv3AR>04jwm{XimCdc9mC`^NDNJJmuq>HOfz)AaAxwD2Xvl zuK?3M(MmE~)gsv-4L4SAn-`E>X(uIlAF`^Qvx5kxQ9%#6Z;;EVq!CjAjNsE_C!Rnx zbD@{>erk;js=2lW;yIzH44VN)QWfq0e1cHc!F%_DY}K{1jvAQY z9GX*Zv*TTt<#6Vh6aevQ~#VH)i%%mD1Z zS;Xw#Pvr&H+#$t>Kcv)YY6ZSuDRA3Ux(hP~&ll$pU43{TYGJ!I?;t%l*AHnyeLwtS zY6``6;mBN72C%VvwVxsGR;kjH(cO9y>o}b=sKLyo< ze!Unk0hUJKzQ%Gn2P?Oz@J^$k_UG48c`dMb>kqP`j^) zPI)PA8P#>uMgCbk)M<0}EMtpBh(mg9Z%2Jd%|&4hNPzW)-vT+LWUsi>7q!Mci#rHp zegQN`#f{2`IBXyMasyQKK~#280T(ZN^;>lPW^ZM4Q#+$&l#ehk+p38CCq&I@i;I&+edjF%*( zk?rpsOr-U_oFpmjxqMFC-OR!77x;Di_ApOTFUCfaFe&?T=)ncj|1^CpkEod@76qdv zAA>6cJf;??3y9<&rY)_oAt6;HmCJNK@#^%JdoW5r1c0H?F7?M|cFK%b3)W3PGx{}* zX6R9$`$FsDMLh4hM3a1u9Ca<}cE zh;#}1uyPgIly`2``X@hM-3h{y7GZazTY@;yIZ@A|D|Wjgh96$_X%;Y67f~S&{duou zPSl8T4)6sB@k&yTg%v~nU6>P{@kh%TY#)7zbu z!`quXduyj6t++8}NV{t-`po2c&stPyY*qR&x)vvf{J@1N*qZpNA1)}`4t1AN zD#M8nfW=8n$T#||aJqh~S%NUE9&z0BmMIaqyI-o`HDYJ#hu`&n^#i99zk?YjEJ`<0 z*bFON5E07(?L*bp5@gJKYjMC~RKFxuFIL&c_VSoDzr35PY|t*vRjo$9$=kw0L(+iL z@_R(Lq5ej*$qiI|&`#(G>PC$apR!e;#^S`iinto=s1MWqH5{r`4?e;KxgQpRd|2#Dvz;O4PjPn*{Vm*w^GRh+s0KCVwo&)53>gdg`Fv4^x%0Nm5Q(-$6I!&xHt%sCmeu83lJn@ zF_)C@ZBnt!S((zYh+{{{c%z%#XPkRy7y1ssbU^oZS6$EW=3l`&c_)osLP}FAcitsj z2G$?N8XLxlaz+^)e39d>FD#sa6yLONjmh>&S9+%PcQOJDKKY^YmbchSnLqEwFeweF zohutqcbg|}X55S{Y01J)-65o@{Ja~6p{Ft?Ca!rC^@Hb75N0~A7rjs-JxLq038)hZ z8}QEfT6~)k6&zUs8%FzGX602Fn%PVkeCU&kDv@C4bIbs|XMRLHMx1TJP(x?LKKSzs zrF|IDXb4_k6VJoZt|aiDm9|$+c63S7RnBH$jf~r&E0lcG6_--1Ri#5gShNdQRn|^9 z%J{oZv6lRVxsz544+FGL3Ab0t6!Tpn6gf+}nHme69w#$fah2EFu!5zh@=S|A;#8eo zLJot*El3R}4tM9Ta6I$NiWn9Me|UtXCENR!jEE_z#`Ow#!~_+PlqWmV1q_LPqR-%Y z*WKgYYg0|dGxi+RBInBmvhar8mVuyvKZ-r(_YXAP^<2_kag#oB#Y}u}?a%`_7N;JT zMt-qcr9LbO=BZ72dR*-yYIs670#1`wC}AihVk2P-C+;m2d&k=HxHiZAUPC-~C!wxl zRq;TJYd6qsyo9RbqoMgIvepKaGgq7}7HzjjN9w%h%s*x6H0P=^X3dxxyCiV!fDo8S z49W1Bg@92;TdY1kev-*WM@daI1wn5u18h$xm55+a%Yd#7!)bSZok}1s)R^Up_RO$E zKKwCzrT4i&OQWr|eMiIiXvA)yrp{U4bdhmncO_bX zS#_nh$z}Q@DM3Ap>&(__00mZ6|5*08tDs)6T$~PX%7oJR4BO_X>Gq;~yhxfA8^5#W z2VjGrSV45dpNI9}o_6o#yFB6raU$w)${PZBY;NF)T=958Bi*o)a22PA8cJ74f|35C+z=Eb^pI6z5N9a@Bewyn~9Q+9Wn!g_e{+So8n?7 z1q9>)tRf;olZuumLAGP8om0!8S;r?nkFU?~ZmCjyMcN2%uI#K|8>fqj0dx`0LAaL; z#5mt5c!3n&mfO1*K}|Ch033tgBN-73zabN_N7&2@h2UX6TyN}L#Q3?mMP?p(YV&pd ziAYTdSEbPx04leNQkn5nPUS#|Mv#mra#9`_FZj+?3^T~wo=l^{Ug*3Mk%*7#Yi{hB zv=^3_iE5!cB*g`I5Drl0&zYWu{Iak_s5G>Ys^zsk3Z@N)M^TqJ_Y`iK*N7$5qLDsa z2w*+GIKe`DAJu~c$Tpdh|7tG4UHj#M@4L!xGa_Ow9r~omi{AVLRWr&+{zE;5UfnUY z4`Q+qbFbGRj7&4ENDpRsg)8Pq>wsuunYq^B_RkE}gV6SDcgf)bvPXS>R9*bXv&<)F z&2pj(6d{g^Obf9?;wBq5EsR>pbnny4{M-Vc0qoD8N`x)y zktD=gi%}1Csrd==TLhGrVu-Xk6&(zlC)FsnJhG5Kj3+PFI;9rlQ*?0pN<)tgS-%jV z+fc&1Ll!Jro2|nReFdE^AEHi3ED(??O?yt3>jBR9-453LOyv0+)$<>V{8uS|ugd@V z{KF!fn^@cb{|G;gelkL5`zE=Okp77@`rDKKU-~Di)NQ^+e}qr%kH{co{6~Zdy;lmI z&?c!UV|%26v060p_&Fq-vQ*)a!rOVh9FM zDuUf|>G?fCLqru0I_8Bk^1v?qm7tpZz;M*uR8 zCTjXbpvlE&9?Niy?TQw7PB#BAvtKKP#@IoOq~EpCV>76!c0mT2|D(aRFYi@|{|@@& z2us5l%t9f_2*`OH*2v|BabQ%{14weI@n8_XcBZbbH&0-&8Hq5)NHQts48ClUhl)kr zbgPF}@f_I9K?f3=k+f}-N5o5($AL*xBj8GpNhgr^oc|GkLVqO7{J{Q-Fk~F7b8R{m z4v4WgO6Nhs%04mLE9?~-(N+vf$qB{J>$Ogtq5D0m$v%d$REEuW^p zLoPEK&;*2+rw=bUCBsrv8Sv#wBR{$Nw2^q!PrhKVR5as0A{yg0x5Y8_i|{s)NyvKJ z57#Me*N(T2BAoI=kk{Q04;PO*XlvOqEMyn3252y4$r~KtQ%?Jm|F5#E42!bq!bk{6 zr}UDNk`hZucbAADN=hw_lqd+oqI3#McO%l>(%nc2NQWY#DDds#`@ZZdq91$h#s0eQ zIWzOj%yZ_PbAOZ4$V2k~aC1zW%f#~?CzYe7;vwfNuzBgT;?_~go(bM#BNw3Wn_#u) zHyY}YB8k7vl#K?9vgj3xHs88|(t%+SNH`*R!I%7X0YG!l)4%&S5xIVIA1 z+J6*lxDQ;h@R5CKe;m1?xI6hB=`-W+*Kh(arC=1Xk`Nfg+Q*@vbtRV0!Kd=Ki?5ix21ND4_em?S z^=%(o^^H-?r}1&3SgGf62nK8A0u2l(<;l`U-x;(9OSwMNTMt;#rPnMhx2hkx zyB*mRo6#CgC^0Zu?A_OEbn1DHn9(Wwf$A z${QrZt2Orbz=g88NSr*v?g3AndswbIP+;s(zkVYhLDX^t2;iD0bfw!XE)uDE0!bbO zs~nH-NvHM@z6$D_v|$~17NOdg2~1M&Cy$H8L#3l@QM4?K=krurVaDr-J?2i!b_%_S zXwZ}*)Op}K=a@2oMUNxOeT_10L2N1#EWra19RT+ZYuy&X{nk}L8so26=m(03T|nYD zl||eOusXu#rr|SdGHa!?V}7EwSJNFWM=6!wr65_J-7UN|>u5HgT=r(&%>LMOE@OZG zTZ5;hE&fwLR>V%V49~Jq)#p2xa+tj&$;o++lU6^(=xfK$0MV9<08RC$`{vI}`cF!{ zCkye9^3(sv${xyBxQ_tYI!hs6YWU+GETDyriK#2QgX0g4-``vdM1_bewr~>qZg1-Z zGK!BD=cCfhS%~lCQP0F(u`M?oO1r`_c;ulOCc}>mqt+9^8@yL6}rO3j&US*QsvLWg{#HTsnahQ}7YSfpk0|L~`Y#fKuu_?70-0LQ72`l8kAF7{fKf(6w9Vb(e z9i%R*=|8ve(7rwQHXb?+s?6H=Dj^%aH$*0w>i45&Y-?lohfqA|u1rym2Mxsyy_?4_ zknijeMw|m`C|%FUB0vp$dWF*$qx4O+ac%Y6P<{EBoa5TQZaX)RVv1^;!&XLi;P!5} zBvsBrVX*d6blL61?CS-=$6~Az%^k^AoHTWL&S;jaIdxp1;eo0fxA)2n-YHcVug`Q% zc8;25u-H0XQ*7x_VDX(jR=xGjYr><&Dk5{PM8*7bt;7AS+x@$@=m$N+2O3~!I$jxjy)kJi_PFwFd zFqambINeHtiwGq6m{& zYGKfzrNxb^!Ucf8DW9*FRzb3t#PfAJlrC2aFM#2K&G?X0$x9{cA)pk8b+b|>XX93V z0dBncBtnolC9sgJe8Wm(&Y_sz+yrmD!v2_Zq2Oy_Fx8p>#>BOgotKqjWDy^0S2-``PBRa70b>{OJ~VeWE!4yq!xEV z7p4Iwvs{)SgKMb@USh*UQKFgG(M4!!6t%&h@u#{9pJq!86d7v+Qmp!4T#1ZO*A~&- zZj;S=`g9_Ja4L^n>Ud_Yl$XDOKvO@MGVNOWN7dUoDHei}DxYoZL)COEyF2A)s^>as zrik5?uhG!6e9Dcg)wW4T)X&9qVR=*Zvb~cQB#x~BFyeZ7E&uLZ?Q8EfRqXea0-XYG z6dQtxwoc%c9LOiAV?;w`u^AD=slQCovCH3&yL7Yjt4xlj;gKgS4!#(+CQ#o5euBt7*gAY*w2d@gXhRq}8xp@`kXf3A; zJZkwUF0;sebOtOI`W3K^;Fz^mPNQA`rwoyW648z+&*G^qY)~iEbmb%%5|>`l^%^Ij zenXzAf4OpA?zwNm%Sy@S)y?Tx?&v09YGAsqzjK?}n3}NO7*$~?DjkoRjG+y>d%t*O z?CXwX3UM^1$B)`ZW94-7;%b8MRZ(b1o(m(*NG@oFjNe&BbDcFmuG{V%?!(*J+l?F^EP02P)ZZ2r*TQzQn?3Ow zvB;A;0Z4TngO`Zx>jgaK`wZy9dV8QJ`fWML@`x9JpS3g_2z~w4938-{}dS8Z?0i7Ld_{Oz2S=Ww zF!@P4J&%Fr2#$r>ar{oicDYqA$;+t}UE7h1YPtmaDGxKU)<7VQIPW5hU5V&KtY)S6 zjy7A3)V{>K&2rTl<2*U3S42Ae zS`H%B;pj>?*$J^#nobr%kBk)LmwEn{Di^P4_5zt?=^|KmdKqlU985IgBeShjh5T!yLXq@MZ9`G7MAAos=GQ2tyt2@ zX5;oq(h~wFChAC9gf)*3_8A2fXl3!e`sSF8d66_L-?9#13i&SWFo%^;b~0JAN<@=- zyI$hH z=2}F^sp&vSYsz3O{PtF(r-_h7cvCX{MnQ_*>o=Y;xVI0HJU49~zoSb@Y1wA38#Ir5 z)dHj=a$QrtCI|pX*9@^}VmiLxQ)*nNKP}RXx+q_uOhJjoWVlTtkN$^HEYM(Wk4r|bHW!o;4 zIa(DCtVyNWIXWh2q=4OIyRL=hdsj%`<)zG>2%3z^#!kb#?~+NnqFC;cB!5|^^TA~D z;2vXX-pkqM>s_hURMT%N5M4~S0~B2CBT?lvI`II6;W0Ql!@A9naqf+vvF(nM4%Ugv zhs}03qjD*ZB>{-X@8*7^H(`9gu*_WYagXi+3jpi3@CX~+tA(`{zhFUt&@ zm9pa<4@bvGtEoDiRc|S@Y}}iqQQ%PI7!LE*NlxmG9*MGWu)mV1YwXP1v^mq46h#2K zrwoL5mncX@WL(3`3qh`_U(|*My0YD4ITB2Dk8mv8E|23}yU;TGxITe=6K@UAg@~Fg zPL*jWusR6vSwQ+z6hH!5tkhj0*+XStyftl#hqM0~P4j~){tg7M>?Uk2A3r*V)jdXki=J$KVXRZPO59Rz3XdG_JsbeK;s*v2O2o!utE+=8nWI)n94as_V$jq}$XVwi;sM-doCuwXJ-$y~D2pNPm$fY}du= znr6YFCQMMwtLb1BYl=ctpIa}5`3Y5^eBLg9{2kYKWYK}Vy5>}S* zTW1UT@Kj~4!M2^=2_f8rKq6YMFB=ds{>@!=(j4C(+}4YH`x35;+_e5kbp&m1E8Lxp>`=L-a>9Kvp7cRX%G_PK=1X!{zC%CU|gFk_W+uU=}#jG>kPA`;lfud{qPCQHFl2m&bVO&0}WzmS@cEOKV~&O*sq1 z7no7_FR3?Icn6AiDWscc>$68oEw3GptUAZYz1c)3?4H}b-Jzi668GqhZjsLiDkmi+ z&uGyxsqrmZ!ZU2W07U5#=$63yGckL4p9u5lxMEhDofH{~#SbBf6e{>p6Jh$cW(@PoI_KCw|+5E5r zv{$#j`&YMhY_rA>RrwvG$meoxb@%13*QZUc=tu9gC~tvhuS5vX%GV`6JDRZMGo$Bv zwEQWPXe(7tgX+Vq2AN1&F%y^zeSr*xMMme)oFm^~IOzRSGgY|3)HjCZ1A&%|fb>2O zJ>uHU%T*)_9=p@&C_|IdnAfB6-)BfDjd~Fs4bbr_M*ss(HiDcK|D+)-Z#LhikwRZD`G_#rFdL6)vhlxA!Fy&8UYm z)*16+v&Yz@)y2S1mD~ir2FV4NhD4I*Oa;=7za8=a-v97Zn(m4J!N}Rd+T@Qb;n}wd zKgrU-;6YP={{VUckqYStY@W28&VM%)kRKS3h}0i{xCn>h&|fE|V?nki&QMSk1SH2i zZPQ<5+Ms|xs$W94f*0APjtr2?%PmNHGT6m_%fFu*w8P*e?0;< zKo<=T+XCX>VYjpS1Mq3j-10|Lzkm+8CWCm5NZ=Y>gb4?H+Wr&o(Vx2n-74b7qU|Ct zC8Q75A>Yez=tvL&rBg@_kg1a)Bq_p>{p2DU0ev zE}`JS?JvWDE6Gbrt7uA}^mSWT-r3`L+SkxI_U|9=Kez}8(AihZ4TlSD|Bos7+YvnN zXn3ta*x7-~^DF8}*PO;)+qs=TQBLc;!VAK|(gg6HMFUs$^+X;H()rNrpK~TVG|dHG z$P1PSDDX#~6IrkGczz59lm}jK3zp~L&pfBYYDsKE80lX>} zECH?HuLLKP;1`XoKQlpBJa|zcSf(J6-!Yvj5d@+8!|a9*6TC7DES-zkS#)sI?nI~M z&t^e+;Kfp4d6dM@;&F7kbEb64-?70K;LEYYGSDjg%5Y-Yf5qAVjsd>FG%Q20@>vXUGXSpK^xyHpS8#^qYgGA_ z?~HoRf2M&xNALwAVQD&Uz|owBJ3xT{@WeoY;Y<0z0=pW*0sp+r`~^0AH5OQGJQFx< zxY{g#*KYW752x6h-k9ca*uNHk_&Wmll;%@{67K*I9Kq@H_e+BFc}%B!%fFjE^#AFh z3cAr(+QKpYo49$h3IE0B2%i`WYr;jlGl9=alZDvu7gX@+rm$4x_GeO^k$n1hOz^pX zuuNYZ&t&>F6Y$SO&?f^v8x5AI&l!&B^n7%Zn|9uypPs|u^T=S)9q+@TpJtW)9WZ>5 zBrNc@4;(OT$mDs@XHQi46REZz9P~eNa)0h;=w1pRB?sH!#4}L;iJ3bO`bUGILkRn1 z!8f?dAFjbCZ;5|%wuPd?N1VYn7#aL4>fbHMXSey^caYN+cv+lZFkw#W!u$dv&%NRY}>f~pL=J{{4-}}ZoajXFIibT zdH33Q^5)r5lmP{U2KsA>&~exJ$DjYVf&sz>axrsuF|f5ab+utoQ-uNoC4@6D`%7Fs zV1R(Z9)N*>z)=63uJ}jEU)yg$5xO$UndME+WS~Gmmykd}1b1Es%5zT`gkj9(5zd zHIUvX<&7L)yrA^1=h+)1Z|8QF2ajtUqpVNz5=5I&8@~f1KE7^5@$P3{ zuxs5Gr1QFIK%>bj?q@y5mkG&=D;=GSxJKB$sDMoHH}$kjc}yQ@>kA?bXjt?)ZW4`6 zBE{M=JF1hKVkxR+6ihu2u|!}E62M#|y34A+Zg&qB+iGu$0UL983$uh764(I(1?eyS zYMeSmH>4F>33i8kC_T>dMHRtBTC&&3UvnI|_Q)}1EQD@4)6tI#R|oY z!W$eJ@{{G_AyU#KS0y0KT-xvQp7%r#9%oh^ zKLMJN(ZAmhkbWa{ zQ#vttw!5NbO?1BljOqK`%PyvEcEDg$`JC|4&M}jJqFn@|Jry6y zgbX_Or1dhpj^LZS$P=ccp~x~FOutDW7>?Haalae*>zc$C@ZY0vy(^Yf91IAE4F(7Z z{qND|U~J%GZ*N0yVs7z22A#T`{RTT?*O>sdfokjfIbU4d1;f7}wr|jJr zc82ZerpJ5D$tABFQaz)OnX}!(tr|I#;}&i!(2;NN^3ocoU0Ez z={u*!+0o@;0zY65aWeS^dyBk)FGx}c(vy1LlP<=WV2|&@@G8pa>^E-RYmweXrbFD8 zWe}Fqr|N;PkEWo2H_!WXxNd;q$%V&RIs1wcS+5f~hDl1r!g;0>{oJjQt(}cbG>G&foYPgIB60+jD4MocQpAv>^D&C`5bX zL46BmCU%2#D46h`GqNt3j5FyYnXGQuI<^YWB-kSW>9_05<6xh&JYCOI#Ug}zW#SvP zU8>MX%^=#e(G|mqQAqH}hxK9XKb82aYPw*2}rD5|j%E3n<>UVm_%1ovI^sX{jfNr1<*dEwrLh z1jDh@$6yPFRx-*FL<6qmRI_#+j8eD=LA|9OCXga0M3S;*mRlA zxP7EZe-oi@0tP|4CN+GaV?UI_9HZOj$O$=uhwOdy;$OZg_YB%LPed_GgM1~kvW?Y~ zXPnw3Lo&OgsauBj^C@}3vVK?ZI^b8vNX!!Z!rWSkei2odQ<$k;EYZ^XxvT7XU zgy21B&C}ooAD;0P)a$DZO4)%*2PplnzKkWD|2EnwRLfd5k!8{)a^G0;JU!rc;@5f8 zO)#AWJ9q9QmtSiyVARB`BnT+Q8EK3@)ygk^q}H?ZlXJ3Ir6t^CE+C?+6Va}k%^IvH zJ&56h>lu|JK~)O%#`S62Fez~T=k5HUs;hyIh5{t}eGXC;7%y&b)S-T#=9 zzAMU0z(HgGr4LR@QdIfR7UPd#euMb4M)EOt{j&jfR+bP3s+qz)`LhFIEF&Qb^!3*- zud6ia&k-mGNiAm}AQ+Ut1Q;kI3-ixGNEazNG00UAG;}1|Y{}pbARy34DN!L+$>Hi0 z8B1L?%whkd4R4FXDOq!Jsd-ZJl3&k)M8#k~Dc61w7QqT6M+^s9nrMzr7)KLf)>#d3KuDo~ky;l^b&z!yln`|Y`fmXRcLB%p zE{ZM3T4!bbAj57x?&joC#} zr``E*$&mHR9|rJ>ohCngG07LrFPd4ifnz}PX|$l!f}UPd-Gx92r?Iyzp{QkIW*=_w z$GGzKiDt2wMs^^T1}s^LFU>nRl?Pd{id9ptW23SD_!7Uk;($x+LO>>ZJ*P1MfX z-6pYhX>1SCgP{`hZA}hJZAQEsLZ-e_LBckv7D&Phy{UKC)P7D+%RMP^m5Be{dBZgh9VQ8U!2h0kUeq)gd@B z;OEN>`E%S)s$cxA(O7J**4}=+V`8v=QcU-nJWr3T@ey2LxLO3C>x;Yw*)q%@q7CiK zug!+jo9G+yVzScIImVyzLnD3iYi?o3C(wHtO}_j|Tt+HwEOKS7Q(iv7VUGc~1ma4p*-SVV<5zzhoaEvygdh|B?1(jK2!Y0q`Fxj3+px&G}Q zma(-yD6u1 z{PR)ce|yHz+Ol8gMD}|!5MuSlXp1;tSAbyv(ao@P>T8yB8r3I2hY3!l%*4@JNL1Cm z+WCBX3tM22wp#<;4PQtNV;R`t*qW%5uzB?Qg`6^G++~=|&6_Z|P?>n{nd+~vf=FW;P3aZ20XPjLaCY5TE_P$q3UAO{Lnn{LtMRV6N|{K0e*Evu^Va(5M2#!uLTx zzn?r=U(K9fU2mx-)ipF92u~EyrS`DI@P9i(sJ1K^>qHKxxXZ!{FV0*$+j!dwm&r7T|Ac2#;Z{TCl1Hru3O?M4Tv_ zxNJoisPC%K?1w24qmEg0Qq$^lP>)#0?Oi99YVfUuWvBr8VdO>vB;Ca(Yj!!jSZZLc zczE8deZXu)U`h;4f*J1X1;WbR%06bfO6KWo;5|T(*62*5c7uY_OAE^fWqX~O%^qe| zO#!?4(2V6M<2@^z(&!fncdiu+3s4)}BKVoc6Oi^cfyZa2ZM&K)x%=$qZ?VVYosY=T zMwiljeyOin0Oj0&iOFsxley-M9C4=?4DdZMj4FP%*xKffQL zhcPFeGu{K%@v}FfkBT%X00J$2LhOl_{z>0?92aNQGwT@W_g(s-PQAx(#J{u4yEqax}X%pR&`7hMaovr>T|_HYd>+dyPj+&>I-+ltdPw(HSctm!b)|IP&+bvgf4l%j4uco)m z&Xe+JzGQsEv=qw!9S;4j6Cflahvq|5aFB@DswPDY-ddl8acZc>c%(7d24A>V%HN+0jE z_bDW_w+^TA6*yw8_h_H3-Va%i+YQI~z@wfI{B9u#4G&$N?-hGJDzImy0`OIOHlnwZ zft5a$RUf97)(TR5aU&GSqOUI&qdHuxP0{4RL6u34P+4iR49r+J#hPLFM;J zK320;;NXsRBo;O&69XR#6^NUQB2dnw;w!?3c4wcv}Uf83OXLc z%b;QH$TCr=SL1IVD7kCWCv+VnM0BN0Y8-aCP2wk`%rw!GPOp@H;39wkvgu#%YhvenRSfzeHR8 zX(i?9@*JZEy>`D%7H>`Sv$27f5W5j;}XF~R$Nnf7^i?2olA-N9R zpnoG5RRESkc#7?LFg5+BdazF!PK}fJx6aJY281L!q7CYBIJ}5(sz4i@w#m+&)GHdO z`MR5-`B3p&{TQ!Z3L`48J}zu+j@7qziQ>J*iJ%=)ypAB!+)^nAYl;Kdfd=_SSYk4G z1YV*iT%sQ*evKA9r6EPJy`Y_QM+|G2IFt>#T3ii7y3l_|SG_Z~8C_Bl`!P-8(YW$W zn_BklQ81;dwENJL2Y+^W_B=h8pfL5SeX8zpr)qZBRNg0{qL2?Ymgh0hCJ+Rdt&s&; zZ2aNp^jlf(myz>^QMj=X95kNq3WodZW%hkAZK0iBSuJSa)A5_^oC^Vp>zS}`a5Bt2 zE>p7Td}ExP&^=u>puuaSsiTcLY!_0pb8ihV?&&1YRziBZ>;{5hq31X4BhuWC$9xT; z7)7B5$++*z&<_c*gxKGeIFy@}EDLCCA8aR>vb*#M+S1}uK*=+?x5RK>&>|`jVaIl| zU9S(^_&e?Cr(Rux?g&g2P|GN=?5=qhFsNa(`J$X>E7?C>q^+Q7tGwf-aulkLElkVx@biE3yL^jL&F1LMk^ zQ}0OZZd;B$`9XI}mo5}sp~8MRO15A$V8d=`)eBj#DnFERNb+MY12P;rG#|0S{l4<|M)L>!frer0KGP`;zkVsK-J? zGk1x36%UpDxAveJth!7E41tdPmUP@7N}Cc|2I^GWLZzEcv2X%SDlWY}B#54$3O+v&@gy0_cE7Jh=$1D~NI>|CIQI!k4`L+H zg3>(&T~4k{GStwgX&U!ydXS04`o{@ZeS`PI#L!B4`+~>-7H%t>=1Bwpoq&!Gb@6Es zVaM*+3~E-QjYUJJcfw=Y3ggtS9Rn#EsP*9gPe{e+WUE2K&LM#JXJYs4k6`>AQrVih z7?~Qm7}2|Uxcp-RX#!l?Oz*!=2iwMXSb++`ioLx+2kQLPPaF#iR1HLgrIO8v7v5BI z3q3-MA#c8Wz!HeW+`vD~?_rlPGdV_)G^vtPmbuW6AcNIP zNgvU~<{7Y|M>e_PHFMlBmf)x`gV$A?(w{U9bym;xIPjREAg$EA-lDsSB(y9A$=t!I ztMQO|$`2c~X^#VlyibWw`oFOu-%0UI5c1!xEh{A=Ca)s)4@3VNuQDY1uZGUn)p1@QN%6Bl`w~ghr&XK1 zUFjG#7uV4dlS&>EEkR9eC~OHZO%jC#(Pbrsqe=Pd-fD9x?2EEsuaUJ$5PUju{+KE@ zo7l;nF0{DwWWSHZ($`pgOhX_@ zwe!?Uu0;3>9{LolC3|_Iot!M{2T_~eC?2=6y=*9;^ge8NK%=`Uw)cCzzu8l`68Ice zAeg)>ic5p6ZQel3qjNR49+%Trb2F;wpAagLx((GTs-d3O($fmIupqP$F(_Huc`yc4 zu<1|EwM}NTMH%bc+rUF#2c_30`J^1uB&j$0>8p~DHtbH))2G7;Jm0CVnX-&m@p9@yxJS-wU329QoqC^Y$ax|qPebK(Do&Z)#ul4WEk|e{ou^%XlFh2 z3MV;GOXpP7H)26=p0rIjJuJ^Ly0SYGnvO^+@-({(wP~(Eap(!!uw=O_>^knS8&3N8 z*^q3d^Qe}u$=2GIOSSn*I4!zV;&XUt=+A&Fpy_VxS2-0BNb?Sya zMe&~}&x|I&XkYcWbUdg=rqs3K7W_hul5|6VfFk^evuGEcKPtld1id}t+r+BFP zZW5*pMU!*gl_%!_VQDA+aBy{QLycWaS0D3EdBHqq-)AFR$?C_K^v_`2DB;EP^|ix8 zxbxp@`#kj_o@d`rW;H}r0cp*&C!*UyWAzf+>lZdMGXX06OQmJbklL70?B$X??b)uk z)QPbRq|A)vZ9Qi%ZTwZDT%Eh)PWSimG*os^j+@2{L`;T&xkLNwewzp@#(@`yT*9CcE zcyw^)JZN)`d1*8}wS)010^I2Gb|kT>sVi(0zD^CmoXj#9zu{5Sc(Lji4sG7r<7mYO zle{{vZq!QO^eFF=yYTk zCdRz1l4PiU%QQ})mr6?^aDN{A%&=+3!k@Q@r9VD>StHH4GhA6qM04YLu9tpUITxRp zGbY=t2(!>Eu85pTLbX2FMr_LnLz~cJrxEe;rZOL9xH@6qT&?eW2wD=$2(uqO1E(7l z_NA1_8?4U9^|0Jq1aXYmKxUw+2=*`LR6<13Y2qtkWCRuMK82gBK41DUo)XY^U`+B@ zKje35T<7|70cP*NS2HPacx;yM)7eelV_@kB+HuZu!ChrjeAI6ERYAZhUN5+ZmvZZO z*h_^RDJSW6uDSF5fwPfO3jv<&b^7t-!JsM52Z+<&_q|3cPA~LHH~u^4T4yf^*-#nY z)hLBcII*N@D{q9LPe8IBEB}_}+Ani?kRkTKN{ARPrIm{^7{_|{Kw~ug5zjlX{`UPH zAX_a#Wr@7nl`XJ4+$Efk!VyOj%2G>I7PGT z(9N**6dqr~g;%G-d2>XS72I*)b?ij|R74?~FznCX@M$_<10~up_{4Ryo?GoAm=Yr) zzn{q_B>kR4c`H!Pa>-9bt#eXyJ_+3=Q5hr5L(#vZ&XFAuvIOmD?_H)2hmEZ8s)Kuw zifvMoJ7gY86ycR|M=}>9uzIA>!MrATe}13)M*qz6U1$nqO*2*19u;+OLHqg9Brxj`kZ%j%ajG;CN~9P1^U9 zIJ?Qoy<;~{JpNen!%N3#f?WNu!f8Hdo-EnnfisKONP_oU51vk(&HEG3)rV3UalK*1 z)C%e-pm1cMd-de4MNh<<5Z1sIWMj;sGuzZM=A>rUZh)RCYLsq+%0wq{P$(_-GRV>b zkv^0OABaG#G`$$Qg@W)7mO~pj%T6ukc?gOF?Dtq9NW-UZu6%p*6Ua>Nn`;b^Qd(f* zCMx`H_h@v&Fu^PLXig!AH_XC*&WE&~Z(=e0W>yYfHna#dSE`FTGMTnY4Q`AOeV)l7 z@dYzPl*oe>`<>SCJ3d02p_+=4BNK-0t_{T9*;@nt{YtcbNBSPY&HG@6Ig80aGx%b1 zZa}hC21g*4Qv8Rsn_tm+X92q18E|iXruCF`JoAI;EFtMEOAq`YeU;mh!fi5n=gG5; z@MD_Ef!4tdVsfesHG^m@MU^yZ4GMx2L$~@C$W9e9l+xJ*l7d|=;gW8%Dy_rgeZTt^ zP&1#=@&+MpF}MOB$?T<=4=wwq*sD1MGC)(#+}<~ju2bA(>gbN{A@pNLGPMkS-0V0{ zkun&17wm(gWffZ*$VcR(xYmY;%N(Y}jC?1aJ*bW;RzGqURzUgmj2<3X|^O65MzCrvB`D``G;xDp0J5R0)y z-q?)@ySAT+C;c3&n!Mw=GfU3n9Z_P&9+w`xw(yz~F>vQ8@Ay zg|Ig&mLxZkv?fK5OnAgRN!mTtXMD&oC52vlN)g5B)Mj(UZ*SHeMUhC>X`romTcS;= z04~bJLl~--vVw>n+X{hRUJmG&h0k5`6ahM_#p+B^i}-j?PtugMSig&*Hqr_)Fg@3& zr7E@xWO|MfZIOyV({G~+srM~8D9{RXIYHIEe(t?9^xI(xAOt~xbUMb|Q{LVUP4?#; z&m-1MS4<9iy1@-V3A>A#lQp8GqVYOr)$)``ci4mgkqolnGv|PoP0M&IfOT$-_6|69i6u)Iz=%vc249 zM)3$Uw5_gLn>Cr*-FpHKd}<088HW25EFST#2q>ZDc@t%N=~jA2Fr0JO|Ew_?JC3%k zW$gg`q`Y?gdH{z?)8RwGL~cfRv#B0!$_`lY8`E|m7@*iuorY(EY)s(=FHnwxu8G1Z z3L1x{fYebe_Mp{EmaxK9aJ3i3Yej2?ms+20xhOroZG*WoM zTV#Amsl6SH;4Cez4B)jL`5>!AV;ZlmdP&R*)pWKgx3eHft6$$o@DI?SKRz1lMAwpo z3ne3)s-c!PH9I}e6oJ;Z!=10XmuD(a$flyVCtpOaBNEsC;3{}b zRfKEgk__b^rw#8{wpzud;HX12tbRhhM6v5*TCkc+%XtO1A5rq%g=|#jrKA{ZIbgE{i*p{Y8)!Gue8Pp)?Lrf9 zt|Mxr7Iv{PAZYJtis~axxriQ)f0EPj#7$BcBSy<6(VtO%MX&>FGH0CV`|*_`AQLLY zaetfPK3-7gnkMnFJ-agCuGL8lzHJh8tiE@R+ah2$};JYYb+Vbwase5(3nI6hy2-lgHWZd zWRyRJUPlhRggTkp0l@Ge{zXkiV~@*pYEfJ-%ADJHzF>f|0ckdxvSD;cL)QCW+x47M zy(b^5)ym#7_X#}eQgTQIbBYp3Y+=;TOF!I6FeMty4q7{8Z6IMqx}a0+UZ&(+-aR<+`V3u& zECc37cqKt4)#kJtZ8z?f`Eaf=f88njQb<#>golh@*e?J5?!?rfLHE2@%xYiZVuCjO zPAT+UZK82=K9s{(PNi6IQ9b|0rTO!*^??S<8mZDQjj9j|OaW^m`$J`@+ReK_;7o#V z!K^p}TN_Ppsn}&qdygrjkL}^joxG^>)OnGtE;bm?+O9@VI=#D|2Coc>GxQwuOMt?Y z4ZS?OYD8{a3I4Y2tDSYASk#|>y4PHUA_xTR_RD`E3Jn7ad|VW&#$^D|2#~rNc`Z$) z*4(Pi1BvHK6MrUOoRzXI9`Lh1qxXt7>Xc^oGk0r{jz`Zv>xIvjJqD3A-44oHzruaF zh9MXS7$T{w0Q?XiS6I?>?q3I6xsnwDDQ@ujclYDWSOrNs?|vHsl&F0?*sMV%Z7wPn zbviVB)PNs^-BqX|?7;b0rTY!mEKZh0s`U2~S5(}bh-8JXwM7)NN=L}>N9Box`-~SV z3+Vy;=^<=p^&(F7-qRuo3H1_+{q`dCN`_8-&LU!#mjD%uzyPLFDdbCpklgrC=G$JS z{p8&kPJ&rrb!F(815aMi!f&=Rjx@1B?<*4xLcMBfsB^-B-_1UPQ9N2Pnlj)kbf1sb z%tN}!a!UZcy3q^FgbUjf*72@WXS9JZ^5dRej_1=u7n6xqj+gBE0vT09+`6~-c(`Ax z3t+1_LyLf!4NDGzP0iQV0d*#OowW0nSk*hi0&Nk_FH$=~{1nH@R!BCqycQn1aYwS5 zI2y-vHIUlm!EEK@EuY*{(PtE1qz85qOWU=X(r7Y&&^^_7xG3T*s0(DT)O&HON?F_mHg- z84?ys!`X#o$K5j$YNO{FMT3ro%cCkyTv`4y7ys^krR=1;$3c1lbmg_>WbYATuZ$JH zlsBqa3uwzql;Eczly47-t~>U)j>1P#7)CnAc;L{IqgTOxEpjqBQ%|8Z5~)?SBVqTs!+z9SXPpUB0yDqi#T06c`!n~9h*7lcs~|tjd{;v( z@q61(y3|^IP;l;ExJBloXz&bU5dVD!j;2eP6Xl_jZ{-5Ny%HUxz z2gDjPc?}bRzrDV5e7N(HI-FHoKX~j2t-s@t=khvoelz`gAOz2T?A8tY%W&6ntp}eU zww?-3d<(9W#HN35-k0d}@FhRanKv#Mp8~W3840Hzx|JVa{?quCGt23zcKV|ZSuBwuaAf?)&8U9!n?|?#E6lz9IPy%3xJs8{P(-UCBUgJTv}~c+wFAsM)LOn5>rv zV__d0T?3PLx*{DdCWA*zQDT-Z=c@k-a{H zT*opJYN5(|0gJpYr zt6`L(PTAXz>#2Y2;IVhKX<%ifBK7?69@+B>7=CRr2;Yx1C7}i74j)UC&=XGgT!Y*l z>FiAyhk3i&7~j{(bv7t}l*f|a9>8!~BiW{aOOw}MBJ2?SF%?N?+$CIPuFKkvR>l&6 z?XKa&74}Oz{#dfO#*HXm3b~w%pNvekf1CIvqXDx|iTP-{KH3hU*(5S#-l6h4d9}9M z(lNX3vHcsdxpTsdMnom`Y_n!?OaFVraFOySkOt?D(`aIxglhOQ$0q>aV4oh-w-Ar; z)F=$5#YsfMf*6=n|3V(edIUbJpvQbLgCfzAPJghvEo{JJ7J*ta(E)vX2n#t(cih}u z$029PS&X-I#G<+wuisq+iY!1yXz{bHZq5Xp>MJpBt!4I0Y98^cKe$mg$L74}y)tV^ zoTFGfu>*RUN9PvVWVJYI#z%&01*8JmZ?x$BI65gzpb;Ph{RH$!uzU z?>9#H_3JGv#_naUxs7!Ob0WJVS}*1V81BW%0yHuK!Sd+A^NJrDeX^xaV&xDP0^94V zitujZFTW6oIDd(N98H=RPP!XVyKy@2?m1bkbeN;kAm6IeNk$24{R4@h3C_^NKqEjV zJ8oA27-st$4^qRYKA6_|)|c2fr=75{^0HRMxJpGXd0birjuAZ-;e}N5lQH7qwta3= zWiZYr*1OP!rEN*;g65;_+#cE_M3HgEh^*qRYV=W0RTPAVEk`G*BDO4;$_AskGiP|h zPin4!278gPNkIeW?2qN65$=0>+b^%%XQ2C=YvjUXnk7Uvc$JI18~>xI zPWvl*5|LGR6gdwgdwZ6(h?R~bHdClBI&^47e&3Erj;x+2skVC)*0bNKx5AfYY$9hl z;^ZWqwXf$4#4WWGjki-t%jfuM8=bRmGCzr)s_+(N-F0#EKk-`%5*8f+#O)id$(&sk zWO6`{(5&Z8RCUL1T$ayMVhJ&m)^(G18*fjlJo~U4i+`901GQq`I85#bc?Bii8()ZE zG*r&7ciX;(fQ;g`x3*Sp`$y>9DPsy31d{Be=|x@^4oY}}`y^OLTGO*o63Ux9y%sPfgC~#&&!g;yQn{ARgJExVvgreR@{Fvd)jXQT%ZN zUlPu^&O#74J;u6c@X(Jb)_pR2#pPJuGxo@qy{<^lzr!n1Ebgr;7aEdGmCCj(-b%Q8 z1s=XNh1l+8c3yyd3ZDkmPk5|KyndMHBnC1@Z6i z(*7cY{of9jzZy+Sy97-C#}1ZcGq61hkUzTquvLlvg{FUS%bd)d?OmNr%$)xhep!r) zoc%f@Y}cCx;wlmR47aVkNF)%bb|WUY%ZKE2*zQa-qRuU)mDb~##G1S|Dh{KbO@dEc z9KkDC=$5udHF4TeZqlgfZFHXmH6xJ8Hmy_xY?g&Cy^-EJ5Z+6qEX=JAGBKFJ?Ixmr zU!MeJG-!V{oU9uPRv$BFOSX5AI&w-l*nI?BI~}q^Ok(&NQ1>~5J3_5PIQ%Hp|}yrg>~>9pMbno7<9bO}+jat_g(nMK5heG*!CwsbsYFOjKZeJgK| zW|o0|Mc4K3KpVBY?M{Aup@FGwKf`qL@3uS&YvkkH5u{Y3E!Tw&MBco-j*o-xv;CM4 z_f6OI2`a4QV45)7+Fy260H5OJd&%Snc@&4{bD6Y)1!9##$4hU8j;dO<4;@+}!X7t3 zZwamt$u(Cy5DsluBc7ZR0!`tJi6RB@rUxe6@8_;Jj1;P-ov&e3&K6~t&9+kp7hfsw z^QZcFvbDaaHv|{mGoaMFn(ryDMt)Xnx}GdM**OJ^#IwGcDxp*I^pn|8(BJLlP#7;} z*?9*`q%2slOrbn4W!Gy5pvgTqvCnROn7_f#j?b{PS+q0w|A!u`bsZVy=!JO!)juAH`-k96{4YJw z&e+q%%+ATk*~QF>-qF_PAMc6z&;Df_RZOy6+!`Zd*ws7qGM_^kAyj<4DscIE!=ki| z7G2{UK1DN8YF6BuIrzsz6AE&BCVryIA>K3#!JO>-iJy0izpsAbmPF0W-Sp!89v*gy zcA5bmMN&w#R07u$S4jr^tix0LdM(Gm0bvdC<`31*5sVca2^055;NtW_$~~@W`PJ9} zLm1wS7*K(4yzIBSd%<@C{BgdHBGJlZZ}Q=in_Ons|0s6 zmAr`u$x`G>N+3+NaBw7uX3#VvaP?jo(5K}O?uBvu;I-_wLtjanF6huoF zlTKayptG1{XtQq$HYpMY((}=~FG`GWjjHFOFv=0ayX}X~r(GesDTa|07)QYh#fIEg zq9ht%P+D5vie^2S-Ueho@AU>fy!N@>7t2r0torQpR#3V>tMJD_6nqE@IS?t-(dY<` z`rl)IpANA31`5o#VR(Y^bP4Li+;S7LKP6G!gUM;n6oGPwAUzc3fGtXZ)GEKO(bxSjiG#<(SAT~f1YJ2Tc;8LRUza;Ze3wlyov$W=@atF`=M^UTWm z0pgpu__>UBD{$UnI+4>S6`^CBX+YHU(tq5vC$6;!V`>tMOh2aID<^bz(Mjehu?mOxz*FP-2GX+t?2vAeKsF5!}PYpW=&pVyt`L^8)_U!RM~&SZ+Hbnd}@2mdFJ z;V*w(x46nP`;#!){;)>=Km66m+2r5-_4&WH{Vm4v!3Pkc2D{78!dT)HMv?%tgFtN~ zBt7+nj&0+eFWk#yvaX>*bJqWupPj@CdZ>Settm>(5I;4(0F7)!y@T9%dDJ~)8BMpU z`5iK8qDjC65*{!J7K#PK`kB6}o8!HR;RgEG0RC_B_-g=~Y1kO(e+KZEQ2g%!XKm)` zZtrC3{6CGt1>j<1#!aYd_8&tP`tNq57WWa;{c}m{KZ5fwE^A-_umiXl82np}`76Nx zg~8;;nVVRPY;UtVeGN9sdd2wr4Y#2{%ESF> z?p*qlm`MM{4gaGwx;PoxIolYy0POAly*mCksi{l#pVhGsI=);+UL*DPl5Aoa0+$eCdmPN4Kjwh)+6PBoCbs4Hp-1$aUc`2;74;$WnJ(!WrP1eT*fc zN%$xckeh2;YbPf}=^(+HN5d$2%@2eKH7Kpl7j+AR_x$z3j72V?_wP-#Kxd$vI`Dc5L7p z;M(Vek|6asmcUn36sNr}(Aeb?DMM{3t5u+Dh!(rW^e0d3H|QI6HV5gk35XaRkFw)&J;gH~=##M(WPoz`lKVkJ2J_4G2og#vj1W zy{~*cHW7VFZqb5e$tLIfS~^B--3+$zb%JGsAX^TXi_dg0;p*z@Cx;#{*#U8F%jq+> z#Ge8*X!&kc9C%Hh50@j&lS(x}cZILm@3}?&CWpXgmgYDHc&&o>r^j(1{`z>a`kfTb zcB1M;-K*kZ6H;1neBrhJmb&P!D*73s1(h9V$xV3zGS_hs_lHdC;GT%GB*V}QTc*FM{6wgDL}KOVU{)L&WS&-zuV&^P{z*d zMP3FWGRkIweWUQ%xc03WTF)_aT7J1I%lb(@C#P4_BPxW4pvQRssA_Ac=jb(V1GbC> zfAqy7=@tX#MYq%<&%w$_9Gk7tLqSX(-P!ThSKX5bM;V8pu0q_yBDtILW~Mf*L9ngj zPk~n1fT5Ae@Nnnmj%y^T66f}B!|9ch?RKcIukJTR&$q!(fW7)qny5caq)60#e%vr7 z`*r=ZM{)@2uv>M^@b^WH^$*G+oGD5+uI5<#wu>JtfDCJ&0^3g1E4hz^$h{9qJDUf= z9$}xJD}Au5o$QCs?HUXdm{EdR&n)tu|J2DzCEx@T{s#k`fdc_i{kNg^?;YCze;w8T z7H?;N&}csVjMX1B8U=JVj17yoRzzSS)O;cuMc0Ph#QOG-L|ZBq3{UCygT7g^TjEw} zj)GFQ{NCrhocpyOBDgOlu_+xipJWR2C=YJugo>9wIe00fEd?%QYm7LE1+BVhh(!|z zA#}m8UNyFAjE{&CqbZ?mIeg&j+qZxEDKV?$W^w6$6< zf?A&2H)hf}^p;-a+Z-z68WTJOUpxm}kS0>Id#pTYKbW4G_PX#{av25BZ@lZb+UYa7p~^&GG)R zhl`67@?IRf-ley1ayqeXNKQKB#^y10v^W?B>Kv+(#UxHZLxt zI6=>YxR7CDeCOBP0CDg)(Ca43c?a)p69?U;xi*fS9Mx*c{PmE@1W{Q!)Afec{kLGw zTzQd?jz^mjOd8^4e(MeMOmUB+WccvX?0caF%fRlMlzN$Ap5nrmL1^4Ll}xUxO8mKj z%PzNg6SMvqCG;p4LlPGhRHjY}{40%=-`Ft&w%Ede+6n0|x}>UIz_Vn5Wal_eqXNHC z>Q$ZlMQvRNj~M5*Ol!v~HKNAmBpc(^{>~_+)7{*c`CDng7G1bEZl_GV?Ie13Eg+5? z%e{cxLJ^+M*ZZ+yN_p^%Y41oZkj{wfkAKc+2R?**DdgKPd@MgQ@Kk(}_3?cU5=-S# zBMOg~cmw}pL@Y>BZ+0&G zZo*R0$)&mJV~O&+XQk;Sh5LZ?=x3$BF|n99!-$wTuTLmWD9gv?bbOF zeU~+0Hy}p?)aws6qJVHnW0wWj+TDWq!2)|FNiwTND@aqSKK;TQO)GJp^1To}xSV;M zISB|;urSUl*Y^Fhy@UIGbMh^@FQ=ETH&*o$%fhXmWwx`C~m*2Touv<>>+Uy2wU}~=9n#s zuK&Aj&Bt`lK?Wf65c%gC+Z2HpJ_AiTtmN-%E9n`tx$$?QQi@(gLmD~fDERk{e zjMNUay2zHJ!0TrP5Jom+U`m=)iWV(q4hU9%!_9Mcx;W~5w3l$d$PdwIC?``!+opk< zN$OE7u!CG81^yzo=WE)`O2P+YC*?WA`>JMSE9$H$mb`jjS5QKNe2!|J2}9CSRfTL4 zBB_8+NssC^u&BBgQB*p5&Y=z7vA?;0{{+*QvR#6H5Xv+-$zQ)ZW_1bpk!tQm5lh^! zUKR;KUBdd1*0+uT`nVe9y3n#-ty0{A`=Kt)7hCnSQazQYdb-5GoTIW7_#@H&ry6rN zrvQFP%Q8Y-ZVp6CSD^txWRLbci=JQl7C+o${QT(Zi-!|>O#kyPZ=}N1u7{Hox2NA{ z&y6wH7c1HSq3j!@EbFpt!?rSP+qP}nwr$&y8MbZPc7~Z@+Yv9mYE*S~b-(xS829fP zckH{@K6_%#^~HqAxEE8BA;2CfI}rGq5<=7c zdAiS@ZXcfcY&#QkW6(feJT>7M`mNHfH1Zd$ND60t#c{_U!edqpwZIf~Vu@+!^q_(G zVdBWNP~Y(w2uaF|1cO$J*=2pyW*ovdLhX$zYtK89+Jye91A?_oREEl+U9Z>A@E_Si zi7TGxlf~Fcg~r;%5j{Rw9d*WX6vO{uoc{|!6tB+534g;MneSKj|HC0>ZSlWCAQQz2 z+kSe4&O1~uDXJ7=eEBlt{s5tnMIkE36G)C}K~h+J+8cf<{#@!xHGZ8O)F|t z`w?SEs#3>@r;zzcoMldHrAJS13>o||^Fo&>T~{-*9QFOYI=ilDFdr367wi)>M6S6*{aAA-fHLNy+q#20e5^5>AlPk6CpH1KKw}?S%R6~ zxJ+XhK(7L=XD#}WdV$$cxWd+R7^94xK^lml4Fg#1CHzLmP)fYIg)SMFP^-$ui6zjJ z?2}h|qSTK!xb6RnT)G5;e6GL$8>sK|82ewG$1ctm))vkdf9Lc52gH)4qF}X7kKp@M z)7k{@(#sSOqX6UsK_CNclgezesU?3NMPw?KO1f{k@*sv9a`lvWm(g zVtxIH4CwAq9;I6Y%7b&m^?w6XP3SY~zN6P<(DymMv+Hm-6}()yGunU)#P7j{j~%)V zKZGua1}OkIC*XE)NYqNNjdIBSbjUASfTcFlHb(}xofFS1OP(caC9)Jw)4!O9Kq7^c z7VCAR7O35jDwh9=e#k}drOa&Mm+#tl9wI4GbRkBsA^`|Z-7L;T$30^ZwZld%_tQ0U zFEp%73R|Fc{Qh~A6Ua^)WI7n4lL=TZTb#XG8+Zb>zVR};>FtV3#B?TF#aWclHtP;B zrd%v5F)I;NJ!#*hi*dGaWPFkmcg~0qwL4J8ynW!Yu@h*T(l^jY|6mYfOKkWuw~fAZ zgTWcPHM=Fr7W7l1mYLpdyW|l&*059G%-z}bX|F-sRs&8)Qi(RrMfNQg|6uL#cIxhW z{W?nQZWD3rLju>0NGC&Qno)9%Or@7T*>YZdw8Ph@g_-)e;ahbsCT{{M@!_`lp_ic~b>7QU-!rUu=m zEHQFu^#D{E5C~0ORddi00+Ilw?S`4b@m!zF+Xfof!14scA+eC${m9BF3MgCfRfF}q>~724eb3}hTcQiD zJ~5@(BQ>#cD8_mISod=a%LwZ-?XKksNXb5qXa8Nn*Ef}qn~M+HnM7)_+H8u}uG&nP zN~WW!zTBtAo`E+Y88^E>YT9EW<&S+)$lUSqV`!S9L{CUlT|Dk;tDLB=#7LGHG@gQU zQ3?G5ae|ChCHVfZmKdZ*ps_cV5+do+Gd1jicGCuDqlDwf=k4u-SfqWtA?mADd;ZkC zzj?cOVxjT|2L!!d!iBYSFVI0Cmo_zWE>kj)_tXIVr5bQy(DY48|4$wIsl?KUL>P$D z1iQjDAQe(`TL?_S3}UbC3Yt#&W9;Ak>7}kh#``4KuxXTQRdajwEgD|iw0E6}-IuC) z%`Y>O?az1o?>p^`FOt7hwuITeIPKQCDdkGww-1!)D>q4via$hlFo94O*|bpz<|gwr z;r-uXlL;l24E!XDhP@g7@U@x{P?}l~k=pHM_NpxeRLGQoINK=SvgB0|d+eH6SpS5( zCiPU}4i+O>VM3U7_yV07>mh&|*_-*+H$;N6ku#KXe7Jhqmpc3I@8d~lQ&gMldg9cv z8ZY4-!>@66SDmNQI!J2sSUE@^D+oTiqugmw(P*AnL@d8EDu&f)-_7>l!V);S=$RdZ z8wqkM$@8-e9o8C32_Huv!XfTjcy*{rP5I)Y9}~xl^@O$JIeSbzY^R1r~^4jhZ zY5nDRzUeA5uoh29ovY(NKJF08WvHa5WvOnu0ZB*e?K9ApK)^?!uaGCQX~lTJoz#K4 z!)Z;TNjE4=VRo-uTdBpQW8R$Db+#XlRQnlL0Z~)FL!?YSAZHA3%dNqYGTXL4R`yMScW73uv2P4Ty~| znh_1-(jWELUJ!)r4Xc4wDJC!7J|Rkd)R4jAk}vz=`#0Y5-@?}Zx^>WlBl%T)!zQ!u zn+N5;c&mMD1pg=$d=sy<1}^Tj?*AYdbpLlbtO3&g-bI31f?AfQZeLNp_C|(ALK^T^ z02|@=kP-n`)4b{!!{`#j79Tlp@Ad+n@E9#VA5+bsC>}ZNa#3966qvcOyrF}HWrC8S zZloYme60$ja^SCUL%@2}K7v?+>^_3*>AsupSj6n^ylzR?RC_M_3Ky4qrFy10CKAE2 z61+im;C{;>A#H8#f#mb(d+N!UXlj_4nCO_v_^hBq3m+7{b%y}Z9fE%bT{2k*URB>m z)7N*p%fC|Y{)V?KY`--VQv)ND|H~OQrEX=nE{gV1t;ZmaDGsDeO7Th{pCN*MHO!*l z;hM?X3l>11UtL-@hT+QISpMn+IJVilLAHGB|YizO7jX@Emf@YG22E+y}O+xA9d{t3V z&fxGOVTz`@X6d*FOAQX>LGWVK(QqV?Y+6RZ4z!(B7e3WSWH6DIazFLQz}S%8UNE!9 zuc!&n>oy8(*`K&iH+!=;;Y&2^(_1t{anL+qDsAP>?Cj}y^M+jPv8}D}b}59Ky@xqX1yrw`bLqIUNZJBr0TX-?n14if6hpu-&rjUlmQlC#cD=c> z>FMa(WvgN?tNLVv(YXVbC1(hENI)Zi#fjYXyRHfB%|8%ZrPji^`7u5&^uq(2R>cB?lS(Lq+A@AIO>N zNhSA!sQ8AYO&nE!s&1v*V7wZ16TnmmdzuNVv$Uzr6Rh>X#7Usjf3KRmk7wcc9dAPpX-lz)W|#NG0y zZLbs*^>558WV?0Q=&@NecLv(OV9S-=E{%+7A1#KA0(Ci9V-WoiRg(8zkcTJV!36J| zW}j{~i9WSaAgUC4=&1b!<;hgi2FhdyfJd>F5&ap%!T5e1-9y!2DsRXh!H#jxCS1#k z>Yw!1rc%@M!UnS>m>MJ3(r?gH43^7fba9u}dajk;KUX!_FFynXK|E!J{Th2h8b4%g z6-;LjttzOR9WrZ;Q0CS~#fzv)go> zv@J>JLAf^O``W1m==E@4 zr@~jc0Z=%aL3=$xn4lUVR*A1>RxrMwQlF8GF59%MrW53QJ^Xl9Pe6`OD~sx%gkEAR zC0c6?(mkvC(d)XNF93aB(FH@kWVo^G8!D15>EUga=EWufS6%?esx+h1w&n{i(SWd1 zK4&t*X5c78hsAqHUryl)=2ZKvBqE5tXZ#*mL&}{)RXgjObMS|`azJu{ ziPVTfNvpfQ<~TUbYm#4=<=tthJ3ewMZ>Q#6irjdN=Q&$`ZOR^{eP0SGcnWb|eO^Ft z)G;~T>9n>5YQc#96o~ZDoimIaCO^@K_4+K=4UPdlsWdB>j~x|~3-+q>h7&7n8tx-V z_}uJo`hLDwHI_V&hquP_F5|MB=ix6U6Z;nI&;4;)T`=vNR>lt5$OChWc~q_FNQ#+O zk@p-_1snI%M!P{5CNxlqX3x91ftHqxppEFlBRhHlSJc<=5`(t zSrC01d?WEuW-f5Jn%oT_d(w)4A7mGAgcZwdK8$Y0D&nK;qCca%4a(B?s#4{U^>75u zW9D#AJi%Hl3O z1*CQ9%Wz(@i&Mi3lIr&l*P_2NXa~&7(+t1W-6g#LmNzG3U}a)zVQum^3i&TP(7(`A zn>4XZC~9H zb3wwM2#yyq*=&4#?{ToeC_WOta5`4vfx#OeDn7W0-TnfHp^PNJ{JU6{G{y{tU@hKy7F{I2ED#y1oP*skL z6W)L$8=gmC*mlH?$<$co#8Ry4L_x%P&P;YJSlm2jkTf*Y7@FK=1r!1-ln_eC2lJ02 z!32Ik80;#s1^f9L_!~9T4rx7mOL9`v3zN$=_=_Z!t1xymzz0_r$A0( z(XI0#BYgBJ*S((;(lE#v7Xp<`T*zgWL0RU%!~=*ihkwl~2@jVXNIhp=a#M9XP0Xll{Rb`KVKay`>6`8uTLv9?QnL@C_(;X2n0A@%kI)9>!HJJ zvmlDL^5rzHYGw%jd~1lOE9!3Iv+iLqrG)`~P(vFA%$N5-Ewm4?yi)o(>}4H4QY3%) zdG5~BEX_`CyNB=fms`4AU++j?#zKQ#ytfA@axJp~eK3zNhL`SB3;YLBcG9?DE@z0| z2H_TgCFRWU((}%rM!S74?@uRqMmeyLY9{aIkW`gD&WiS~ADi2;GqiH;S>BjqXI(=# zJmPpaUqi&pa{w=@(ogEYXkL%j7oe?0+aeqK?ICRP@e5YgosF25NF}n9%`uPR%BB6e z0h%XK2nFh2HnPw$Y5*Tcg>n^R&GQiEd5*^doK_7EI=RBm3e+cRsb1GOpp{|m{}J4R1aezaBNWllu0wxw_f?r_FE`fV&`?XM#F?qU-hw2h zObNJ1=v!m02?@Vys~{U&BuI)eEruE=#)Jwa#-I;FQn&c-0T=fE&2vT|LdG~2WPm}Y zKuQ+)K&<#u$%*KF1JQ2w>P zHEe|;i}Jx45Us&R2l%t>*_dfXbKnEpD(~94j`@)s4-Tp*Q4@wzP+l{C4#uQ!YN537 z5B2>ShS*{QA_?*-JDn-gPb2LSM9VT-Vr4AXWcS=8e6vCI;&+u8OAY9B^ol&%i?p(; z-PKe)s*3W{tP6F_LdT3-_Akd$T@OH828zgy#@$7<{6OY$Z~9>gj(Z~&@cH&pS{Q4( z=!S&bu{&+7gxr)E)Y8yV?~&`Z;wx+U7OaP7&=SFgbpSxv0lcyzbFVbB`9~?;JB}u+ z*>1LGEEgh+H388**QNgQLzOON)1_sbEuoZ~)y$YD62)2d$3bOmaikxPcXDY){b&#V zuBB9sv*JG?bMY(z%tdyQ&PYRB0_JwEN%kp$jI9?^HK|}LbX)#Zk-&Au;l;Oi6~JF{ zeofou`?a)S-Q}c*ZIrW?m%$JOC0)v;*>TOJf=^FxYnHhA)BF|)RlPVgX6LP)foZCC z@N5-u)^bqxgGPPEQ-r^Iw1S2?MBd`+)_H$ldU|X8I(-A?bbt78>bHve+}Wc#2-<|< z9SC6q^FeF`MlxP6-Dg1b!5sk`fgt5PPv;rU<^%&jnhvptH@e?VZ%7egn|DYquO-AB zT2l{AcF0o(g&X*2Lph22h{V0kOr5OOf^?(AWAN5sz^YKxVXT2W3dD`agcCYE!6>tj zLHTIslRL+Kv_Hml-wg9neb?WL_~27=AL8A3Gl30LhM3Az!g!sCna_bqnq-W!>#NyC z4~d8HQg+E)K!ic-C#dMHRdg=K04_5jhRD!Q-paQzMeK7eI4z!{^LiA0g`6l$0Ty<* zbb8Q^p5!2)C!I`qCEDvR*FV8BUd+7KGOiSyutPWzKVCxuIDMn5e^4_bK<_VTfp0D3 zcpZUSp|ew_F`UQk4qcelZL18R`9n&b8=7{Ae99Uo!Oe9BO(?fb$#y$$T#!c4K1%^P) zN^3quGE#q~a^X-EH%3&OFd`bKS;rG)>v#mmR(2r$DQa*|H98(Jb1ZqQ8VXXscp7)i z_f=FO6eplskiokV^29zJfWRffdnW8b$BFx+3wj#ZoLTW& zeBGde1V)B9mMr@rB!_f_&>%+|BzgAZY(v^@v?vBWqIPHoqc;kUEyeX4sTMy6&(H3= zsE!LP?z@~f@Uz+kk75^^m(B?xH0>ih?pR-IUlRBg62UO!ZWnVv%s=Ga7aJGEj}Q)W zD^vq>6=;#+&^^=sMIyNlv;phJYG!wp)l6!cqmKS9} zl2%~{iIrK#(N2cm8-ID;8t|rHkXZnWf7+l+gO@WOJ19a1PZTtMXtLTyxwf zWDAbZQIJC`O}2kUAXczH>@5EI;|Hvq&vxydUz@%CDc@L15b}e+6nG~4>k41m3pD># z@>efY!Y4*$BY^5FLCO8mRs_n$)B|JecfKipWWlx_Y}r9P|m9Efom^P1Pa zDQSajLW~WH3LD?JQQoZa>PQI2XfA8EB|OeFk6A~3#I0YlkG60((Gw+rLTDwHIPSY8 ztNSC+V?+rvuxCKH^Y7TIoMn5r+Me!%Omb(50l1J(zD(FhjNxF7Bs_uVg>hXEay z&Pv9SRD!9C!g!yR(aasry4&4SS0ImoWGP$7bf2hEoSb))rdhuyRrx(S*p7Lv1FNF% zb=P4R|5%v#W0vZ#SyiY7l}9T`9b)Ea8BHGHKsFznleA1_5c=- z*1jIps%RhM4bAz>!Zvt@&xa4ueh|`ZZ#3|iH+$4q9nGqd5M1<9lL^m_kqKho$vB@> z)NlG@T&S{MLy2iE%?I27uTh$5bvK;t1k07An=suv<6gGW^u{b;ouRas&6z4vE%nEW zRH)V>pcJ8aY`)A|^a4#cV?b8uRc`BsfM2GrClVD_{HfI7o`Pr3IK>_OTFXv5=ej8# zls=_HfvkXj!=Ny;x1g&^g;Fi!!69Q^QfuO;EI1Fn(qT?n@t*6r}L7|YRTN? zt%`C`-En?PV!CA@7|Ew6GpPsD_BpEOx3wqX7zsz9q8O-68SggX$d)C7W*-0;(F;k9 zAN3L@RG{%CdG76)wg*LOa(hdN2>%fkl?cUuv3+wuXxt6X z$xJKnb>2S*58~Q>TcdIH?bx~T!}c>8WwxHwqu%ZYJ&kVuoT6<$_NssjTelpt`@7up z*TxjPz=W^(mV4~KIn{sKn2I6-!ZISX=Fb04CI7WC{{@vCqGIzsO#tT8wHP`8R)>t$;tP)NdFOJ{;o=vx)=~mBf9I9^b-0UKXi&eM3h{!neBvValr`0%{fY z%@@*o9}}mlaaoMlXQj?jxSxacr|6ObS`|x0ELmv|i;R?3{Y=l+V$Y1B5G+}7_0OF=7~XpeEB}ZcU!Hr#rN_Nif28;3x*zg@}5hPIQDGD=^JCEFJ18 zwh?^@ZULGHbrH)mxj^RYg#e7cZ2Wy@O%Pr8IrU-vSE6&fN~f zd*%AEr-m%18!{bG*oT4n5ITY5kLYd`2aP>J4&4?=KnKsS6CAmGJhljhOG$y$YK>P}Z}oGaRtXW0s{ks)Q#6DVHru5PO+P+;ebnVVEa6$tOQkr2Mv6 zJ@=+dH1g}zVPEjsKkc$va-FU&J5!oTMk^S%q`xNU z*Mcc8z^sw%E0e9KFsUY?bd zzp7|kJ`O^;a9l@HB}4hR-3TfJt6FWn?#zf{ZzD)t#S50a^edO_8AYNECBRESg?yk> zVKp@k`Z=7Uf>+dy79=J}FR~bMgx3QbZkdVK6Gw3<>Z_K>r8Xiz*+8eOM}XMj)iNFvh01(gbBpx>3LFo1(A_TLpxu_NOSfr`w@UWN%-w3=WISyv;FK ztIjm}`bgHZ#-xe`x9qDoH7rX00HfMr~XAKk3!!04o z%^tX$&Cx_7vaS2+qs<5(M+41=HW+CvBr*4@nKl7U;(efiQuXE}CNjnR8+XCf>E$~r zx(i&oxzLRQ@;XN%GSC9T=pN`rOv{zW?fW5y5A_FomJZF&KolTY6k3SxX-5-hcSS%y zbHksnSWp@E&w3}&Ca6L@-X4GVa(%s0XnB8@13F1@>6x1KOajA`cx!8C6P@Olg5Ge~ z5K^t6HA2n>8A=iF8iIl9xoJ7e2}i^di5|ii0)pJ&G)A*BJ~ETeS<};#9gn$2;R?xM zF$xYEz3TSidcenJ>3-0bM576X1xCyp~^a`rGq1*b^*+JASdI*Y1`2UPFdhAcB~B;M`tc(h)=+7bS4Du z0k6EncWH~^Hsa|Q#FHu^C>oRs{Y@HxygLACZ7$+yCSeYYE<$Q0$`$IQIC)b9W|qlw zjy(c{;5Y&zVRNDmsSYk-x8+93<_;;2{P@!0;lmZ=zYU6r0>t`rCmjZ6&0XggM^z(K zfJ&Sh^VkX+ma{dcF#E3Z%;I%iV_y_1CJO`ED5A(5`2?rzFALHfzBcp+QtWtLtSHgO zpqS8~+{uNTXJ8Jd6DOHY!zug0cgA{;Vcn-Fm?!EQu9->WeDwS4w-Yzsghm@To?tcb zo9`c|lo=H{QY-qz_gT!0C~2TL_83WZYRHAv*est^v+$)Y>i8>_BYQV~RLft8`BC{5 z2XR6T^T&8%gSA%~Wo^^37?#H&gEbE;vQrLaeF+zV3lL|=hB?DlFYB|QX~M3Zh@Pxv z-QQtgsU_UyciZ77bkb3`=Gni3&b6*fQbSYkH8k)}qL;%>R1N};m zESFs!_h0POU_>)KVIWTGF7NX=D=d`M=**Wv1(VR#5Y6BUuQ+#(yKyJb^Pzx8r3QQHHl3ccoFT=sDVGmWR z`Wy}%yE>o2BbU*8p~Cv7GuoPrp*&Ye!HKB@_i(wR!)Pf}a^M<8979_^Cc0jEx1f6f z)hJH@P4jLL3Dm(V3+Hf4V5rz|100G%J#)b{>PF^{i?lM+NekUl#5@2{M^q`e8|tD! zMspI;n0dRPzOJ`lyU)2-(4;sAwRS7^7hMn+E^JlT&P?Py?*g;tsg2Kk)N3I#(UsnR zy?0G}S2E9kOZrpa2k^gWv-!7~Z2#`~*E2M5GNE;`_}>WaswAH8CRBvbr*|kprHG?tI40!sa{`@+?2O+IPfK;0AjH78^+ziT&d9-1E0r{-e)><;N z{sgWQc<_-CvS3ItRzEN3pnzg8OwQ+h??~y=05k7beG=!akd(r>f+>;_FKR%?(r(-9 zBVEe@=6i!yBYDp6a^3GveLJ%2y>ax>lh>zzuJZa=7#qjMnz_vf1B*urtMrq6o?zIo z&~mq9`yQ{9v>>CD_Nam&cu*Xz($$Pop!AbEs;UDmSwz2ib-!s zrUHS)C>h6FO_L60skAp|L>c8~4Vnh$p73JHvT+u7XoRTaPX@elXQ)m4Je<(=Vbt@k zm4WvW?!BEb9;W^^(BTvXPA)ieo`UXQ&9o;ZQnT#fgD(5>5eD*`Rm$e^VIbjRCdZ>K zGfd+;SoTYcT4aWQbFpH83lEeBW#>gUKX8Wrrf1cmQ76rOvs%;O=(i$Eg zXn#-!5x`Jt&eDa4fwy=ywSMTZw!D3ygEdn?-gc1xMzyvE$;<75L^0t6GS`ZE^%~9( z5rC?(Yd(E!6aRCK`Wk0*0R;yAc8@u$dL098BM^yawUTZba~`qj8kwKQ6o zs?m_`l>2BV_GK2+PV4^Q)SWvg+A%q_vv{X5?QW&L8%pyqXQbim?#6pf5pQU36WnD5 zUL_jcO;fiI|LCFv%T@S(M_7&G!rmD??3Mrl}Vk$XN(H!ct*B5XcYQ*d+^% z?%}SYN>FPlkFyNoDoEko%fW-2=NE2>%-zY5v6=FWK#p7`mcq|D*(;6hzr=eZZFR{-zNc2`~(hnDWh^*5 zwt!;g2{ijfwV?0LY@$U$73#Noe~xQA`U7`}2yU{|(O{5uwA9g#(|qxz2Eb8}kR^ol z8o>PEfs0%W8;LI)jj*;cqOam4M>txKN`xi;_qW3M%Gm z%1XfM859;;Voh@A1zb2r6~OPyrG%!Al)%0Wr_hf#)h$q}W-H}J`UvA5@)3FVnH4L} zv*GgmgislCJns5r!H~VJCmcpIrIpYBe!Voy#t)LDs|>0>VD1a-eCsM<5k%f>E#8+{ zN_KGT(JL-!`a!~$8^hSGz@dj|e^{!XvXo^UDJ7y&z_*_8T+PDq{%P_DXg%jf@`v7;B44D`g~Ge*ty4-d>rJ)1uZygM#jC$z zi1J=JIgKrWViTif_UW^Xr4DM@UCJtX3*{-Zf>Y<*bg8!4FeoJ*Wg=K&d#PEPheG8=hpJ7iQ!vabpfn+|_RM>!pbi|KU&9r4yj^=8alirCxOXfz{HhjZXlxUV&%TgI! zOTS~AjWs!feA3Bvj~k1D^;3?SsS?v;2j^jr$u07d0RCVe>?v5J4wfr(N>G=HmQd>c zdWJO)_iQA%m(CmCsz|zV_I&IgGWe-3R)6&ZYmMks#B|X~=#QCVRrW_cQm;!4?PUd%>uIE;rd{CySi2rPF|Hx%FK>az?HQVlFKK%AZn9 zyx(U{b49lxMoi(IdBSl`XQvJzEo(tq0YClnCqpQ9<5=|qeIeOM6?%EXZiTzX88lba zm5&G?oUKCmS3yf{dhZmEP&=%%qS&3m%$PC=9VsJKYhL}+lESZ~z%>nyr`9$*ew2;v zxw1NxXPr6z8~~y6G2o}8TZGm*bnZtfk@-%*DhzTdXCo!x{*G?99=dMSs5kA4;@RM2 zKPxX^-oCi%fC5nFPl;-5jn<$0Xn}C8&sgvAyWYvD346l3tYM>fKrI%Lp2HzE1&ktt zmHA=llOFoVezyqX93U~xKqa_Zw?--)XRlVHCLzCw_({T^nH$|#gx#Li*VogP7qylR z^{lj%%27I4z8Ek$><8wzwQ3x>gc-YnQ7!xjdo$BLExtI&VmA?v$z@>UY)@^l+_;L1 zVH64cgHF$@55p_s-Fo>8*BIBj*+e}P4(N@U~g`aMO@PqyhoCf~124uHC=2>anRDc`^ zNeBsjFMvk2U;qfOlR4(vgL71aiHMP-1Y4^g2ag*}G$&m!yUMG}`NTD9p7L?c8s(=? zkT=^Tl*AaOSAglBXeF7gYLRS^h8wH5%?rq`w3Cv&4_VdD*+B%;sGx`3H^^mF(uk=5 zM)2ve6HlO;xzNjbKea{%)m+;G@tjaphRpyYsfzZ0K0zq!;Y+VZq)vwQnvn!LF>(?^F=Du3_>YN>sMon}Lnc3b}$ zP_aNuFGu=LW%!Xy8tLs%)MyA>d!^5Aeu^=5DHa((8O3PeM+LDrUTt3Z#K#48uzv=A zf<16&z6t$Prmo+a^Kz-wZ9;<0MADWvKsp1B%pO1Zdu}QU!CsDACZYm9d|B##VKxfG z2q}S;q5dt3d}5o4x<^8*w}&@-7B+)xZ8&s}m^>5$er4LW4GbKVsOa;m~ma)Ym#38-5x1&C!=AtkLB*6N@Z-E?AvRB;c zi(2EJ#T^7PzW|z};zs2|9JUXBxdAHrAS%14fQuKs`YpPCGoh=XIDPMh@#CT|lYj#8 zrNn(AU7Zl?T&j)gnIP)0S4)kdP{p%4IsAcy)TqJs71Q0>Dscm-=HfJ7vbJ z1?#4t8T}eYGxVrWa*v|EN=5Gw@pn#wTne$*MA_+V$s5jhc_hV}ZP`c=m{XzBLVCq1 zIEfo6x!ZP7M7jihShMHX{=8Q+Cu+nv2l#@6cqJ*v!iu5(F3gEec_M8QA6$C3nX5Jqq#Ld(*OI!Z!@6w-V=~p`m`k>YPOH}-9+y%N4{% zM3+$W>Fv(S;qA?xy|q)3R$NDgl^mmfq}{a^eP;5!XDuo;wkmxXU5gV#e&E6sY)yRC z4;K_|hq}usmEpt(z~Uq(Y;Yt-c^ z7^=c&Go|NLp=4Kow8SH^%*ev^g~l+`+b(7c<1IT>TpR?+ z6AnP41qhO{m`lp{HmO+VtW4=x#IYk}ywOeWGtRxU3w;M*I-q;ItFC8w^RHl?ypzT* zA*CsmJMR)M1M81sjSXW&IirjYzQ}Rc7Z%Pyif>xC#$@}XD?QWtI~f56pZrjH%Uf)v z%%68-n3M+8&Xo+bRHwW+4!8G8fzN2A$G-5YUQ|GL2y2!jos3mjhfN;*8oq{3&MVY#G zSh8-PyArLxth!R$4FxWkP9uhHdlHbbHY~ zUL?(mjo(@G1F*qQtRTAK&%=6fPrG;WT^{j*I1zO?oP3*z1l&%&yzWWe4jyOalFl=OmO?SDjW-F5*t67yhJ4>BaNfNWr6l znV$Mwsx$XS_jlO;7xp6QZmj$9&1p4%!+zBN3j6-fay@%8!LEmexINE^Y;m7Vo#<8(1GfG)y0 z2=|hK80Q-WFOb69a(nk8sA+}*fMf7`BqL(sH)I0#2%CAK5IoF>>y4d@7(W-c$jl>8 zZN9EQ5vd8`sx3ZDl>k{sT>H=2$JzcPRirr1>d=fVFsDolWA1g3!PUY67f-e z&5b>i_QLWqQ7v?bq__YN!U4+sIn%R{Ulx`Km4+5lwY;`R!L-5fDC!dDp298j8nJ|0 zG}4C)0j%d2Cs=6jqk3=v*(Ou+U(E%$Yrj13eOLKyMntToL!T6R(VKsuYDO8!f2gO> zt2>7FK};56?)4gkk!gk%>A?)IaK-#+9T06SGuIm2{+XeA5Za#YE;(F4_NdQ~s*C@4 zmigqYSx$6;BE(UVX(1M9wLWW$4ym|fm=JO7rf86Af`c|AH?+n~++@S1g;6V+{L~p` z9}%GGv=LJQBAG6&M%!(A7de-ioNh?|#DgThkhw>m;poDJjcg2qvadoW6O|Vj7Ta-r zL*dX2xXc!}|I#^yhdrrwme-<<&B%53wzvH~SlVR+jwDvos>QBk0LynUD#W*Z>>1X!PR7pk?b5Jy*^q;41x4mvNv1Ox#zUIy z2*(*Sl7v`mG3uc%H9tXqi-6Kn43RdcqJv@cq#DJRM;7vj@#Mu?r_^G6iVjX+Y3Q*b z>lXra8%mgW$bv;{vvt^^ub|WAL(~b01p-o~Y0t@WJ;3?C+rgTji9CO!dj4&Z|0?D0 zRr$})KP|GkiM9RzNBC*^O!ar3@(f4a`(VK>9- zmSlpXBG@gLp5FsBL{#CRV_qmD5A4EU3988t3`fmv1^aeW?#7NK(=b)*lZePhAw+PS ziNI)RUemW~W@hVnw`ptZ0e)h4%;0@;X5`G8$(uHu)Jrz7t9R;ozr#GtIOyw>nc1G8 zQ$s~$qNYy-np}M5u?)x9u4sYhWb+R*`?XSNj2*;C`du46HiMdK7i5t6KN?*7@?M4b z@1Q@9ur!RpEEJNAfSku+ja*(B2S!yrfFze14+i0DXX@&D^8^N)kqBdqB$IN^;L8?y zs94lZw|Zz5&w z*QQh9fEatDbiQPXMflGFdF(`}KEGY$U_%4>E=gCneO>@uBGazV#8V=Gg4bcPEb9~1 z@@e`zE%MKkUrqA^}`TO3or2yYXa zgsivyaGlb2?Re`b!YMxldENc+aPg>vww4{kLUsXbfCf{RyukrJ<@`UDU1eC5O&3N& zKsu$Dl$4ZMLSpGIMFde&YH6fIKoAzCQ&74ak?xl6MoK_B6cI&%Z^8F{*;Pb8_S%d6 zb>DMl=9!u2%sJ<_$@nIzo{Qx7;nt`Wmy!EBPAYp1g#*r45Yv)pMJ*$s?s48D11F%* zn;_NaH|uMWB8b0DmyQI9vgj0uHr>97(vD#kKsYRT!H4{HK0ssFUC+d9DI}Ap)U#YO zDLKM&%5MZ~s25T`|B-!hZw$G z6C-6%R(n6fJsezvRJyVDMfG;uL>|lR*DwN9bKx@p*gddSpx1ut`|;kqw&F*>08ZXHpE4j>NLQ3+g*045~q@ZYW>V=OldQ0;W!x zlrK0^?)J7&22NzYA4m%n~$zVukZi$ffH(Qz63?GQV1D1b0=b zRXz$yOu9KIkcO%T+;=5ST&k&fYFop6e;&mxRB(K4s6cr$oAM?jhHLuKT>{No@ubJh zn^hjJE{C?nCbS0QiVRF9#?4rKR3vt?u(8{lUg{$;t029^v$YW#hQ0eHJ70e6b_}mqjnep0Vj<{2^ z96~N4>NO?{b?iIO+9%Il(cy@6U8PK&7n_WLNbmqe`yo9;ns-ESzjfx5M*Arg_=3Y@ z=8^b~We|7$Ee^4{Y4}VUO`ms9O5d!R*dDph zrti&tt9J)k<39ythHqC%^DGHfe!hDto7odYPR?_bxbh)d_eRV#5N)Xt&{%i6Z~nZb z|D?owvJn3$KmC8K>>+#wdkE02vjqC3hClAX0-9MF89TGv+5gb^{mscQGFV)pnUm0G zYfCGDQGBE*50z%tOnf_+dOG%sb(#KP>J^TGLpP035jMStZ|1uBd6c~Qd+fFa?~j>w zG>aFPlW?xEy|i(NcjELfDQqdk_Y@9hXWL6?iLob+L*6|qK^ETeECqeboVd9G=-Zgg zAEyAks$WI`@3MvTF9J9fUz+qbK6)aV`-A_ralPa=u$eUZ$Pl%oK1Nol&m!x12 zHOX>aa`l(Sx5rt-C%3)(7Psbgf%9~IbYPhltM&2bC4p?GIxH6HKE6lHkF zp|~OUa@hs)9PL7hv%&QxYw4K;sG(1CTCc5tDQlYlmwJjwLN0`oNZMZr>HGbM|P=0YsnU_VQf8`9t3|yFEPk5)$Hs z)iM}&2Gy@wS>98-bo4?;qkk)9MSW_lT+P-{bJz_BG&JCvqT1!QIG7V5KhMf9ddz?YAKJ?(S%KxR6U%v5n*n+k#~9R`AF^%Q`dI-A(djKwbshiR(Tceb{G!(^`| z^D-|EkBE%}M=LA&;?=n|GWXho7y!;k5`q#U8ZFhr%(D9!$a-aya}SlxJ2K22K`qEM zJZVVHu7plZ{Y)k~EP;C0Qsh0whKM3XGp?hH(9$T}fPBWE>@0YiCDC7Kcmt4Z(f8s? zM7Y`w5sj@@narn8$KwenbIB!-rdLaN`RfTZbb}~Uucdudxs#o2CK#do*}5)7RlB0A zLvFfiwu5Gp*hT3Y4L!@JoXDCRR%wX3Ihal?Zwg?X{|c z{hm^wL%@Y%T`0kgbGaO)6# zrc-X;>$Q96hw|r|`ID|y(`~2-%i%HmW5eulYnfn-))BF3N6@WZH6$Xcvm!q3JR=lFo<`Sd>+{qN3So$l&^3)a1E#MW{aib zV#P+T zS3#E6d@A3qhL7Shi|j{7z(RpIF#hAXx|z?WocWp6dKMb$oSacDz1u z$rWwSF#_s0_OvP|VH3Cxt(zN{?TTMn)g>^=$3PMoncub`A zt)y|Er4#ZbMp&foMj5M8_4Cc(|6zHUfsECiHh-S%j z#<+eWkl9tbgt$+zJMDY$Ncn0Bxga7+pC&l9KeNd6!Fn&C%8Ihl0mMGsQmk_Ro7dy}NpBs=|&NP5yYbt6LHNZtr^BzbZH8F zc>5#148SyFv7X!13`~l4fCzqy0Tb0eM;(e9zulWijf%Id{g^^Ni`&eh#grXP z=ByG?q+ZUKxV>mnDTI}FzExB{ij|(+ao55r!W){Q3vs1Ein>VZK}sT?Q?Ma;XZ>2s z_Zv{RGZIb6aJ^q#pEy$IH?iJwQgK!B;8Db()#dq6!y)6=#A43GWx+-iv*mlyz5xnP zEy^AwWP~~w5^`$T5z-nn7z)3=-QaE{WER$#M8BS&Z1eh!do=Ey10?qitH`2V?Er3j$)thh7V3MX5fxoU|B`DK06Au^oDvWuWNg@JI6$j7NNXqY<*6Wt{xZi znS8gRNefq20p!tw;eIY0Z7aO^UvV1JCtbZMSV)A(*$R}8! znhniMW_*ySrrup5=N*>w>(wd-d#->~2|T=p!}OGnh$?Cq+(@s(tDqN^c6f8NRo}$W zX}m`uQdKG35;k_s;@z}a?alAa6)iu82%)M$CbxL8qVxl}!o^;OvXkHqbJRR*G#*l? z_rNKGbXlRyEm7{W zbjQPqq=8+=lH8XwP1ie9s;H*klp{JBZ~4nR+eV2}Mq}F< zAswg{l?$EeYC`2w7)=BakKN1pMsLLUetwC$_~S0!Llywm9i{b(K(-#eN#o#U$cD@G zkZ-05HY;WOJ06bqj~0`)I4fR~Xj!)jHiVGut>QRkTHx2dzn zFDVKEbWa%w?=4c0iby+$l@)-UQ@*GU_IGBv#&E=5bv-`3Bx9 zo)Zx@S*!}vU_ezM;In|#r$~SVvRH|$e3F~;o_I^@BoAlbGn%G{mHh1po>`69n%=&2 z3@ZcjPHlBZ2s6T!+ZWy0dP7+!Zx_2H-x3}^*nQLwbjEKcsP)3?eQ8#0VoFKH!ELW_ z&$`1g3%f%H-%*{OuX;Z5@Fp_4pr3}eeBdwTMWj1B@u;+ zz7x(qpfmI^=t#TgJ7L{}FjITt=BlqWtyQ(;CQ_|xk6QFGaqlmt$5>aq+S=w<2Bf{n z6t?MPbxt+oP!%R9;?=OTh%rVXs>`VZu}bch_g|%ck013kDF$;O<5VZSv zE&X#!>GoGrq(+N?J+ic%l|aUt-X_K?jLny`12d^qY!tV||EW1{t!O=Xt3CVaz zFj5UazZU_1e;~eV?*LUGF#JE>j}q<7?wdj-f2eTJ_I!b%l|$GK^v``SO4It``J#Y& zzAhkbgp83Cs;mg-g7N9S$x;3Nc0r*LQS8JPF`BY090$8XwQXU%{^KJELEw)SRZGoP zeoHedqVXK}Y#&6?k7gvyro6R^X`79|jJBuVpcG^O0W(H1=jx><%xGGr|_&hTT|0T7ia<2gKPWd#`EM4{}$)(kU;T6Yd**6>Lgk7^cciQDOonk%SXcu~a zpmI=DbdM4pr5YWOZF|ME$G`p*!)g69HfQ(ZLbg~s9*e8{R1sF=MuBe@Z|vkuZKFm| zb`7GgHzC@`>$i{=OmsYtQWAT-`5f4D3+s0lGb)5=YIxepv%%)x7f~LAxsceoAI1xi zTpmHxpd+$&>DSH0aDYr;U_ARY3jxnN+}HT%0Gixr(>F_)7P(@ z1ab}~N;NXF7$I5+I6xeFbZGC@nm7TZ(@H1ky}BVPziPOgXqsh!0`*8WrU3~))imi{ zPj+4SvTz-N^{HoPI$P(mhcCl0jxb3A(N(!%MSU!=95w6U(KKR!H`4pl7nG72fdDwwsM5k{SWeN04chKPW*? zr{^)Ws;Mzsc;BgF-OiPH)e%h*a4-8R6R@o?v#zwTDan$#O4dyRU50gLin7LREp7@Y zn7-P*P*84(wY-PxMLjpp;z|%c$qG6{zbzN(d*X%U1id^4Jl_i+z3IK9(R!sr_OnWn zSsP2m52)u_)qGxS)jg^eC#BOBp>5rvMEhDyfH{y%Sn^<_Z)6HhJg52K(@SZYw()}_ znY_?_v{$#k`&G5JZ?VP=R{9>H$mMWtcJ=11)um1>>qc!iD{Vq%u7nHE$kir1I~+IX zGok15So)Mfw3#BSPW53%olGRPhzY`lK2L_iBCT~`%8};Xhm)cB}bHE#O)^kb#h=%-WhaP^y`4X%1N`&{6^!bLUU^1fK2 z3H3nQGJQ^L<_LSFstEY0f}6lcFDd`hph(iJu|S&Pw?qEl`yYNv(>?J&7&w|)8vSu4 zJo`4`Cs`U8yyMj0KY(69q(b@!R>w)}zZ(kZ4-9BT>W@EMgah&8Uniww!PZ8O$Dk+( zNYq$&jen78I|lqw{qlG#c#&0N&j7u=+=Qkl1O5SoI=2vty`UX0@$=pU{ALDB@JK1JrRF}UksZEeYJXmbC=fZt-Db~MNUU-}Xh`}j-n zUymFcpp6EHZ3gx4u-jPu0r<3MZu_CBT|kFklR>>kByf!`#DoJrZU2e)=+9lkX$RJ# zr{cc)cxHp8Y@boT_h~M|RqI@G4udWVXOxje@JV@plC9 zs#vfDw1U49oJ@jWG_wB8biCrhivqzi1&aKR=}d_rDBT}s_wg{nE3?4TIf)Id)hETKQiYPAvPcIQ!o*z!#W?WhhcQivey1z?GZ+J3jad&aiw9 z%D?iRQP26$G{?^oe1S+w4o%P{>e_m$(0vo;>3oJIC z5gayLZI-`lH+;E=Q|t{dOj9`QUyDEd9RYkw^C>~Gmp>Se;PmwL>^U+Ce+IfS1dJconBZEb^e*lMmnpO69 z!0C`b z2w|Tr_y$+{!8Q2gE%9&8w#TUO5ofRsMuz-~`gaTR*=_#!UFFBIKQQoD^#6h?&Ktqg zSqC3d0o(NH--E*hSe(alwh6RwZ!Q8*$-L9`lH<3*2IujdS_2D|?E%A5Q|M+2kM}Tti__1Yðr{5bi)>5D@+c%SREe diff --git a/qgis-app/plugins/tests/testfiles/valid_plugin_0.0.3.zip_ b/qgis-app/plugins/tests/testfiles/valid_plugin_0.0.3.zip_ deleted file mode 100644 index 613f0f1f3b60473e627c61d5d494e2039b5a064d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45559 zcma&O19T=`mo^-yW81cE+qQYfwr!(h+v(Ws*ha_f*iQa_-uY+Vd1l_3`RZP~PF1aY z?K67}QjufIwN{49xx# zR}UBZARsW*KYv&JqvWsl8&HIxNmHbnmfhoYq#@>S7 z!Sf%A{m)4a>HoXp8nq3(MMgy58I72A;v}n!*(k^+MNz;~P@C(TIx!aQM~7w z7wlTM73sWw2GC@(hWpuo@nu4?>PkoFBCZj3FDf7t{7pUmQXbO>+WLYB0~!{6o|{Bt zi%7Aq+>YwBwnU0*1qD;jLo5kcg9I?&gzmEDuiMj$#kSU$YQV-E-pVXth6HwqKtcLb zzXqos(G6*pR)XE307{RuVo60XiI(g&>gPNMu03*WISZki&P+@wj6G9+Ct}_#=<@7r zfGpzswslJ<#QF5RpM+TBMU!K`Eo!QG>viZ+hCvMm{7)Q)yX{TdO$sg56G|8V1hFD9 zqwq#YhJqBi1c=m(s5J=)GnbCLf`s7h`rMtkj)hiG92=$fe4247qGL-lG42R@ejlIo zoNhindW(d9DZ-y312~@DmyFA4?gLBj>N03g!ChQb#Y%<4oD(1gv@+(`jOV>ELnm3) zCr^MDWc0}ULDI-VN`&ag&+*tY$h%<&=3s*2B+tC2KIBGO>eEKrmemxN6UnIYoURpU zi84dg50)BBQJ>4E-2$vEnlm2Oo9=U#Zj=KNi#%aE8j7sY!3>xLg5hYrpA5KxzphJc1OMIoHoD_T#le7p*kFKw z(Esjz4#oy9_VzaPCgv9Z;dBiD!s*oI>^IpFyU#RWcSPJl>rmfRDbri%Z7}JO%iQ3_ z48Q3n(jb*cW)kSz`}tZ(O3{pz&jF$PXiBr#n{Q3>z7~5uxhXh;xDSNG40kceU%MFH z%e&1r5XOe(sDilt3{-1K#%@ufrpE;{L9IS-DIlyu;uq{CU64L3Zi z{LS8jVQ1KJZhErcl2ZD*Db+jnm^IfU+@_H`HDTel3LW+KUa|ZB@v?UG@p5!-!)_DH z$GP^fo3Z=bI47neOyE1r5l$B0P+zeZ@C8ZQU`BH9d-BD^GVIA+7+z&Lo&DymdmYld z$V`aaiVVVX#&iSl_3<E6R;5!}l4&%UaM?+fR>V_~-O56{uqIck}t$G;yr&)-j-rkWO zsS8jQ=%2CSCnc9w+VC_N@2>04M&IE|&)4}F^CXtnph3ER-pD!d!tgGX$n(@ zYo(0SyXN5snxB-OLjWY?M?;{w!H{5*-@#6ySV#&`ad!=ZmbqSZO2N&0O_YdG0k(xE zsGD{bRjg?<&1eppNWoMTm;#E2<$>ep62^WHfIG}%2j_2mj=`%nk{!7;FHU^;L0S;} zWE7%(3821(vy*#4IuuNJ&zaemOvYLCQA}1hY@ORhXA<_WORIiBw4=@Jpb z{c`cmx^7iyq!tiu+L+3bqzT9_nrWIWXspwF@Pfs1B!1R%VkIu?kNpl=(rE1Fvpnod2&LI;9+|oy@Z!<%DqFjEtApA(jZ?c ztZd^AcOhMhn6kt?XP7cgq(RT2c0;fyxL{MITcd8F2}^OJM3SfeG}VlE`2su$6& zp35F;AU%|FDc&4H(VuT}N{Qip^UN5>42NjL7~hIVk#9r~*z)O}_%cbEE0XU~Qy!^A z2*!5&5+PMg_D>CJ-@37Ki>5mHrZxzegp2iM<`Y zgWW$v(!am~k&5yXaM0L)>4TG!6jlDyV*C-zZxDa>NIvGSe;QzCWeH)R+G*U=KPM2z zG7_RdUw{4byUUXQT!C_s)N%#_f`kqqtx0s@Va z5*1RF9H~i_vD8(=9PvNi^tL#fmNh4rS|BAa{rN0NR08&ca{W7D2~3SzInA&lLB1U; zjTj>;NQW{24a3|-EIl>92Ij2~f~>{S2w6yRgPLe=kMu5gY_j_p7lTa%pM|L9Lp}U1 z=j{#u=_`jMhDjv#J``VohpqZ!Qe?AOisv&2Z^N57!Q;7+vaH9qUna+M8x!piJp)mF z3$Q6b{Bgc`T>}P+SybrfW~I4hNz=ss-18GtW#j7j_58xZ1W_31`fbUK8cl4(77l2r zu)oqCj^Czg09U=(U8eq)4b6ZkbZ6AsAc>BHh3f^BB95T&sV4^v7lz}Y`m2;Nyt@O) z3c~a7p)PVQX?PNP%?E4dbnl>o!NcbZ*eMj6#*Cmuwz*LaJ)oOueVvfNmw#&DL#s5I zD`}4ScNY3|j==OzFeqt|7WamC#7K~(iRRd(aSRb=z11KGgcSN0sdb4|Cs|ivDN(1O z|29x?H*g&9lGsX|b$0f5GVGR<9=?HJ?xK=IxWv0>np$-;3oKOhtaORV{5`B5z{7h+ z!?jLv4x7R0+@Wa=vwBy5u!fn==En+A7H?dOd2ywg%w ziG-hRBCE|U)9oXmZk3Zn81x5XK(G-X zAgc#m9fFere!R?*Kga)|`pMrGgT>}*?d``qE(Yr-#dNR9^YqA?5Wxk8t3~j+vBYbT zBg6b5+Ssx3+G04fg}#{}CM!*yYy2rcJlZe6?iO}(3cbIYq`COBQPkFhT4i_KC&NUq zS3RdYPZZOuBb5T7X<-wy?+#8`aJ9L<_Ii>MF>m_gybh4lj+lR2PD+T#-|?YpkE zlmr$rH@w}$GPX4YWz|C&GX6Q4?9D&5tJU6?3H;-zKvH7zqP4Hx|w!P{Vj4%WBLT>Fu`e* zSvXpYNvgV6yPr>QVT%mXcI%*f;frZuEQ7lo+mrPYHjiFEkyFQwyA4yP%mwC?$Eq~y z$nRDdo-*nojn2iUV>9aQeW%xcEj{qj+*#2@Lv@<_Ot1^Xq)|=L-uG##t9Pi1%dVzU zh8(FE+caCW+QE5ijPas*^|LRhMZfs)u_)Fbigl&Asni%(9JyNq%=P`$CuVwgH*DSk z8r5J}_&(_8_fw}EYgr3x8?7~@x`yV1;Yk9z)E<@?{%^+!HI{|r-DzjBaAHN#6)Lhk zpP|-OpL-X`4TsDGZ${MT{D<0~`*REtD9t(b7`%Hg2cbxJuP=ko0{qR5;jygMi&izj zls;2Uh?C`$mu=_*4c(QR12Cmx)UiuWYFhmc>Jb~beH+A5jlNZ|43!|?joe6pq`TQ< z%`Qil$_%U(kIq}P51EY!Oo^dMFvESlKv=ok*vBo`$UL15ya(ygnw)9WZctGAXkq!F zY_GF&*u$)9C}6i9TCf~tyys+7oBX2S&b8uT0cu0r1V7Sw0@B|m@%YTNZP)T7_nzJS zE%tf53lKTl=~7$HFZDGGp_~UU@mU#QAM05C5@tQM_7rs;Mzm|G*Qnd&;iY`iPPH`` zq;n|b7Y-uyFy^ImCwjp;fAl5xQ;`M*K%iwziapWNKj}MfMlL}cB$I!o%3~eqvJm(c zE7mPPNEZ%}8EPHMe09yuI$d}(=nJUVk6)NGXOP}EqUdlw3M%4VE#pFCI5nV!x&}s3 z9mvz7Vd>D5wRGr;iyy2(C1WWJxy(5snlh*c;#MMy^5r%$pp&_`OWeY^=`(5prQO49 z#~gz9tioM)CVRq5n~`;iRBDEfjINrh3cBHB*icsFnMNTh$FNKDDLcJr$f*Z^nrbOw za}uos@*~{y4IKnEz<)T=>`l9OGKlTJ{#w`NsC{bv-*qNGG#{G6!z9EuU3moSX|;j7>B=uC zHd}JBh3ClQ)QGr`PEWIt&z&B81%K}!oFBgR9#Ti%aqk7DNZ?EaoW96iylM9Sv~iF# z+N1PeciYc17mnHOg;<`^3jj#AjOMUmjO+GDFa3x5^;aXE%zQofVkEh|SzgD(;hgtmdl0!5!;KENo6E2R{@l5x15|)PW>QQSC;~-M*AoW2FUdKX@D!bW@vX z&0MDxbUa2@K*Ktaa}Ba)li3Tkrpup;2CHE|z!RUyrtRyzeID1)a21ks3j`1AU%;pb ztZ-N1vuk#k!GXna^#*@U{YKyvFdtr@0PlQy#5;B`4VPVv8q`B&?=3G=h0!@3pw$d! z%QjvR&JQe4e`>N)?zCik5MJV07P=;=ALxiMIB`O3KsaIaUpN{eFim!J6hrQzI|QwHt$5xxEK`J*++uctnmjCh-HWT;TY6 z%!Zzd+;0m=Y8#Xw@J=%H+QVOef;CVTe-u-vpr>f*Z3_<+7MIk9ENjVqj@eB z8^wfK1Ai`aIy<spWE$ua)B=K{=<8 z7i`BoGS?rb;xR7>8ursy4HRK91)+Hb%C6ap?g}5FEwlwRY1S!CEfl26Yp9=LXPoWB z#3B^=H>5816SUL+=xMN?oLwhG@JQXj1RwBaI^f;4Khd^y2XBkfJ=?|U$9xWD zY+Exq$DN6V{)Jpz30Mx{DY56l)byY3#s1Ck+c=qj`^@ZYP)MRP#-JXD!;1)~8nn@A zhwR)*y|R&-ucrl?4;8=7kMY{2D54VUlh;4Esb)hwj_WZ zXoz2gB{q{s;3a0#CFXJR=UCxWI#M*-3)(q%#E6E8L-~-a#nlL;3wYcI8*s_Y) z_Zbq8rqyrS)Us!ff~nPIJx88A_;Vw3=NWkfMQK+Z)Af(L)pL8M@;-@`MSQ4nJdc4k zfgreSO)SV_6AwRT-pcd7jGQ-(!i|OCpz(ZHG2CA-bMAv_i|q8u>p=UTPTpkaT?kNI z&xC!0Q(*3KnNma-n&Rbz?&)d(jb5A0o$b_NdytY{`|Eh|PpA2|64Eo}HxLAiy}xK5 zk>+usSwv&|U^~T>-J?g)mKK)+N}0{OC5H2Y z7Eyr+JF%1PetqD^-|fiw?bR*lj=)3#wSof6?wW4_gBmthAj)}0o`%rq;gTX3ETe@$ zzZ*r4@lFkUwR_gl{2OmOZDNn5GGbk9!SI`v9loVqJD6CT)}KIOvJ?0c5+zUqy7&+EF=;4dfk}Ozpot$BWG*e!9 zUs_QS{aA!(<}R_I;-PZz))6#|RiCARA<$XSnt}UWX-h)OK%GiksBEh_4o;w1#ig&8 z1kn=}k8=dk3O>c8fYks6elV+-GN6TKxLapv_w_qi;pYb;o+Ly0UgUa&ZbhSn1ca}M zbHAYU5Jn;`DBV-g<<#mFLoI!}rtyHL2boBmf4p$@H+Vlx46W3+FNjQF;r8+wo^ygo?Li;7Ix46 z2*%$bm93eJk*Sf35xt9t%Rd&7E+Li8EdKifl5Ik#6{rxb*xL(qpw16{2eU!JO2Kbo zsN`~zL|3Do#CK8RC>t-YF$7|;cJVF<^Ev%)dm!GAU?NdbxG-SKF6H0eNFAir2;aD! z&SNq@HW*YYxBBA(_IIjCAj2c~xPY<38E{YO`GVno9?3a@s6(6CPL4<>f_;=lVvgEX zPWG)>fA)#1iJ`7|@fjT2@+{E1_rvZOAaLRqQ%-`b_+LnmkG zmfd|@Sv6gyjSZAoKMD&1&TIAq4o}asl9zqN=?Xj1#6`3^X51X zC}H5r59|a>nnjE$mUNe+!SR?(74%5;Fbj@$ou%TBRiIOJn&Zo&Od2?tdvQ6!eXJ0~ z?;!s<+gXw*Lj5c1z=TUJU$OkPFo9~1p^qS}z{zfW|Iu8#A@XsVwD z+LuVWKCRl^?P}+kxwwvwm{iKJXenw^V^M2>X|gCRh%PH398Kz1&vv^@QGc`rd#$WZ zqTti1^T%|F+2n5COp(Q%CnI*u8^Y1)%d3%x$@R7&E0zn71b&%-f$ix$F-M-)(TuCz z)JUHH=jz4IO_p&d&B9U#^Dzg;^Rt4$)3c1ThPE~NaHE%uwDn~aw*e_y+DV2HfA4w5 zm%hf*V>$vsnw_UsN)^Ia@bIT#9ofqh?bKB90EpVmX32z=?PX&jrT0;X0~*~;iM`+J z{ms6@mB8nS0>RW>aeO*tUCSm~KAo$%^@N?ruVXy)k@_VLVLK^j4b64!g0??Jtf8Q8Gt;22 z{X1uNX9w$%S2)R`S_Y@0z7Y$0%am<~=}|?l(Usk?&`d;fv8UNxs7*^HibHSErX|Z= zQTIuw-AMAsCtvOwVnyvw=O$ZDq{sa5muf?-a+gv!#WERVap>jN3YOe9)6OYE?DbsF zA&^Gf?%!_M!(PY4>_TzPU25teroj)vCeH`Um+boDXslo!o&4}3Orl&rpgN&g7O zjTT-y-&j97f;<1Ue!$ZZ;&~Q%I;SD33P^9EJr&&v8gG!$-ng)lnGI0kUoIBw=trA~@lBxPo7r~-waYNu9eH%=uC3k)_~qG9JaO+x3euh22LA28>f-Yl(a zlGd9BT!0^_FmI@%LqwXqa1``Db9trXE0@@wl5K@!V=HoDaC@e19z4oPKA!g?+uok} z&P2D?Ul-XYzpc;#l;VbdyjyfN8UWtfF#NoCY*GOG3A4q|&|7}}&BJB^5!H=dVL(sBV zW|;lh893dLurH-V{!mQ;u7~CR5{P5OCNcv}Ww3tA#YgSNpOplh z;thiPc&WF3M}1VtQF4-Q=bF2b51dVmS_trDuQQLQ4+hQgK0uuIzVEeC@p_?8x(Sh- z>s@^yWW(inS7Q`5;lz@rZM+eJJ^?9uto+-W>p#uqL5A4_s~}>zlvXdwVH_LS1C7z} zM?LSn209LQfo!z|nE|<`d^Uw;g@|D0grD}Dt7*f9xiCbyMv~)-YTCf^JS1p@MxxRE zIFfB%;S?>d!#5+=(|CM|7hYWo=PeP{R&XbQ*KrpCP!UCF!mvO3!e{7w4U}lZ;FC7Y zdT+IhVM>jJ{C=dEkPLVZ=dVIJ%cVRKwarV-`y}?1MrV#P56Aq9K1X&y$QHDty?2>D z8ZolMs|oH!DzQmT>6CdWRfJc@9nD&b!0MGk2lJZb{Si6;jsBS>QfL}vT{|MJGO1fd zRhS5f0Of7a8Oo${0;0}V3MGim8HmO~`lhUj8Vg>2&blj;+OL%)lLKS6p7t9{u4qhe z;6z#QO?u>Myxr8){)rnW9)BG9(WPSyL7skC(F~t6PqysH;F-m16v6wg2TvEy*8M5y z+C!O)xZa3jS|xQfP&hKsy?V;_k|$zq2y5UfvN7iHnQdA*b8-u74?xcpHCne(WwHx6 zD3lg^1!Q@VNFU0C4@4kNnqCauLP2;J%b^{dWw#FVJOsr7HZo2K((vhlmaS%7X&Cfr-UX#*u4&%#g!OGpOG@&i9ef7MQu zaJx+YdCFW9{J3UHpmlJgn4Bs@?GPGEaTQH^qk`b%@U6ZDvQwoDrF0H~q+oYzxTM>h zO54ap|E~cB)U0Q;{2_>246eXOGJ7fJBg_72_8QKBOwiObxA(2%>r^+Hdb;C#2>sa6 zEGo6lKgNKIwit9S|@gUh0rE;I(lO~pel{B9| zS`7msh{ISWZ|XsWT|dY|aT9k_CArJgGjk3_}0&2(zHZM*bP zsD!WEK#C8Su!<|fA$loJz66}3<=6tNJ-*sPR`hJ+Lb0Im?MT%7LdftJ7SAI0J;pW{ zGB_bx6pemGBkYffCCg1FuS?M*6CN{9k@igY8y_)DOQF~OrikWrYPUJ&w>RsFrbr^| zGSF7ME!Cz}02gKAAq-VZT}4EXYlA?qr~nMe!sjh}iU1wgVRfadMSQ$xBx_1qY~00C z8)=0Yn4as?Qk7T*GCjwNwn{~y>9LwxV>Z072paM%L!4aZWv{tI;Vz6byqY<+y|A=kUE#UeMg`y=Sz?CDBgF-D% zy?AFyM;WyqqKv%NX8D^)cf^DMkqol%Gxv~|P0M&YfOUSI_6|61nI&5o-r)1X+_<{^&k$Fro)GViQKI2R&xW|v>mYEH>RCHFhGf;It|Yx*|@?B zUZ5NWT{DGIG&BxNA*rKS++mxSEMcXo;94Jw*Q(YkF%|B`4lnm+z(QThqM9jMgH%)i z6n4Y#W4Z3n)Vrf{@X#Iykd`Vwg5jl?l~Y?i%ScjA=NIJV#wAfa}>MRR@X0gW5w`W53yPIr7j3Z&D=4c7z`~H z8*8;gdSbq(P~Fl_Q@IFSGVnC@6(qNl8F4xDF4ml3I@+Phz?KTCnPFDyA+nNWO({*H z9b+lxc(mw(x7hfSQhO&D!C6{b8Nh2h`axEO#xzk^{gRX&s_AS~VP`>*-mr0i;2)qt ze{ww3g{~zB7fMDpT}v%(YWDj)O9Wcm4tJsYUY@B?A%}{F^N@qWc*p-8bLWb{2wj^nSqb(K{84bWV5z!L?3JmULhR})FgY0rA{hsJZMK3qF2u` zM`5PrhtwEc5jl|&<*>f-iV9|(CQaFNCTfrK1p{-yyj|7|EhYE*{J!&{esvdL+yXJM zmT}&V66SWgk_XegmK>Z3bkE2}> zvu=tEb3$@--kpQQ#-uVp)2W7;Vn>=DRcl-5W=MmSlX@N`a0oN)qlN7*5r;Pm47~Ur zzl*BG*p&QXGCVHh8$u$cxKIZyTVVjfA@AL~-m$($OKDh7aHAyQKaw~V6-RCg+= z1&kq)^i%cBUoi0A{C!Bi73e#dgh~MZi6l$|*6;zLs{)|9Auo`i02!T$!dCo`NBB5Y z9E4U}s$$EM_|E9fI@qP6fS~=SX{wKO`rR0zb=M^Q8$jLlgv>PP8ow)_+d9kBh9xbb0w<|-M zggsLMr7;i31soN6nOH)+-b6Lo{0s#F*htdSO2@29cq1psE04f^=`3eFXVS_g_*8!T zA|$>)HOj6_aTKrI4;<2@jdDxKr`#-HEADgYJ31 zgw?*%#RP5Sol@wz#zf=hd^ne{f=aRQqGsWZOY_HL+XD@jHByybI#m%Am;%;h&WFly zjhlC&z?lT!qFG45QHR8oY8O z&d_trF98ZqHuQ>|>QT7~CHUL+uMXD15>bEpnLcw7iXafM+b{pcXfzBc@Ci|+vWYEw0jNU88s7spJ&)lt1IsrZBtPeg%_5?)M zbSEf#;|llX8irs3V2Gry0`Nn8TxH3~y?-5Q<4REkq`JWu+}%$wV-+Ury!&kmP@?wp zV6z65w!5fU)a%gjQ3Jjc_Ee*Wumcxhl^rx%vp882snXv|Tv2gzB9axk))iC8Djg%k zA6Fz19xz_0EM^4oXN0hsHHbJhc+ZF+BsNGW4%my(D;YZVJBx@}UIJ7s0t1-Jq>wKW zLh=$qnQ!})4pR1FISJ-~)s>-V4?TH7i@w>)IMT!gy{}F-3iYX_qs|KlMw)#DqjOLQ@n}>9h<&^^ZbYm8o2^V)JtrJ|Q&u9Z-LX7s1wWhL-@do0c2`Tbi$JgX&E7I_c-DajJKOh1w#VU!-<~_^FOlZIEne z`K>&36OLrF@i@ha9uTPN=V%_xH6yO>)N-HOM%f!ks*NUjzHzqZ=U$&nH~1--QkE zm+H@s8uFFWw`Q3;Nz1IIj2mop&72z;S9k?!!GRRFY1ysq=w#t*!JKcV4GGW?6`%(_ zjrZ?vQy=fkR7C~2qI>|*q-C^e_OjElVt{haFP9v3BVmOwej79m@dKl=8 zcGlse$dK((84?ys!@0#2$GtNWYNO{_MT5@8%j0TITv`5d7yq6ErJUru$02$FbmjGx z6z@@Dugq1y)HkX)3uwzKl;Eczly477uDkZQj>5;$7)CnAc;L{IV^_g`t#UHC(@&u^ z5^2?Sqhv^hNQ|jJsjdeGgjbkvKAACwvqFp%0o8(JuHTb^+iNA5i$LhrSftVt++vfH zd+!wv9?Z=r%s1IcS9S5VotAr_u7x0o=~=OX7Y%)W+U1mvOZN_G)k%SHRln^fmdX;K zS#DtR#x%Xamrztue&C8&GccT(H(tlVY)ahk?YzrDiapo!{0#ko_(c!9iptM(eWmE+ zot;~huJ|6Yq>zqp^IEVzs*S0H3p;ZCoa=G4tXZ@0X*5y#&C|P$+_x20cK#H_P=ksi!+zXWZ=D5F3bU~3#T06c`y=m+h*7ls ztFR!;{od1YJPrf>|4B<^Yi@Sh|WFx2AX(~rCy7W{KzC-40XbAQI?jeIbebKZdVwjSB> zVtyatadzG&@W;PDX}<}`wX6}}@12A8I8XG)f4Akvl$%;Gd^h9FF>XJ@Sn&56`2BGf z9_V@E4_XH4--*R5H`V&qU%ug44A%oEG-tYA#t}wxXUm%nFOladhRuwRAh4}4^|3#A zVK|vQN>I<&Ynl7Hf9k+^>Lxh0vTOUMYw-a^DZsKFw(vG`49L%$ULolI-U}agKe#AaZB!I$Xs=%paunKwQUp8~WJ840HWx{V)S{?quCGu!E@ zZswyNSu;kg=U#$?wwBLvK5}YepCuFv_hUB=-;n$UWvDu^9d8rPu5_?2ff;{TJo%Ub z)a=!CT-M8jv8W%8@-p6F(v;!LI7s{)PlyFE7Y)=w1 zZ#@3V=zhOJo?|%)wNTZ)fXG!uN#c8Fj=cz?VAA?Z5Pokk12&yUpfF!1!v{tNzrPY6 zk@BFAmkwV(UTooG9YG}0O1|Z2NFeGSwAAmGuaYlM#{Pz2_Uj{V)@_#Lvi_-r&%mpK zkc5}udI<7|6^kTt{#i9y0^`GlMCrkposX=nW2W<%+qe9yg~; zIH1S$Ey5%GZ4?I6>LemzK@7~Pe<6=!Jqn*)*lRwNNs(kpr$1EF9yaJPhd?cv;{x6L1>(^UUjJ?Y`a~tbS<|KAUv_8y9Fx-pNMQCIKf|aqu=T$#6`V>o@ zq^e;o1h&^T72&<6pMD__@%|D4xtcUDoOCy!b`x~oJ@c|y88F9XLB7>xQ;ZVU`iBxh zlboSPfkuEVcHHhlFwBlO9;C)keK4)_?Ju!!PP<`Y73FP+@l}dk^0>4L9HV+F!i#C< zr{lyU?FZbX%3z$$taqV{%R7?Rg)PT9dA+pBh$0h=5!of%HRxlWswfDJ+m23B#cbIy zRgFgTXU_11pVV9dD=z|{n;R?+jrJm8Q-TK0IUg&>qulrOwqIVi&p`J#*T_XDG|Px; z@G2MiH~z=bUG`V>BqD3>C~_V~_Vz665v!fYY^G4%bm-8E{Jx!09NE3mQtkI9tY^Q{ zZiO$)*+kBA#mPy!>R!(oh+FF>n{KC*SI+U%H@oKCWPT7mRpTwmy6fT=eB!qjCN4Pw zh&wi2Q#iXT$>e|@p;^zHsp?POxGbNi#S&wutm~)jHs799c@AJVm;Nvh2J6JWahTi> z@d`@1H@y(SXsDcD@3nso0~sY~Z*Q;O4vf;dQ^po83MAV}(~G<;9+vV1%O%#e>$)uK z_TFfcgfdK=xSkDd{fcabBmd81u~+b80b&NXyL|RP7xP z&s*9p=F0U>MlumT4*e~S!XcHemv(jkU0-0t<2neuPd`M%v!Y(Y1Ivc{uYc5?{F7t) z7ftNnH^jfcOZ$rq_HPv|e-)aPbqkpOuN5pQW?=giAb)iIVXG4T3r+vvmN}U@+q*iM zm^uGve%ZgE)5ogF*>5nycE4#Lt`Wh{a@)#_L;-fBOVX+54v ztjlYo;xOvjB>Ke16TE_jZfk4Q5T_sKC6Afj#`H^2GXk0H&`LGJW?Sge8|iHT;k`u3 z!rba06N4GtZXxRT_e)U5fDY8a$-1Fn^)q9(=6DCGBd3Og-AAx>&>=g-CWWs9^_(-f zBh)#B!;i6EV{*jO?PSr-f`gHaEg@TN4w#!NPuBeQz;f-Y!LS-!?xYc<@e9h66uPYE zG{>Tv>01y?##a9T>rpw76(z`LdWT4pmvm1eosm0OSIHfOE+uMF&Lw&?vxxYxPe$v> zkxroOBQkYtXyfhG%r?-k?7sdLXrp$w)5WhZG&sHEXP7}AY0IOqPCmgMK}t2&dR^2= z~#b5mEsDKTyu2*;m~$B;mIi>&=k#@C{hq_d0@i*dhUM1NTq7t{Te~# zY*lvIYX8mP;w$BS{?w2_w%-5rhTx)m29$PJ8=2~A&dd4lUuk%Jm;IG5;`qU zKa~Ro9ceFz!gw*q&O1~pWx;}F3gvkzyHPg?P42mceRk`^{0)9?oJA`KBB$9roWV*~ zAxhp}nNGwiN*;gAdlTEg;Wf`&f!ezBrvKKc*&}}RAo-EYqJzQzKh;oe=*TF?EG`JB z{;@#ZKLlsue`$es#-1)_c1}jlE@n>jj_%NpQ42*EjxbQ9Q@;<83nl`3qMKa2ycdkU|#n9)X%%s-&enA zTcURMZf0p=9}l}!JKX?}A~_^RDv@iMt27gS&f%$JqmE$_^#D8?#|go%3- za7o4x4liR7bV8GCe?FM8084zy^f=n-`yd) zsfJM$7{|d1C5GHqq9ht%P+D5vie|l--Ueho?+pgMy!LrL7b{OKtorN=R#3V>s`1A` z6nqGZI1nk+(dY<`2Hs=@!(7x#cEge@dph2b0sDEe7QdL3$|4 z%?H3_x(TI-7HpufS_}Q4k6?oK)w$D2*v>)XnoRDg0jHxp2^h@|74!}|qO;rr77LLl zI)Q@LQCtNESCLHW8I03bTm+U&a>(q$aXa_XjCD)SzNBW;c4n-#GFInb;!=r1ZEsPS zm8+Z?QEUCl=9!)Q9mF?j>2n3`R^YtTbTYSJDniFL%Ydl)W#FWFUtDVm#?&MZnSNZq zPfqCUqKnK^Vhyk`tW-um-Ho<%UpC1b(vEU8YIkAVQpzRm)?QiGu%J7~iDZt=zA+!0 zlf@KK<=l(=4*pLb!(aBgVR4mj_9tMn{b7y#KiI30v&sKvuS@ZK@BxIV!S3>NFqZg) zQ6#|ZAW%C9$xpqZ<2!igi}x~Ftm~-IoDJU>=BBWM9vWU@Ym1XI#eW-LfJQZ;-a&4@ zJnEjYjAdBW{tB5g(Ij932@e~AHn$-w>2;T*a2J& z4E{aG{0|N;H^BrQzz8e8`%C402i(OG5w|N~?_kqvRi27~QT8jw-#grn z0x1vor?_+ZPhcYb7ajgbYIJcjvU9dEask-e{YQ5EA9~)c`p@jx51mjUBQFvS0@J~U zsR%2zkPH5^)TE~6K-#JE{Y@z@$3xFOhn}76_U7ge)RcY2-2YeD-u|&EbB0X=p@)N| zPpFX2{%)X8f)p_sCeFz%GQRXhfuq}1J;Wy+E|P~%h=z*`IOK+K7zFO&I%Ju;cBI&=h>M2*}N~t+kUAqI8g8?W19|yyk~T2pX1?W`D;oHSS=ZoWvWj(dqqI2C8tq zcvM?El6c!0P&+yicOqS2fS>!d@bnMZ=zbXlF3__$Mi7yMt3LKQr}1KhzDQ0SXL3#% zsa+emM!1f7p=3xsj%Dyw6~!6v3p94QB+5`*${H2uTB4;MalMyi%#Xu>l4j-~%Ewey zObXo8Qy3U`3K-D}cb;7{HqH)S24R;<#T>z~)eYbK8xO%uN|1W8HnDHt-J`WgT?2xW zbMObT^X{u2PfSFgQd+fOS#rqvzLrlA+qQyje4Sv~Ajnq2WpWVBaWwHm!dvf!1@(nvq|r&bEG1&&}=A^oS1OA?P(eIIiB_?LB^t z--In^!5@3ENWR5@dC@Ji$ak{M|XC-^;P%e!BNH`sIL_Fut@2lyqT>F zYZPp+{F9(nHeqOFGCkb6x#OEis>Qke+j07&WP2PM8fpei(F<(w6Jf7DlqMUFk|>gN zpC31k$$nn{=#?CXI_gm!H~e)`YyF*a7-yQ2jjJWjzWw6+Dj?I^r_i3!q z($3~Vuvgfp_evk^YB%ShYo`{&1ZIq2&NG|5_rFwfQVBT0g#W<+XW&3URR5!^{YQoN zzo@AG7f|5e{q5`z8ZCgIwfcibqk+ywuwn7miwP`*T24iy>DrN-S>GO#Y0IR7;VIp| z)3-?WNZcyTQ&7rQ-20qYaK8>f1ox*VHD`bpkW6D9=fmxuQt{HK1TSZ{r^1D7j}r&6 zpw$!)vuNTVgf1F3sK!-~^AS;kR7}kL6h>)OwT5s6uUa9~&Ib?UZE-ZLv}0-a4T7?2 zY7DEGu~zFtP|J7w#!MQI-r9$Jn@eR}Yl4U1i|1eq(oAZ0kChMY2h%&-Q6D}>E~DW2 z?I(Grta9~s5P-x9GfH2C4gdo$4FaRFtx@j~7$I2Q`dmLgXHg-<*1h z`-r2|<;Q21B5 zy3x3H@D|LOCol5R`DinWNkhECZ@p=rCGK&Y0v}$Mb1$@L8Q4>s+8{H+Q&QAA1dThd zlEqbBg+D)d+3l8KVm2_VgdXi;NaBKm%G5=Hf2EQ73p;ku7F!rlH!1x^mt4IEc$O@b z?3%!7Qs6gAyQ+7;sH^Yf5#zj;Y3p30M%37vVq?5I*d3#Ex|{zpe=7^vrVIDR?UG5b zokGv41H^M ze!kBkVyS#;MB#~2Z{VMdh=qyLFieu&Jaq#ToPOOKU5PS25kJp!vXZ6i^}{6`G43N6 zP(Pk>C}3t(?Xn9WJes0+c9UJ9MD+eE+THJ0vhw_s0ptJ3{{wn(aI*T}@~*S{gS$I? zoH_{d&F*FYO;{Q_ximL@98p2foHV_pa6d49tuG`52STej|%^XF68N~dIic>+Q{D9oRg7(OYhQyM@ zih`07s01Ty9GL$C;SU0sj^qAhO8q~dL7@Df+}F_p;A~)K=LT@Hx3e{~bNN5=e5&e( z-3BM3?}`TOCgfOvdc)ynG!PDH+=}3Ohg%RoSYV$dNmh+$C24B)r(bxJX%)^>ffu3& zmotwuCjnt97RFiC`hkCrcX0m>aPwS(47h>C9oj*&BwTtB+9qq_v8NK4Fm_j0NI2Yz zB2~M3HJJ7hG*Z*#6pqR>61JAeI2h$*8+%mmB19mLAu$i}71{6<4#K%aS@Wo3;2I&O zY6(O}=hXQf$)sYwF*xn-^q?|_85LCQKqUw7LXPkKGG}A!n`^x4w)hSdV{AXje83J| z>UPjlOZkTMQkKoWDck-6n_PWI8y(k%r#gjzHh=sn3Z8gK230jBUP&5Aa}CGvMc+2s zz7r-;G8JUOl6&B? zB{B}5k=mhFH`z)wc*C3k!sw<9Olh-9@sh>tA;H=&xCPEG7e}3sj#BOy`C%FjB$e=~8PReYT{(eumL=7)PsSQuM-@o++q9eCd3jZ&E2^Kf$F z_VoMgy)owcV*URp`^G5Cx-8qUtqj|?ZQHhO+jeAzZQHh;VP@EN#EY-G`*n47b-jLf zjQjVDJNDgcpFOeWBKcy%WZa7>$q-EZ_BJ#btn$jR=Tl?#6xQut34Ey8nJ+$kM@`Jy`NdfFtd< zTnVA+{yg1hPqz=xe72p5xiM%UFP@rk4EI^YBLVu8=>|_m9^&`No_)Z)d9g;CMrW^(5~0( zXZVk7p~Mx>^T}fDq(WnD;)os}td2TkIf~)GGtU2oAc|M#@fo z5k$P#Z*!&O#r|ckk|p5I__ZWLvD)Qv&yzrw%HPx-<^@u&O%e3fzLX&7X^}1J?8-|V zFrivuqz9_C)+^^t`2sfvK3dXvcicHs#dnYsVD-PfUnLW5+)*hz3rtpN9NNMV)~J3n z{GJG&Wb>|RT*Gpp3hZb(nnG*FfiDv1G+Px}!dvaUyqCy(IpB^DBE6USVIqWP$cI0v zBTF#T8<%Me1L#$t^{ho7QZFzY3Rl>A4r7$DGe`pwv|#|Ny@cQB7)ps(x6mcy5^7c1 zII#qJl6~?@Pn7!c2DklRkxQ3Akk9q^e*^V>9%KKL^Vr4N!rH>w;y?Mk{{gXNsVG>j z(3>f8X6Kt3qh%^5H@T@p*|`Ol{A7{$3!uOYAW`iFO7+{5J0%j zPagNLc6m#mpe`sCK-lp6UgbFZ1=%5tbT3a4RNX4myN4zG+PEqAHsV=9D3I3RCWBBi zd;Q1;n^<2zA_KZRlt<~-fb!tnaQ)xFR1^A)y6@;U8T5UQ@9a9AN#q91b6dnq$p_~pCyorg$D6kUkXt4IJsQ#Xq<(Q(fhMD4H< z%l&kX+zSmWlfo8g9lw7bm2AK|q=wt$x%NA#^)&`zHt#7=HZhE`o5;2{LR&f?3 zw9UE$j42n(O3X?GRZrSC>0+EM92uXa#GNxDMC}fgF>fDuZ0rP@rt}T;(LWf(*b*DQ z%x$AD-C%HrZq05g1ZZsapq6uCF$Y*qqBs zh?rrda+4x}p*~BLaEjubY1Y`bonc%7cbFQQjK_|xi8K~Wp-MT1?vgF&RrELf{}*TR|G3E%sc6J4 zd{@y-4Z2HNV&u^30jM${5SqHG=Aa`4Bmqj>4Kst|xjvW4+Z6_v7`*DWKc1%7<;&qH zWU{EZ4$sB=Yu~;xYle^L5sr6j=PO9nDP@^}F9rVoI?`YGUJ1jPv}l?&lVk5!PkeUCR@Yl6@S{{=0&&Zz>-*7az1UiPU1X z*%Yl^wV5!LOh;3FxlfHf18+bwZgzjvw8uosAN!(^x#Q!<&@@Gfo{*-xc-+-iIZ<7S zkt{K2JO$^X68Z(=1R1MJ@cm&eF-VUUa>6?`PpE~qY ziKP#TFc75)c7ZB^FIDrJUuGoRpYQnJciI_WB!8)F3A1}~+O2a_%9X%xA1KjRZju@me~9d00--Fj zX`>L#P3CFB`@h2`6G|!>_(>EEdo%puYc(ODG_@WgwcE|?Ra*$CkSPIiwo$%i$*Umt z*fp`R{t0(Y>Z!yXEJm`zgfQ#y1v)dQIxK^2J3zCXN;ByPo^t_sA{!{)}km zTR$h{wcR7q`pfZr(^X_(EuN4%SI2*R+#!_9P)SeAQr&a|l8)BfXP_;CfR8|5Ax~t} zit&IusRMO~)0#w+Zcv!Q>|WVM1xBR7Sy~djyK@7NfWGCqNQkvqG%|caLHjr^83kge zRX=4Ejxvwe)y7ReOFMCnKUD_M&=yrD17gOhMQH5QqEE0sfCgzs7r20f{^k&h{0M#) z&^Ess5F1}KBO1n~KkBc&APCtTRs*Y2OkTQuLX`TbA%n#wU-rZIZ@lHdg{}QnkM!V3 zeih%a$?W^)LHSSKYTp{c-wOrb#4D|Vi#x6R-w6iY|6LAifV97Nk)W2KmZhoNSCp^4 zk)e^02D}x(M)*CXM8MTFuR6vsy2P->N6y>3y+9{CMvKqKR5K`wM-IDO6jwO~W^OER z=pbR4pk$~UDTowbtHP)p_$%BHupYIKAeJDzk05)x@1{EzF}pjjThcYvp3A<%#pPb9 zo@tJWM6j#`Z%`e$-!e!@TU&b|`8@ibdNL-O8YU(tI%YCHE9lU|2Ssn)ApmrT;2%Mk zOxA%{)%Vf#^_}kW&y>5r;Vlc>Z_UKiz{uqPaYjw4TiLCPqJ32BF^FS|11XbIyb{P~ zh+tn0v*>rYX0rBz1<>bLmzIrTxUx5ve>`|yPRbLKye3$qb$}oxWxi!Q&1j#NiBAch zoV#44Zuekvd))px{d2FG?2$!5qpWbK9Pc_$QIOP!FA>ZdTdZ_rP(-PqnWUisu|Z3d zP&zqZRaBHSIJ`)hqN%P~Ie?0!psawUi8ELMV4ixO|8Fz8Ei>rdik2wIjRL1QT^s4HXj^l=k} zPL)PdE|hxeggAH_cO4?`Z&t;vkjkosu{k}o$w{cXW> zE|h}~m)X5AyGre{qQ!x7V!C0kJSL23L;R>UsczJ6z#;-rsx9ho=5X()X?$ijpT`Ne z&hV~++Uwh)yB~BpPWSJ;HvKnIM0oqxR273iQ3klAy)~*X>u>q%LW6Gf{dJj^BQ}ir z`B|?=UyvC{(=w>iwA+k;xyAA-^p5=GPf7DP-cPEX7BpHy?Fo5Pl9SStHj;fr#ojb99nj zc^+!p=q$22jB|EuYb(563ZZ82VNO#4)hp**I<72|wm@0H1YZQ^AJHAf5b(?M6L+^| z)GfVTZ?0^5I{J3ms+h~FKG|S&?to><8A2Ws&^o(&|}EE5k9`;lQ13x6wNYilHs6bF9)f{~9z0Bj zQMvaAa;ADx$-N*dz9DH7N7bLITj@3!uLj)&Fcrd{W`gQ0ZEEubYdtV=66o~btLE;Pi+*4Duo_8YCl1FGL^J}GT8y(QEX*If5vbyzF$Z8P&Js!8?r~R zW1O=I*RrDeC%v_))bzZt!7K@;#>lnw8}t-|<#HKa+-0?%Yo+(kRSovb4*@|CPg!BV z#-5PI4;fnp)7e9-3aVy@%vvM4d5jUoA<~yQf`$^$2uRm-b-g#PGp^s}!+F51_rfr7 zaPkbV+U8VWis(#^v0WG(lJszKAQNu(&_qgGW==?7xr8&!hZ2_Wsqm7kCqfOsRUr<6wWE-a6YcE22jJJKm>%iZwU-`e8Z1EBD#VrPxy zjm{|2%_aM(@KtUA6pm)lUQZAvs78oY;;WezjPIw^XC$M`HZ7~^1o>VMKc3YSkmJ+J zqWUMHm)J^))*6F!&#Hd(x~}I7K%ZB1!H_Q*ZtVJoieyWAcw41;u}Q#{7r?P9&FHkP z`GQL{AncUSnT)U*ILgpr@gCBbQ@DaT)jlhU2x9LUzX#Tka_3Oh&idwDyaMRj4o>r$ZqS7Z4OF7p^KLHrqxn)l>u2!skF+SXsQ{iEa9R@yQ1@;Ow^}Q{x!yL>467KXu*ZPgIg7jW4BjcC;^yFH}2!9i-Tmkjt$H!37FviwSHk$Gme}v!4?b&57=oTGhtD ztLtyuS9io*kgz9$<3&ui*b;JAXGcE7&4HUGZ|++60_l6~h{Nt$?p!1Z^TL)AL`7x2 z>X|VVbS~A=_s!qIpqdkSxY*+knK!|#INV$-c063>7gu+^?6jlO!B}g)1@*4Y*<-;ef$X73^54=5d&MPJ=6zqwbdx$HkD3UYIGe*kf?Hg!_2a4^60w_qT zpu`n3Cxs?3MiKY3B|zlK!j>iYc)WckZjDP@Cdk)dlRAi-Igm3EN$>-H4oMpicIQa! zyV$AcL`mX#7U1im)Q!VQ45R;crfKTq!dc`}F?5OJO}lN}2dH;)-44b3!$CU;o@g#ZgB zgwpZB{G&)PfuELaC}tFDYdP7Y+IbhM#lR7f(8rjT*&B-`&xTkmUm9wwL%vAAQPo@8^Ux3^K-rKqV6wa#>|imiaI70AkGHU$aVrgzP`0WeAk; z>ha?rvcj#53suFLAuHy&Wh?jD<(fvN4I9SKSI5eJszU4Q(+W*HoLw_YkpCD00gl(Q zyR^xA=rG$Xh@!20IgP8D8G=9G8sh1Sx|{f{dl*bBK%llm{3*Q50XXlv27$cBD<2%CKTf|Ye=Bc>%%iR@%^ z%wxE6X@72j=1CMnf%=z?EOd+-z{gRcT*X-PJcN0kneSbPZf(5~M#1hWYiqWhOvWM`nogFQ6ZX!dXK? zG0gC>x7ZM;FyG-1&k$p(*EJ4kWmx-v1h*i8+?Lb`MKrYQ(BIsBRVLcYjdeIQ)KV&O zW-73^AW11x0xlBz)>vyo!mrvY$i@~4l44AYp~i_Zp#q69=);iIExvoeg}s0CoDqnS zF^&ZpV2~-$Q_L4uclDWEoxdN0AU9(fG%%XVA4VW0$Q)`^>)pq3t;0HV0*t9oWV(Ke zbkMDma6!L(X@uQ_U(@E}+`XFg4%F!O;NqqCZu_8u?BhhErMp(wnrHP8%yBHzfwOG<4K^){!+L~vmp01$QnudK-2E6r^F zQA+oYqseNvo2?nkg~(z}Ky=S_slWVCrAyg#Y1w8=DCK4~Gv4)Q; zT$)io+JnDqDOKaF_)o}OJWBv`kzJ%S($JQGxt(j0eM%r>>xEQJD%c9$mOoV_a9weD z@$Fp&@K>B)({}lOEiG7gIq6{=<*empFa$wKmvU)#Tr;WQ)6?6UC2sySzXd{7FAj~_ zd2452nyMW-rtv=-WtD7-+(#YA3mJ=t)f16 z_NWemHeq-NLfF835F3G!jMq!|84!JNN5DoPNIB2bc}BB2!N8BEL+s&=?swB0QbgG1 z9g@py2{DJ()I*aU@{~d020q$QPU1cyac?tIC#$s}-6-)GyfqlGDin1XYv7IoapN)J zgbq(I%IsrMKHB-@&T${@k1^dh!+ccV^|vBE_>|m-csJfmV8fIlrt*|9UMFJab6}Dt z8Kdm_YIe~>;vu}0T{0ICVbJ;sDtc=bor^Jm%S?zNGW3(T@@-5J`&K(k9z|av zC(2TQh21Tk9<-w;ISA-UClg+Y_WH~9Pq2&^Gq1IbD+MR)5KhF8*N^~C-{|Te)Qkww z`wLp&TMIc}N1#^d>=Y;)hz+(d;#xO7uwWZ;C}v&Fv^{GWN|vU040&Fezp5ls2&o=g z3x*9AvL&t8!r04f7 zll63gAyBi@nh%kT)L*GwI26T=5!EJ)h{kEw@kH4=9>KAd9Y}wQ8k|#&jt9&fOWvx6 zg48db#vSv06;%ku38)ri@NR@Wu}=qJaZR1xBDfhy7L6EH48HM9ud`&gKl2g3J1xa3 z01p9O3Q-?hp^@Ag>}Csa(N!XCSeQ_&s~P^X2<=2AtI3+65Rv|I-niyQ9YX2JrH`44Y}NLFipSNr>6zOtJubvQ4ZLtJZ?oXR4J>beqC2aOII=rj ze8|x>X3a3Zsg1EMU4FYkx!NJwM56r)JF2+&;0TFd{~Qt6s_hahD$K^73473S;{ND@ zo(48&R=gHpH>ev->Wp;{uEOF6RyWtTw@;*oEe$b3zDB`^b(v*4NsX1b&4?Fbuid#T*dx4|(^+ z#s%>sgoE4))xcZ@T4XqM&$NG$NUj5Iz`8M;UQpc>O;2OuC1(WkFVD*q;1Bie=CQDR zF0!jb9@#tUE9yHJ%_&JELU4ZQ-)<^YjmNzg?bUo}{;jrc6TbZJFW2p&$htOeMb96hbl=HN$ajf9 zmW%M7n&#|_h;S|5DxZIaf_2#E9r2VEn_#z7CDRW|B!9svtq>6@>xeCY!u7c0tNXwP z2JNt=-y(1Kt6%2#C%%QGBRGv_3o89A!M5L$$}M zJeUR79QO&?g5z@(c?i+}$30W0UTU3=%(W^aGWHhH9}zpkr1xc#~k|8DpFhtT$abO8PjH`Xd;o4-`4&uTpf zVw}dj=5=pM+8~<{V}qi?#y4)1H*35)5`rzC}KE!<7?LHXl5r%JVCteU-e+YrbBD9;cDK|O$Ri+G$`&%+Cu$TY=iQ`f*6&GGevb~e zV_xgPs_1*&b=bu}7AF1}rfch7@<7{wNxAHQ-E((wL~Z3o;>~-05I!NC6E|Ir>CB*} z%+IGifW@P=uLrd%+Q)c9bN;fh4W8li;X||^gf!b54gBTJ9`#j6vuY#+7roSE!gFI} zg4lO5&Lb{0XM*FlxAAp4QD&SawX{|Ot;Rsmu)n?F$-8{DD7o) zrixTc{jnkysW9DmA#L;Mp@yaRQGIx2aq8wCroZpg|ZW#zh^6AM;>cO;qj_Ubs?MXOB!V#z_25M8ryG=N;Ly@UxBXnaYYdpjoi#$h*?3?Egg2L`(~rRuirL6Mr=-V!3he?&zkLh)a0 z-y9GccY|{>)5?3D_s_wDxVGQcXk2|ec5eKz{ftJLtta)Ux4S`4qnkgcXq%6{D&WG_ zEr;y>Q#Ac+V~Sm1!dHCDJ@((6>OX8uMG*mE84+4@=YOY?|Js=Ugh~!kvH6}R0Q2eF z6ZkPd;XkOU2BVJrn?KD~z@Js>H;f1$j&X)rMFNRR;=V_ZZ{Z&=i`2Znp`#?>+ueaM z<<$`ZwTk-Y3u(QNiBr|MEXM1zQs*e#&q4ZAbV&iNilri!tTcy3MoO!Gre|xhXU0$n zmaI7ZK9jquhwI(b*Zqe2$NYS72u3=s2Y_;)u1kdumSA=(@2)LPFhK=1`Z0hQF^oD;lj$+HCQsYb9opL@7-2hb6a(x+L_JX_Izo>X zm}VT74)qkGzu-2`GY-wzA-|J*ibagOb-E&!Nz46D#i@ciNA8tuIMD8I z0R>{`ZinH$a{bs-Ll)BwnGPuI!@zt9oxt%&bT^8F#-1RDZVM!!gXh-?j$A$-TZF=; zq(Ex5#xs$4$~A=;6US@Mj7{y=6p(BfJ=)jE01<7eMM5x}vf%12T`8zpAEXsbsyKor zJNX_ZJ>zj+fi*2*x#AmNxHDQP>si(r4%p=}%hDWG!jpoO%N8Ywz04%;IXJj5%oCmD zlbD>&z zwyio%*5-7qc{}xRZHYj8}!`broCvFX>mymu8j~s(+_JCU)UYV|lQWx<+M7*9NOJv~FP9qwn#sQ?mqCbu+X&#s zB_=>pqOT0$&+qbtUbKm?2|_Coq$N3nthg8ITgmE6w3HwXm3se3?37T|+nfm|o;6HB z54VIUH+$f2Hb)bU$hPjMk2WKG91S!d+F+!yki^`pX4(WaiT8m5O4XZ_n8+0KZ`=h_ zrKq4(KGsrDtl^GYJe&;;pTnO>~-H z3VOp`LrArP)(ANlWGF?vYX}Ca=ceT>Cmaz=Bzg#62nce6(-_Ul_{dB;XH8E}c0A@9 zg)1b3#V9yv^s3v3>j58^rTal!9*ImaYyDMsAYGrhBsb>$xndBErF@;tU5@Qz*6Hc_ z72fOBj|$Upb1$AQneqN-gevQ#mJW`L+66G@ft-vJr)@_cIAwve*s(TX9G$tCAwB`S z(U}mq2fXqQ-=!^v+lZ%M5KpRvplDDk^fze$^6mhnwYi9+nS?nox(KP2C|9VH;^a*c zm{}&zIraz)g5wB?gw2UMq&m2S-Ig0An>(a9^5aW~hYwef|28Nh3J~khopczOHFup~ z994}_0V;83%wsEPSkBg%!tA@sGmF=8jeSw5m@EurqlhAN44c1;|l(kLAVptxB4AwlX$WA$w^(9;cEAzyS&fitguj0qcdLy6-+{ZGj7MseSekKIrGs+e{b5w zxt9;|R4&A`wN>0FN%~e0wLDPi3*_n#rtMGB2(66BXS}sFOy&)S=AjJ)*y@*(?GKF4?Do-p zHIZQH55QSuJMqn8-@|fH@I)!DcENaLaDE59>qWn!oTJjKXiv5*&j(T@`7?CN|5k6cFYg$nDR&S+~ghVon?1t+Es+{5LL4x^<^$$@JWaSUzwnCN=t z-Gc4`RHHlrG|js~Bv1#dES$qFfuUl<4R9z5^~?p+s2iC(F4D?OCoObK5%T~*9Z{v= zZm5d_8O=#VW9IFG`nukJ?LOyTL6hPf)Y`4sUvxoSxUf}SJ2R2dcC_!;k;6L(oLvv=rW0VPKhR<;)dO0*$e5><_`WctaU7$$gYit z5NH{$2B3!k<{5=Zrg^>Sh^~iBqBA!0!G#vce$1C1ymv(?#kaV|O=8_J53I4-jZ7wu zTt4L~NsC1sUE<2KF8i~RIwA;vGRmpSFl?-hIwbJdG3Sn}INXd~>#p0OQ=CWNgsT%k zNGEV5bL&UFXmNg;@Z$NOj8TK>SC4kKO_Wj?k(i?#kNlbcas1kwf5!dmPlm8QK)ANJa8SwN;{rPo(4?@{Hn4Hi1-jUL!0cPH>`XtU-At{A%1ydv=Ueth& zrQNpIN4k~+%=ZSZM)I8B<+|UU`gUa3d*kS%C$CTcT;=t#FgA{hHFKK}1{RMLR_Q1C zJi)MEq2+GJ_B~!HX+cIQ?NJ3m@X|SJdDO^~jdG1r4U?vn@6+FGS^wF-_U!=qzn_1Y zuj6FoXkqW{^l$CYe|Z}JW&87Ao|3uE^5*96$Gv@jN&c&+=S_VHYZ@Phg|W=1#j-hnC2oTgx)uB*mmR zBvXMvVw8;It)@u_vsBufGop-gvj$Crb5D4&WZ5{2J2XPn@h1abxii$JeI8Ee`Y`Hw z*UG^A2>0Gj7!Onb8t8C}0w))oIZr|NuV&g45~*2s@IjY-`3M8~%_?Q{_%M)gF_Yub zmKmmT9W48$MJ+PQjwtAeybb#ernwe4W5$LtzkJ#jlLwIhtjNC>Wer}}&+i+ed;Ir` z{EtO(vHwoT{@<=imhznJIz7twXv2-7EHH7JjK(kvMLbG!c}OU%O<>H0RtZ~7=$UE5 zFj~WpC!KW(3+RQyE@l~TpG%G-+>WPHdc;$D0VUz1G_n1=v4>JW1Q8x~66nZOV``WQ zFh_zh|G8S?(MC_BJX)qkkYQCp8kj{%1<7H35@Tc$F|+;ElRwXIV~YaaG1az*W@!zN z541lhg9uIJfa)%QCYG)Yx7R(C%OoYoFw#=7aQ`$`MHZ&)X1jF zkXqs_LUM{P!fGnL=_Y_lKc`D{CkZF^dT^nxuk+lC)hmGaXS*w}i$hCy1Z&>I$G*z$7#O!QUl;9NXQaG zdJSNH@W4ebhKTPvFD}}x!WQ3de(aw(yyBPFme!zuLRO?3;Ds@Y1pkv_t>hkQhyeP+dq z^K7_0KOt1c9FMy`SukX8>j{U^Oljrwzh5uSvhjl?=_-S&519J`JKwrWSOk$bTZ{K4 zmXaOZdi075ntqV5<;E~}D{$x`+8>sxrz~X|M@ork6!5JlJXf=DynmYf0a_2dW`Tp7 zrezL=kV^6bft?UzuGSiOj4uXJO_>(fnhKt7@T5-R|bbnaN%+^V9o21xp^WTV5LGb1zHzpbq z^BPl2IzUKy1zQIdLjuGab(p8FjKVM|-1!e3gujBUDgT<4X8Ha7hw(`NnO@=G_-~B} z{{wz&fOK?i-d6+w%IMEu+6D#9_ZWScmZQBH4ZqLJ|f0Ap=YX{C&0>o1@V*V`*Ia$3}yXi(6djT|60Sj`0L z08!p6C#SI`P;6qf%szdVvD85=yGvOmZ=pP8R&eT^n=aKh8wRDMqf7)#Y%euS^H4~i zo10NW>oe@>V^~0GB#_LfnhG1RkdFAVs+qQo%F$dcbJ9EUXUW_M+lFtriW1E-YgsCT zYw34vv#};ekWV_f?r~!=uzt!hGgV@G?BG1?F}X!v62KqKgFOX{)WLFPP6_HV(Gp7C zU(c|{;hv2I_tJUeTNOz+&YsU7?&YzvIaxVbxD8DsZQ1+wh2-{*o{(=TraH+gxrU?7 z)Z?%e%L^)LXw8L7QeFm*CQ02`!l!BZ!>8EyFk40#T`C$`s;;o7tO2~|{d+@?lXo#{ zLVV~5VJa_(B&rk>|~KswsabhEw{eQN6yF1UO ziTC@gX|CuN#E2=pGfz0K>Fm@2q-8BgE8wSJ{$vQnZXBy#pf4mFsX{ML*sXBaID_Vj zy7Cd>gR@l#|0-yyP4AuJ5o(8ZRusE4m>E+Bp(ACaYR#*ET2lCx6u73r@zmO8$B(kH zJy%wT@~kt*p93INJ_h`Bbc@hBhtB;dB{JU$ScO3@}TcW%i9-M9Z&$u{3%h5t()qxUx$DZCCgyyxeZ4XIn+lKv zAqgR&?*-7v77PI4bu!0XdvK0wFcC3wlwfQ1~B9e_ss*4-ing)*VJOp>bH&~38rnWFL`fBF9Ay=h|2(%Mt;G7~y z0J1*5LAHHnVBU40(XeRhrpVG*@3-?8dvE%e@sSH1oNh7`ei5d-IYp?X#%}+6=F2y1PD5Dq+{HP%I#;eT>pZK`I4))K$ zPp}8>%r~Kb%GC8cb6zf$x=l#1nMm5w21sY1k=f%1f6q-tA=t}t%S2Sbhc8RrFU&?^ z7$GIFGSt6Ckxy(hQTIq__4e>)&%$Of&yr|@U{TQrXAV2D=m~bI&F~gUX+M&hB;K&a zbeU-Iut^A2$e}sqM!QhF5Q*JBR2uJbQkbRDJ}hm+1hP#&aq;H|vjr6&Iw7s)=+}s? z6{gYN!VJLfn?=m-{Zw9H%^gyF_(MvardHtll>)atrMoa=@O*Lp(A9_cp%%7V^A6H; zbN!GO)c3_Zk4*7EyIUXY#I!fs@+sITj5ALN7C-p*J_ES(z=+}$!5@2~$Ap^#?w=ZdO26JSG4YPZw^ViGjiK7M+qX+5d;~kUbe5l#t z#wVU^V+Gsz{JBdmD;?50r$L7-ch0-QV0=_8u^$gesoueGJu`WW#{@4qjf|~7#}HhV zRb)L!3bp%+=#-b@mQh_dUF4s&L!CBf&oZ`HggB(v_IA{V)LazCfCN}y_$`n_O7@C7 zeNk)Nv$%sm<`+P7RNSb1h{N{5FE>C%A4Fvr6>#yQSHDHqZzgmV6sPaKFn(P0WfD*z zzLdC6q^lERolCV*Jrjhzu5Id6nsQ|EkJNo)a`swh?Y1-VWC_7`q7VU$LfKjoE(4pQ zcLo*l0OhGon<*=N$U8ar4wxS!4<;TS9xqnTC-H$#(nf1+#l4~>bp3CRN-OU{Qet}=NZx8bn^TGjnevN?C`jDP>}>|r-g^RaOP{tRM9uc`zPl)AfN@Hg&kiqR&j8_pC*Q##W^dqibJi61Z4DkCMRovZj{q-p@A@= z6(7(pZ!52C1`oh8)NbybU)|hOcbfsrneuqX2#9Pl9nv&)Ez>a%FnxD7B*2uUWxRb}mzql~}n6l=*(m^*2;@GwB@lyG~MOflaTLXoqio2jwD>2Wf%6<2w!4J%lB zD$lg|BTm)XCFC$@+=A3#;&6BV3db|QtcYQO@P|i8TC%-w$%vSeYFw{?M@&!wNqMp( zUBHm&C;AMYcilbSy*AZUJY&y6EponGAPaBUZ5ap(_@mfke*ZwzUC$-$6*uW4SIors z)($;@V{z(XY2+8HRqDfnV4m8Pr^nSUqJ}4QBj7Y?g%XBBA~q7XaN^!Vv3IO3k85+> z?={3@cM|FjB5Q3xIdjFyV$pVcbfnH}&iqrBPIIm*W7dqB zu}cEi4hVsX#E=Z1SqK2(@G`9T3jBvr|w6peR$< z4olYUb62AEmsMA4n_Q+pk`mOjxXx^?22fyC^^awby9(+B%f;#Nrc5Y}&#-NNnr<(; z$BU#{vGF@=egHQ3i4{aQ{CQXp?rHZZ;Q!%0=AC^unJsDZO}J8!31+ zFw;|?OLgYn=>8M-|AoCsx*O|$d~;gO->@I`zry~1SNH#8(%b(44o#GF?2s7{yk}}w z*c2BtDIg#ZU=9?6JU_zjtWJ;G*QCCh)dUi9W4sG3nm@*nCc z^y-eGeGrp{n0vhjVPu+NMS3v9D_k)@S_ecM%gnU~w|{1+9)z}MyGsrikUi@2qw3;6 zo@G8cYnBsTpa^kPWLk&?TCLC8qC+a~7$!ssX z*+&FuI&H*MfJmlGtI>9w-bK!3CZ`*cKk*=mFJ$hKXE?fWVIv#EpzN!V$wcJ^hQ)Rq z-%vO-11_`0?Z0$R;bBjzo#nM?V>5D{z3pv(50-Y>fFp^OdXGu}akl~4v3=XI*d8@U z6T*%Sx1=PMSKoawd{_g`vJ3JHET@9@@jdKU&GgPkM^qFjA8#M#h1rkYQ%g{2^Et^M zR9h~OK!5GBf6h(+LoTNU1H0}22QeMxXudM}&1J}ZQ&s=4U;p+b15;Y(e;LUpBhM4p zPY<*CMiuBb>R_mEKOkY>eiMJ(ADq8jMOXx9Igk8w<)9^eHgz@N%ZiIzrRKLN@`F9> z;t&|c0fTZx2F|Daw-@_Q7`2jZBL^P8iao>H*2&nJzFiu&E*o-it)R%fE6H>Q!+1!O z9pN}*Mv@R~Ek-@mrRFEdZxK*hiXqbGRCF+Go>ZgQ^2kE|FrK_v>y%oIPtn2YD-At1 zWc@;bZbJ$44q32hZMF_O^c8fve26+Bu|PnoH0?QAt_L{ZcRN_~Gm+Tw^ zdao2Zp-oaz#`Z`BW3_1H@pDKvWvRj;g}3v1JzR;N4HzURX!vz9JZ|2X?oZd5JnUvz z-I7djR0O-_((`+OhKMR0bj%B7231J-|=wjv2g9&WxN{GkMd7lX}SpcJ)p@?{}Do83%oRGBeu~ zbZV%GOw{y=K$DBlJeJ`Y+Z8SFoNWGKX1`Vnjj@9mNxy5O$7WDd?Sc$4|3`ysU*4+_ z{~h$l5tfEAn1w=;5s>pZtdYwLsPaj@zN`|GTGT_UVMt*YjX(REdpM1e!sc6Q1L^Q^0Zi{2;7vXIp zlaTecAFfl{t{rb3ML6Y$Ag{Y09xfhr(AKhJSjaA54bWi9k~cWOr=0dBZ~v#Vs|<^> z>B2||NT>9Yl9CcjNG#o@h#*QzEsc~22*RRt3QBh)(%sVCNC`-XBBChpE%?4KyNc+? zUVE{>?t9M6JTvp0Ip^Gx>bXdMA8w6GaT&S4A>K^AkGH?R= zya`f$ezU$7DT4Uhbm>TtD2q;kXw&VRDD4<#0ffVX7ktQH=L0l$-Ste&mO?UlNt4_r{Rxi@K8Dkv=p0PQHWRNjR?m)jWRhdr6?|;kwXAPvN|D zJTX!RWwrMs+{3{&NTnNVUsP|mP2{o6ehni)H75?6j}K@PxO0I+mTSJ92`%7rBC?_} zwnoJ&`XmPv?yU}JtwXLIOT@vnjokxV6$(ZX3kiV%tUVn18E0atYUyC8^W4p2{zQjRQj9!k`+Y>W1<)eNMt>E@0}E zNm(P(0T8eD&SM4N=nrjxE-*fpj3F~c^1Dz~#wZ0%@pfzT+mHY(i@=uE@Y-w);S*!J+##VtR+n2gU;+^82S((5;iZX1Sg-NK{MG1~Tec z3E|82W;%5(%r5H7yb!0o2(A3uH~z`w`BYW&mlcY&`|BJ+u7I)07b_;y?ztRp-)eHG z9=8ghD6NwWtyJ6IhZM-}I)YM}e_T{rZhuI8pN!iu&3S-47X1~5^rk32R850#FtS;4#@j?Z0jnHjG==7>8r z%OT_(zlqxr#z@Et4nxu#@=Kusr1d7iS3d5 zZ2I2Zw|aMwHU3jTX83lMG|!Sy<>$MXvY91JRZW0gZL1`{vI} z`cF!{Ckye9^3(sv${xa3u!jKMI!mBmYWU+GETEZ{k+Czoo&67u-`|}4B7?;hnmGx5 zwzjka7{y15@=$4J&BV8Jsi$MFSeNM!re5I~ICRqp6=BnR_-3w~pGV1?zsGJ{@cx)- zN3(cwISJ@uf*`n~khrGE}{e;*#KalUae@O}k zQIjm!C0BoGe0!WVd~(~nZ*gm07dTJXM+cT^v05*$0Aw68`-fi;sSVEZ-^|L3jQwQb z5~Y?z=ZZc;+SAt{@o5%#6e{U}8u=x2p8)kT8^?iEOmdAn_nJ{_{4)9Phw7)=Pq00E z*TEQU1Fef{{LgLtao?VM8-F|vDoxsUE1(;_7gQ#g>i45&Xl-TkhfuuZU74Z`?>H1U z#4XyJokC2VX)$oUmF3 z(Q^pG^(Qc2rxbycvhlpeO#O7In08Q%QA#eAtCR)*oy;a8b73-N9mztamf?2JHdL zf(VmINR^L7*guWs_nB zXTxS)K5m@p1VW%VC9r_3Y~4bA)~<-&)ChPpJ(mdE%tznwJG&>3opl9$T*HDfYf<9s z^=r+u5N0`!7g659*IYE3?764_&>WfD)%LdOu5Ax!bB46MjNgf+%rvq1EU=VU1~Vm1 z7(990_q3;d?jFcYN0ON;u4_|4kfp;wFs+^fkW*(9yP2{0Md2__)%woX7I2vCwParA z#o-aLao}iWC11Qcw?^h(TMz@l`A9-gLPVpbT9{dO9|KvhY;x|QvUx{_xg)3rnT96~ zso9m#iK(B-B!?wX?^=qyr`Qltq-e%-LlPqQTY3k`1ok}djPT!{!* zyCI^n)hd(u^yzp!;bbnktwQ9Nz6=69%W}lpTg-1Fbi3hPv}iCbKtzT$P)A`yV3x_g@ug4w**Caq}w3 z(wa}@yVdYfTxOB|=m=OS@Xco%#xZHBm_oe(NggB%A)*~sn!!_@U#E_*?#zzYCoZ|7 z?Kwt3{f0b4_j1LY>~o*^mldFdpiheb>baO z%x#}r*h)rr?-3jKJ_W?|nW`HNQ*#PUt^1%ymfc~Qtd(++BxhciNs_q~^1VpYd;j%o zGwx+t{q?fk1)+FpoG}8-1P~IK$-Exz70Xpwb+t=0I!pxd=)^=^vq@`)u@+sL0w3N! zi9w?rNP5yn&!eX?jALeU6t^9|Rc6sc@^Ug++j{t-sy2ac@}u<3RWMjR)~nELM!wBO(O(rJuj0%E2p~xj+UgS%AnaU*X%87@}5W=0HkU z{}QedU|DQOA*eAPnKy>1Pxe_$O=+X6lhx3CAbkyq2U{SmS7ak5NFLRtDd*cb3_Z7fGYyEo(ofkk8^ab7(PT2a`Fg zL=>r)^CfOCnp6s5rJZjTm5*YjCwJVnaEkDTX6Qm(X^^5Wl6sJmi02e+2;N!0*7E%Z z)a{H!6Ea-y7uP3_)cH-Ux13a5RXlhUacFgUKGbl?xHYkuGjUn45yfozUbJt3!c&W~ z2MHOW&V_`W8g_)V#teqSZ*Moa8wr_(H73!o=O^2|e&ZgEd*=YjeZ%VUJG$iL<}K#h z0n=CyccG)$=eyy9lZ+X-BNtd!5w6dU0xi9vp33Xmp6$*t5u`;ZuNqsQlccN1g?uL8 zt!UE1l~n+Fw4iuxW7b?BksPSsDt2ZWg?L{G%E84(?x8Fn3oPqjN1vE{UI_9D7N}-J z^O6}KB&w-*m&kdC<@|cJiou>MAXNenui-E~r6Zz>+66b#>+mY*MWr3y9BtJ%F?1U5 z5r|Y(3b%xf9kX~hZB~2pdvit0k0C;+YLLk-UaTnn0IqPcm!a$=c*7hu&l-(~)akgg ztmjMZEeiTqB~xwe?c>#xAuckV*Fy8W%BAk{Qs#^YPDE#6r{X{3&S7N*VCR%Cs)PNVS*|f0@y6PRIOeUF$8fG)Xr6go7f-%{w~FUP zL`@c}!Za9A6$tn&AoVE{Ab~7a;wqoyro1QKk~+!5+4qd5>0u>*JA!9cBetfuFCD|m zfV@*%-4Vi!aOL(zcedV8*2&w&F3GoqhYxli^#h&pn+a;YuzFvbRhyVnQgLwGE8Mg0 zaLmH)(7|`+>;@qNoUBQks9jj;!;FjCW9$l>vDCyK)uatD0E>$f?~r8QHbht>OicLd*%ICso&#AJxz+i90+;I^ySKyG*5{e5(^zi zfZmg#94;I-69GtXQx0y=(^vz+7rKWY{$%vH-vZS)QZVQvHxQL?>Rfbg+zt|}9z_WYS-ddNNG0v~8`d;9>qxuR*c?l;8q^W>diy%g_aN9LM zQ1WY6e0{H_%1URyCTV533zmYy7O947Etm(KbhCOifUfYn0}sot)o5@u4{t&;-Vuyc z!_V(UfZrd8@7g;+6$lLfkN2ZQJG1+yP{|)E+_ODjU})tKb_4x$-;2_;et5nppq{S_ zNE;zzB!wy~!nt64dT(-6f4^N&Xhak{u|PKr6r2(&gv` z(eEx?qNKt1_`Lhkrs+}1u!>7fCR_w=->8NPrut7kAZNTh%2Q_=J>9!BZE9Om9Ybl% znJ+%ijKY6Ot*P8AK)h2v%`{7wJxX$E^ONJ3)wogMo5dSDIaAxH5tLnn zsOwFL_VM~Hqy-Zl&!d#Y9&bJe_T0kyoyCj_A(|SVw(@MSx%Wks$6zibcJ7Dq0wkA5 z5H;wCtX=wbb1@ts6BroJKFvbF^A7hlK01IVH`?^g66W}?N-?I1S`kN6Mg)Qr3_Yky zxnu6yB&xStM>O~BHy}?!9P1sFcpm10QI%8>(=DDEm#n6?`shrF>yB@L-dp-7)Et4F zLy1z2j4Vco76J|shaMf;d$lG`0O_>S33{(?h{~@TE+?918K6KtQjKXqf=@L~de@U( z7rrc9M__&G*_qDPx$NP~FpOg?7x~ry&LG6tZIcfytM6RV~M0j!1EDg8s!g4kkjdT z46SNv%og5vs#v#kWnOheQv}@0zRCn_E6l7bEo@4%WUi8RlR%eYotdJnF5%=bQe@V~ zQt<=oxmGow*IIRtYQ;(EbVX=ecPP=m7876&BomfA*ytOXLKDwvKKS%fTBdFM;7BGf zbRX^2?eBh7t?gT^af6kcZ)0_Opu(U7GobI32 z>!w3ovvIkSMBZ&@Dh*|DLJISG6#m-{gNpL+SG{f`Jhm75Kq6o+5vLtiAg#S*Ci&$% z(RJ#FbB}`26Z|9{jRYzw(f2`Bc(nLsyo!>|oh~0_vV=0s%4oaZJ{(Z#4ra%*!p7xL zaqG%+$&k4n7mg+_H2Z1CK1IbRe#+yZpBG6J2UtZqS89ypYA#cV#xwCK?v|U$T9Wi6 zo{4_x+I=mYN6_vp>K!PW2=qG`CSEr{(F*oQk}7*aYxqRV`}5asGazPs7QLC6_f}$v z-Mp{z6+MK=F!@mpdL1=BYF3S#zCQg}={5T4R6Jb$=4gYfp3OcN_^)tL4Y<57mS{pf zkhV;p6Pr209;qqW|l^OTnW#< zP54Qc1_tjq_4f~;7Z9nCe!zz5IBETNLjnDP0gXug@rR3WAb$Moq;xFU+Q{)36a@k4 z8rEIoUu4>j0e@7#Jl+akWR=)6Krb&hq3Owhe*mG*Eren(DR{tBY*?8LD9N!4<_{#3 zH9_>!&~1Si<2VQUN2`C3G(o9PkvVJ(?mAdoTe2IP+&?kkx7ep04Kl!&z68ZS{u2Dx zBgY14qrqXDLH#@IHdcQCKJA&?erReJ(4p64P_GdQT%!vy;eb!uf8ss*bC+n?gSFxh zU4WF(K3Ic(FTLVYv^e-SE%gfJ)r zy@Y}Tx4jGpt|$kRQr3_<>Fd_c+_T5=w6Bln*uQ_c|KK7Z9M8TQZaCcI_WziIza7ET zj)vC?gq4?$DG>yv`@`%$9wvBY7Faqbv9svlrrn87 z%b(3U=7ASWf#p#YKa0oS;qIBzDSyWXFVz6cCJfy-V3x0wmEflm$|u4Nf5!taIRMM^ zR{B>ScmaaHV}LKm4$D9*|0~0ZW&agt|2qcw0@JVzMM`Hez|8=-a?^ju2VcP%majqi zSH3gqIscjF_&I_v5D81ueiM%7G~59S{D&vz7#O~k4=k{=J{<7R%gkS3!&hU0#l|y& z!-lKP@^|fqFZXbYz2Svv3Wxn`@rS=7fKO>YB`Egt2g4DZK7YR?IG@LKy0`qh$shke zJyacU^cB`{O#dcso@~N@u{pvg#=@F#(dJCxv(jXtHv9z@e7Y$t6}j!1RA(fg{v8v1 z?jJ1E7yC1re$52@Gtu#r0iTTqOVsNKM|65VI>}8tZ_rQAVeolmu;}&=;LuOA%Ki=* zK1dQ4c*`3O7&c_`Jm|A0D*TD`hA$lSKXGz@?&ssZ6h2B0w!sN!p#BpxcOLYQ1|JV0 z?2`rG;7UKZ2A{ko{>|C;7!^L^47S0@kY7>%Zb3e~&Huiu{5bXp2L6ivUr@z)BX~ON z;6o~4n?C(}aF_s#^H|O{ffnw~MF1+9ce-A3{5II&Jf2f);6n{yt%>+O&wqgj=W(2E z%fGMeKZYYM_V>2@=gU2hhi8zS`gPAkKih{to*eKg{n4P%a)nw(QW44;})-9ViR}!v6r{$ODQ1 diff --git a/qgis-app/plugins/tests/testfiles/web_not_exist.zip b/qgis-app/plugins/tests/testfiles/web_not_exist.zip deleted file mode 100644 index 42bd1bdb0f2060a0dc3501934c0cc6fc32d09e23..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 39906 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 Zb^g&<0si111)>LhB?C+zToDgX{{zTbis}FW diff --git a/qgis-app/plugins/tests/tests.py b/qgis-app/plugins/tests/tests.py deleted file mode 100644 index f51d798f..00000000 --- a/qgis-app/plugins/tests/tests.py +++ /dev/null @@ -1,26 +0,0 @@ -""" -This file demonstrates two different styles of tests (one doctest and one -unittest). These will both pass when you run "manage.py test". - -Replace these with more appropriate tests for your application. -""" - -from django.test import TestCase - - -class SimpleTest(TestCase): - def test_basic_addition(self): - """ - Tests that 1 + 1 always equals 2. - """ - self.assertEqual(1 + 1, 2) - - -__test__ = { - "doctest": """ -Another way to test that 1 + 1 is equal to 2. - ->>> 1 + 1 == 2 -True -""" -} diff --git a/qgis-app/plugins/tests/upload_test.py b/qgis-app/plugins/tests/upload_test.py deleted file mode 100644 index 0c05cc97..00000000 --- a/qgis-app/plugins/tests/upload_test.py +++ /dev/null @@ -1,6 +0,0 @@ -from xmlrpc import client - -server = client.ServerProxy("http://admin:admin@localhost:80/plugins/RPC2/") -handle = open("HelloWorld.zip", "rb") -blob = client.Binary(handle.read()) -server.plugin.upload(blob) diff --git a/qgis-app/plugins/tests/versionfield.py b/qgis-app/plugins/tests/versionfield.py deleted file mode 100755 index 8eff890b..00000000 --- a/qgis-app/plugins/tests/versionfield.py +++ /dev/null @@ -1,90 +0,0 @@ -#!/usr/bin/env python -# -*- coding:utf-8 -*- - -""" - Tests for version comparison field - - DESCRIPTION - - @copyright: 2014 by Alessandro Pasotti - ItOpen (http://www.itopen.it) - @license: GNU GPL, see COPYING for details. - - - -""" - -import re - -VERSION_RE = r"(^|(?<=\.))0+(?!\.)|\.#+" - -TEST_CASES = ( - "1.0.0", - "1.0.1", - "0.0.0", - "1.0", - "1.10", - "1.2", - "1.9", - "1.0.a", - "a.0.a", - "b.a.c", - "a.b", - "0.a.0.1", - "1.0.rc1", - "1.1a", - "1.1b", - "1.9.0", -) - - -def vjust(str, level=4, delim=".", bitsize=4, fillchar=" ", force_zero=False): - """ - Normalize a dotted version string. - - 1.12 becomes : 1. 12 - 1.1 becomes : 1. 1 - - - if force_zero=True and level=2: - - 1.12 becomes : 1. 12. 0 - 1.1 becomes : 1. 1. 0 - - - """ - if not str: - return str - nb = str.count(delim) - if nb < level: - if force_zero: - str += (level - nb) * (delim + "0") - else: - str += (level - nb) * delim - parts = [] - for v in str.split(delim)[: level + 1]: - if not v: - parts.append(v.rjust(bitsize, "#")) - else: - parts.append(v.rjust(bitsize, fillchar)) - return delim.join(parts) - - -def test(): - transformed = [] - for v in TEST_CASES: - vj = vjust(v, level=5, fillchar="0") - transformed.append(vj) - ck = re.sub(VERSION_RE, "", vj) - print("Testing\t %s (%s)\t\t %s" % (v, ck, vj)) - if v != ck: - print("!!! failed !!!") - - # Test sorting - transformed.sort() - print("Sorted:") - for v in transformed: - print(v) - - -if __name__ == "__main__": - test() diff --git a/qgis-app/plugins/tests/ws_test.py b/qgis-app/plugins/tests/ws_test.py deleted file mode 100644 index 2e952717..00000000 --- a/qgis-app/plugins/tests/ws_test.py +++ /dev/null @@ -1,37 +0,0 @@ -""" - -Test services - - - ->>> from django.test.client import Client ->>> c = Client() ->>> from xmlrpclib import * ->>> from django.core.management import call_command - - -Utility function: - -def xmldecode(r):p, u = getparser();p.feed(r.content);p.close();return u._stack[0] - ->>> def xmldecode(r): -... p, u = getparser() -... p.feed(r.content) -... p.close() -... return u._stack[0] - - - - ->>> c.login(username='admin', password='admin') -True - ->>> xmldecode(c.post('/plugins/RPC2', dumps(tuple(), "plugins.auth_test") , content_type = 'text/xml')) -1 - ->>> c.logout() - - - - -""" diff --git a/qgis-app/plugins/urls.py b/qgis-app/plugins/urls.py deleted file mode 100644 index e7b2d636..00000000 --- a/qgis-app/plugins/urls.py +++ /dev/null @@ -1,372 +0,0 @@ -# -*- coding: utf-8 -*- -from django.urls import re_path as url -from django.contrib.auth.decorators import login_required, user_passes_test -from django.utils.translation import gettext_lazy as _ -from plugins.models import Plugin, PluginVersion -from plugins.views import * -from rpc4django.views import serve_rpc_request - -# Plugins filtered views (need user parameter from request) -urlpatterns = [ - # XML - url(r"^plugins_new.xml$", xml_plugins_new, {}, name="xml_plugins_new"), - url(r"^plugins.xml$", xml_plugins, {}, name="xml_plugins"), - url( - r"^plugins_(?P\d+\.\d+).xml$", - xml_plugins, - {}, - name="xml_plugins_version_filtered_cached", - ), - url( - r"^version_filtered/(?P\d+\.\d+).xml$", - xml_plugins, - {}, - name="xml_plugins_version_filtered_uncached", - ), - url(r"^tags/(?P[^\/]+)/$", TagsPluginsList.as_view(), name="tags_plugins"), - url(r"^add/$", plugin_upload, {}, name="plugin_upload"), - url(r"^user/(?P\w+)/block/$", user_block, {}, name="user_block"), - url(r"^user/(?P\w+)/unblock/$", user_unblock, {}, name="user_unblock"), - url(r"^user/(?P\w+)/trust/$", user_trust, {}, name="user_trust"), - url(r"^user/(?P\w+)/untrust/$", user_untrust, {}, name="user_untrust"), - url( - r"^(?P[A-Za-z][A-Za-z0-9-_]+)/manage/$", - plugin_manage, - {}, - name="plugin_manage", - ), - url( - r"^(?P[A-Za-z][A-Za-z0-9-_]+)/delete/$", - plugin_delete, - {}, - name="plugin_delete", - ), - url( - r"^(?P[A-Za-z][A-Za-z0-9-_]+)/update/$", - plugin_update, - {}, - name="plugin_update", - ), - url( - r"^(?P[A-Za-z][A-Za-z0-9-_]+)/tokens/$", - PluginTokenListView.as_view(), - name="plugin_token_list", - ), - url( - r"^(?P[A-Za-z][A-Za-z0-9-_]+)/tokens/(?P\d+)/$", - PluginTokenDetailView.as_view(), - name="plugin_token_detail", - ), - url( - r"^(?P[A-Za-z][A-Za-z0-9-_]+)/tokens/create/$", - plugin_token_create, - {}, - name="plugin_token_create", - ), - url( - r"^(?P[A-Za-z][A-Za-z0-9-_]+)/tokens/(?P\d+)/update$", - plugin_token_update, - {}, - name="plugin_token_update", - ), - url( - r"^(?P[A-Za-z][A-Za-z0-9-_]+)/tokens/(?P[^\/]+)/delete/$", - plugin_token_delete, - {}, - name="plugin_token_delete", - ), - url( - r"^(?P[A-Za-z][A-Za-z0-9-_]+)/set_featured/$", - plugin_set_featured, - {}, - name="plugin_set_featured", - ), - url( - r"^(?P[A-Za-z][A-Za-z0-9-_]+)/unset_featured/$", - plugin_unset_featured, - {}, - name="plugin_unset_featured", - ), - url( - r"^user/(?P\w+)/admin$", - UserDetailsPluginsList.as_view(), - name="user_details", - ), - url(r"^$", PluginsList.as_view(), name="approved_plugins"), - url( - r"^my$", - login_required( - MyPluginsList.as_view(additional_context={"title": _("My Plugins")}) - ), - name="my_plugins", - ), - url( - r"^featured/$", - PluginsList.as_view( - queryset=Plugin.featured_objects.all(), - additional_context={"title": _("Featured plugins")}, - ), - name="featured_plugins", - ), - url(r"^user/(?P\w+)/$", UserPluginsList.as_view(), name="user_plugins"), - url( - r"^server/$", - PluginsList.as_view( - queryset=Plugin.server_objects.all(), - additional_context={"title": _("QGIS Server plugins")}, - ), - name="server_plugins", - ), - url( - r"^unapproved/$", - PluginsList.as_view( - queryset=Plugin.unapproved_objects.all().order_by("-latest_version_date"), - additional_context={"title": _("Unapproved plugins")}, - ), - name="unapproved_plugins", - ), - url( - r"^deprecated/$", - PluginsList.as_view( - queryset=Plugin.deprecated_objects.all(), - additional_context={"title": _("Deprecated plugins")}, - ), - name="deprecated_plugins", - ), - url( - r"^fresh/$", - PluginsList.as_view( - queryset=Plugin.fresh_objects.all(), - additional_context={"title": _("New plugins")}, - ), - name="fresh_plugins", - ), - url( - r"^latest/$", - PluginsList.as_view( - queryset=Plugin.latest_objects.all(), - additional_context={"title": _("Updated plugins")}, - ), - name="latest_plugins", - ), - url( - r"^stable/$", - PluginsList.as_view( - queryset=Plugin.stable_objects.all(), - additional_context={"title": _("Stable plugins")}, - ), - name="stable_plugins", - ), - url( - r"^experimental/$", - PluginsList.as_view( - queryset=Plugin.experimental_objects.all(), - additional_context={"title": _("Experimental plugins")}, - ), - name="experimental_plugins", - ), - url( - r"^popular/$", - PluginsList.as_view( - queryset=Plugin.popular_objects.all(), - additional_context={"title": _("Popular plugins")}, - ), - name="popular_plugins", - ), - url( - r"^most_voted/$", - PluginsList.as_view( - queryset=Plugin.most_voted_objects.all(), - additional_context={"title": _("Most voted plugins")}, - ), - name="most_voted_plugins", - ), - url( - r"^most_downloaded/$", - PluginsList.as_view( - queryset=Plugin.most_downloaded_objects.all(), - additional_context={"title": _("Most downloaded plugins")}, - ), - name="most_downloaded_plugins", - ), - url( - r"^most_voted/$", - PluginsList.as_view( - queryset=Plugin.most_voted_objects.all(), - additional_context={"title": _("Most voted plugins")}, - ), - name="most_voted_plugins", - ), - url( - r"^most_rated/$", - PluginsList.as_view( - queryset=Plugin.most_rated_objects.all(), - additional_context={"title": _("Most rated plugins")}, - ), - name="most_rated_plugins", - ), - url( - r"^feedback_completed/$", - FeedbackCompletedPluginsList.as_view( - additional_context={"title": _("Reviewed Plugins (Resolved)")} - ), - name="feedback_completed_plugins", - ), - url( - r"^feedback_pending/$", - FeedbackPendingPluginsList.as_view( - additional_context={"title": _("Awaiting review")} - ), - name="feedback_pending_plugins", - ), - url( - r"^feedback_received/$", - FeedbackReceivedPluginsList.as_view( - additional_context={"title": _("Reviewed Plugins (Pending)")} - ), - name="feedback_received_plugins", - ), - url( - r"^author/(?P[^/]+)/$", - AuthorPluginsList.as_view(), - name="author_plugins", - ), -] - - -# User management -urlpatterns += [ - url( - r"^user/(?P\w+)/manage/$", - user_permissions_manage, - {}, - name="user_permissions_manage", - ), -] - - -# Version Management -urlpatterns += [ - url( - r"^(?P[A-Za-z][A-Za-z0-9-_]+)/version/(?P[^\/]+)/manage/$", - version_manage, - {}, - name="version_manage", - ), - url( - r"^(?P[A-Za-z][A-Za-z0-9-_]+)/version/add/$", - version_create, - {}, - name="version_create", - ), - url( - r"^api/(?P[A-Za-z][A-Za-z0-9-_]+)/version/add/$", - version_create_api, - {}, - name="version_create_api", - ), - url( - r"^(?P[A-Za-z][A-Za-z0-9-_]+)/version/(?P[^\/]+)/$", - version_detail, - {}, - name="version_detail", - ), - url( - r"^(?P[A-Za-z][A-Za-z0-9-_]+)/version/(?P[^\/]+)/delete/$", - version_delete, - {}, - name="version_delete", - ), - url( - r"^(?P[A-Za-z][A-Za-z0-9-_]+)/version/(?P[^\/]+)/update/$", - version_update, - {}, - name="version_update", - ), - url( - r"^api/(?P[A-Za-z][A-Za-z0-9-_]+)/version/(?P[^\/]+)/update/$", - version_update_api, - {}, - name="version_update_api", - ), - url( - r"^(?P[A-Za-z][A-Za-z0-9-_]+)/version/(?P[^\/]+)/download/$", - version_download, - {}, - name="version_download", - ), - url( - r"^(?P[A-Za-z][A-Za-z0-9-_]+)/version/(?P[^\/]+)/approve/$", - version_approve, - {}, - name="version_approve", - ), - url( - r"^(?P[A-Za-z][A-Za-z0-9-_]+)/version/(?P[^\/]+)/unapprove/$", - version_unapprove, - {}, - name="version_unapprove", - ), - url( - r"^(?P[A-Za-z][A-Za-z0-9-_]+)/version/(?P[^\/]+)/feedback/$", - version_feedback, - {}, - name="version_feedback", - ), - url( - r"^(?P[A-Za-z][A-Za-z0-9-_]+)/version/(?P[^\/]+)/feedback/update/$", - version_feedback_update, - {}, - name="version_feedback_update", - ), - url( - r"^(?P[A-Za-z][A-Za-z0-9-_]+)/version/(?P[^\/]+)/feedback/(?P[0-9]+)/delete/$", - version_feedback_delete, - {}, - name="version_feedback_delete", - ), - url( - r"^(?P[A-Za-z][A-Za-z0-9-_]+)/version/(?P[^\/]+)/feedback/(?P[0-9]+)/edit/$", - version_feedback_edit, - {}, - name="version_feedback_edit", - ), -] - -# RPC -urlpatterns += [ - # rpc4django will need to be in your Python path - url(r"^RPC2/$", serve_rpc_request), -] - - -from django.views.decorators.csrf import csrf_protect, ensure_csrf_cookie -from django.views.decorators.http import require_POST - -# plugin rating -from djangoratings.views import AddRatingFromModel - -urlpatterns += [ - url( - r"rate/(?P\d+)/(?P\d+)/", - require_POST(csrf_protect(AddRatingFromModel())), - { - "app_label": "plugins", - "model": "plugin", - "field_name": "rating", - }, - name="plugin_rate", - ), -] - - -# Plugin detail (keep last) -urlpatterns += [ - url( - r"^(?P[A-Za-z][A-Za-z0-9-_]+)/$", - PluginDetailView.as_view( - slug_url_kwarg="package_name", slug_field="package_name" - ), - name="plugin_detail", - ), -] diff --git a/qgis-app/plugins/utils.py b/qgis-app/plugins/utils.py deleted file mode 100644 index 521a5797..00000000 --- a/qgis-app/plugins/utils.py +++ /dev/null @@ -1,58 +0,0 @@ -import requests -import re -from django.http import HttpRequest - - -def extract_version(tag): - """ - Extracts the major and minor version from a given tag. - - The tag should be in the format of x.y.z where x, y, and z are - numbers representing major, minor, and patch versions respectively. - - Args: - tag (str): The version tag to be processed. - - Returns: - str: The major and minor version as x.y, or None if no match. - """ - match = re.search(r'(\d+\.\d+\.\d+)', tag) - if match: - version = match.group(1) - version_parts = version.split('.') - return '.'.join(version_parts[:-1]) - else: - return None - - -def get_qgis_versions(): - """ - Fetches all releases from the QGIS GitHub repository and extracts their - major and minor versions. - - Returns: - list: A list of unique major and minor versions of the releases. - - Raises: - Exception: If the request to the GitHub API fails. - """ - url = 'https://api.github.com/repos/qgis/QGIS/releases' - response = requests.get(url) - if response.status_code != 200: - raise Exception('Request failed') - releases = response.json() - all_versions = [] - for release in releases: - tag_name = release['tag_name'].replace('_', '.') - version = extract_version(tag_name) - if version not in all_versions: - all_versions.append(version) - return all_versions - - -def parse_remote_addr(request: HttpRequest) -> str: - """Extract client IP from request.""" - x_forwarded_for = request.headers.get("X-Forwarded-For", "") - if x_forwarded_for: - return x_forwarded_for.split(",")[0] - return request.META.get("REMOTE_ADDR", "") \ No newline at end of file diff --git a/qgis-app/plugins/validator.py b/qgis-app/plugins/validator.py deleted file mode 100644 index a6a9f2b2..00000000 --- a/qgis-app/plugins/validator.py +++ /dev/null @@ -1,387 +0,0 @@ -""" -Plugin validator class - -""" -import codecs -import configparser -import mimetypes -import os -import re -import zipfile -from io import StringIO -from urllib.parse import urlparse - -import requests -from django.conf import settings -from django.core.files.uploadedfile import SimpleUploadedFile -from django.forms import ValidationError -from django.utils.translation import gettext_lazy as _ - -PLUGIN_MAX_UPLOAD_SIZE = getattr(settings, "PLUGIN_MAX_UPLOAD_SIZE", 25000000) # 25 mb -PLUGIN_REQUIRED_METADATA = getattr( - settings, - "PLUGIN_REQUIRED_METADATA", - ( - "name", - "description", - "version", - "qgisMinimumVersion", - "author", - "email", - "about", - "tracker", - "repository", - ), -) - -PLUGIN_OPTIONAL_METADATA = getattr( - settings, - "PLUGIN_OPTIONAL_METADATA", - ( - "homepage", - "changelog", - "qgisMaximumVersion", - "tags", - "deprecated", - "experimental", - "external_deps", - "server", - ), -) -PLUGIN_BOOLEAN_METADATA = getattr( - settings, "PLUGIN_BOOLEAN_METADATA", ("experimental", "deprecated", "server") -) - - -def _read_from_init(initcontent, initname): - """ - Read metadata from __init__.py, raise ValidationError - """ - metadata = [] - i = 0 - lines = initcontent.split("\n") - while i < len(lines): - if re.search(r"def\s+([^\(]+)", lines[i]): - k = re.search(r"def\s+([^\(]+)", lines[i]).groups()[0] - i += 1 - while i < len(lines) and lines[i] != "": - if re.search(r"return\s+[\"']?([^\"']+)[\"']?", lines[i]): - metadata.append( - ( - k, - re.search( - r"return\s+[\"']?([^\"']+)[\"']?", lines[i] - ).groups()[0], - ) - ) - break - i += 1 - i += 1 - if not len(metadata): - raise ValidationError(_("Cannot find valid metadata in %s") % initname) - return metadata - - -def _check_required_metadata(metadata): - """ - Checks if required metadata are in place, raise ValidationError if not found - """ - - missing_fields = [field for field in PLUGIN_REQUIRED_METADATA if field not in [item[0] for item in metadata]] - if len(missing_fields) > 0: - missing_fields_str = ', '.join(missing_fields) - raise ValidationError( - _( - f'Cannot find metadata {missing_fields_str} in metadata source {dict(metadata).get("metadata_source")}.
For further informations about metadata, please see:
metadata documentation' - ) - ) - -def _check_url_link(urls): - """ - Checks if all the url link is valid. - """ - def error_check(url: str, forbidden_url: str)->bool: - # Check against forbidden_url - if url == forbidden_url: - return True - - # Check if parsed URL is valid - try: - parsed_url = urlparse(url) - return not all([parsed_url.scheme, parsed_url.netloc]) - except Exception as e: - # Log the exception or handle it as per your requirement - print(f"Error occurred: {e}") - return True - - def error_check_if_exist(url: str)->bool: - # Check if url is exist - try: - # https://stackoverflow.com/a/41950438/10268058 - # add the headers parameter to make the request appears like coming - # from browser, otherwise some websites will return 403 - headers = { - "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) " - "AppleWebKit/537.36 (KHTML, like Gecko) " - "Chrome/56.0.2924.76 Safari/537.36" - } - req = requests.head(url, headers=headers) - except requests.exceptions.SSLError: - req = requests.head(url, verify=False) - except Exception: - return True - return req.status_code >= 400 - - url_error = [item for item in [url_item['metadata_attr'] for url_item in urls if error_check(url_item['url'], url_item['forbidden_url'])]] - if len(url_error) > 0: - url_error_str = ", ".join(url_error) - raise ValidationError( - _(f"Please provide valid url link for the following key(s) in the metadata source: {url_error_str}. ") - ) - exist_url_error = [item for item in [url_item['metadata_attr'] for url_item in urls if error_check_if_exist(url_item['url'])]] - if len(exist_url_error) > 0: - exist_url_error_str = ", ".join(exist_url_error) - raise ValidationError( - _( - f"Please provide valid url link for the following key(s) in the metadata source: {exist_url_error_str}. " - "The website(s) cannot be reached." - ) - ) - - -def validator(package): - """ - Analyzes a zipped file, returns metadata if success, False otherwise. - If the new icon metadata is found, an inmemory file object is also returned - - Current checks: - - * size <= PLUGIN_MAX_UPLOAD_SIZE - * zip contains __init__.py in first level dir - * Check for LICENSE file - * mandatory metadata: ('name', 'description', 'version', 'qgisMinimumVersion', 'author', 'email') - * package_name regexp: [A-Za-z][A-Za-z0-9-_]+ - * author regexp: [^/]+ - - """ - try: - if package.size > PLUGIN_MAX_UPLOAD_SIZE: - raise ValidationError( - _("File is too big. Max size is %s Megabytes") - % (PLUGIN_MAX_UPLOAD_SIZE / 1000000) - ) - except AttributeError: - if package.len > PLUGIN_MAX_UPLOAD_SIZE: - raise ValidationError( - _("File is too big. Max size is %s Megabytes") - % (PLUGIN_MAX_UPLOAD_SIZE / 1000000) - ) - - try: - zip = zipfile.ZipFile(package) - except: - raise ValidationError(_("Could not unzip file.")) - for zname in zip.namelist(): - if zname.find("..") != -1 or zname.find(os.path.sep) == 0: - raise ValidationError( - _("For security reasons, zip file cannot contain path " - "information (found '{}')".format(zname)) - ) - if zname.find(".pyc") != -1: - raise ValidationError( - _("For security reasons, zip file cannot contain .pyc file") - ) - for forbidden_dir in ["__MACOSX", ".git", "__pycache__"]: - dir_name_list = zname.split("/") - if forbidden_dir in dir_name_list: - if forbidden_dir == dir_name_list[0]: - raise ValidationError( - _( - "For security reasons, zip file " - "cannot contain '%s' directory. However, there is one present at the root of the archive." % (forbidden_dir,) - ) - ) - raise ValidationError( - _( - "For security reasons, zip file " - "cannot contain '%s' directory. However, it has been found at '%s' ." % (forbidden_dir, zname) - ) - ) - bad_file = zip.testzip() - if bad_file: - zip.close() - del zip - try: - raise ValidationError( - _("Bad zip (maybe a CRC error) on file %s") % bad_file - ) - except UnicodeDecodeError: - raise ValidationError( - _("Bad zip (maybe unicode filename) on file %s") % bad_file, - errors="replace", - ) - - # Metadata list, also usefull to pass warnings to the main view - metadata = [] - - namelist = zip.namelist() - # Check if the zip file contains multiple parent folders - # If it is, show a warning for now - try: - parent_folders = list(set([str(name).split('/')[0] for name in namelist])) - if len(parent_folders) > 1: - metadata.append(("multiple_parent_folders", ', '.join(parent_folders))) - except: - pass - - # Checks that package_name exists - try: - package_name = namelist[0][: namelist[0].index("/")] - except: - raise ValidationError( - _( - "Cannot find a folder inside the compressed package: this does not seems a valid plugin" - ) - ) - - # Cuts the trailing slash - if package_name.endswith("/"): - package_name = package_name[:-1] - initname = package_name + "/__init__.py" - metadataname = package_name + "/metadata.txt" - if initname not in namelist and metadataname not in namelist: - raise ValidationError( - _( - "Cannot find __init__.py or metadata.txt in the compressed package: this does not seems a valid plugin (I searched for %s and %s)" - ) - % (initname, metadataname) - ) - - # Checks for __init__.py presence - if initname not in namelist: - raise ValidationError(_("Cannot find __init__.py in plugin package.")) - - # First parse metadata.txt - if metadataname in namelist: - try: - parser = configparser.ConfigParser() - parser.optionxform = str - parser.read_file(StringIO(codecs.decode(zip.read(metadataname), "utf8"))) - if not parser.has_section("general"): - raise ValidationError( - _("Cannot find a section named 'general' in %s") % metadataname - ) - metadata.extend(parser.items("general")) - except Exception as e: - raise ValidationError(_("Errors parsing %s. %s") % (metadataname, e)) - metadata.append(("metadata_source", "metadata.txt")) - else: - # Then parse __init__ - # Ugly RE: regexp guru wanted! - initcontent = zip.read(initname).decode("utf8") - metadata.extend(_read_from_init(initcontent, initname)) - if not metadata: - raise ValidationError(_("Cannot find valid metadata in %s") % initname) - metadata.append(("metadata_source", "__init__.py")) - - _check_required_metadata(metadata) - - # Process Icon - try: - # Strip leading dir for ccrook plugins - if dict(metadata)["icon"].startswith("./"): - icon_path = dict(metadata)["icon"][2:] - else: - icon_path = dict(metadata)["icon"] - icon = zip.read(package_name + "/" + icon_path) - icon_file = SimpleUploadedFile( - dict(metadata)["icon"], icon, mimetypes.guess_type(dict(metadata)["icon"]) - ) - except: - icon_file = None - - metadata.append(("icon_file", icon_file)) - - # Transforms booleans flags (experimental) - for flag in PLUGIN_BOOLEAN_METADATA: - if flag in dict(metadata): - metadata[metadata.index((flag, dict(metadata)[flag]))] = ( - flag, - dict(metadata)[flag].lower() == "true" - or dict(metadata)[flag].lower() == "1", - ) - - # Adds package_name - if not re.match(r"^[A-Za-z][A-Za-z0-9-_]+$", package_name): - raise ValidationError( - _( - "The name of the top level directory inside the zip package must start with an ASCII letter and can only contain ASCII letters, digits and the signs '-' and '_'." - ) - ) - metadata.append(("package_name", package_name)) - - # Last temporary rule, check if mandatory metadata are also in __init__.py - # fails if it is not - min_qgs_version = dict(metadata).get("qgisMinimumVersion") - dict(metadata).get("qgisMaximumVersion") - if ( - tuple(min_qgs_version.split(".")) < tuple("1.8".split(".")) - and metadataname in namelist - ): - initcontent = zip.read(initname).decode("utf8") - try: - initmetadata = _read_from_init(initcontent, initname) - initmetadata.append(("metadata_source", "__init__.py")) - _check_required_metadata(initmetadata) - except ValidationError as e: - raise ValidationError( - _( - "qgisMinimumVersion is set to less than 1.8 (%s) and there were errors reading metadata from the __init__.py file. This can lead to errors in versions of QGIS less than 1.8, please either set the qgisMinimumVersion to 1.8 or specify the metadata also in the __init__.py file. Reported error was: %s" - ) - % (min_qgs_version, ",".join(e.messages)) - ) - # check url_link - urls_to_check = [ - {'url': dict(metadata).get("tracker"), 'forbidden_url': "http://bugs", 'metadata_attr': "tracker"}, - {'url': dict(metadata).get("repository"), 'forbidden_url': "http://repo", 'metadata_attr': "repository"}, - {'url': dict(metadata).get("homepage"), 'forbidden_url': "http://homepage", 'metadata_attr': "homepage"}, - ] - - _check_url_link(urls_to_check) - - - # Checks for LICENSE file presence - # Making it mandatory as of 03 June 2024 - # according to https://github.com/qgis/QGIS-Enhancement-Proposals/issues/279 - licensename = package_name + "/LICENSE" - if licensename not in namelist: - raise ValidationError(_( - "Cannot find LICENSE in the plugin package. " - "This file is required, please consider adding it to the plugin package.") - ) - - zip.close() - del zip - - # Check author - if "author" in dict(metadata): - if not re.match(r"^[^/]+$", dict(metadata)["author"]): - raise ValidationError(_("Author name cannot contain slashes.")) - - # strip and check - checked_metadata = [] - for k, v in metadata: - try: - if not (k in PLUGIN_BOOLEAN_METADATA or k == "icon_file"): - # v.decode('UTF-8') - checked_metadata.append((k, v.strip())) - else: - checked_metadata.append((k, v)) - except UnicodeDecodeError as e: - raise ValidationError( - _( - "There was an error converting metadata '%s' to UTF-8 . Reported error was: %s" - ) - % (k, e) - ) - return checked_metadata diff --git a/qgis-app/plugins/views.py b/qgis-app/plugins/views.py deleted file mode 100644 index 57ef6a8e..00000000 --- a/qgis-app/plugins/views.py +++ /dev/null @@ -1,1864 +0,0 @@ -# Create your views here. -import copy -import logging -import os -import datetime - -from django.conf import settings -from django.contrib import messages -from django.contrib.auth.decorators import login_required, user_passes_test -from django.contrib.auth.models import Permission, User -from django.contrib.contenttypes.models import ContentType -from django.contrib.sites.models import Site -from django.core.exceptions import FieldDoesNotExist -from django.core.mail import send_mail -from django.db import IntegrityError, connection -from django.db.models import Max, Q -from django.db.models.expressions import RawSQL -from django.db.models.functions import Lower -from django.http import Http404, HttpResponse, HttpResponseRedirect, JsonResponse -from django.shortcuts import get_object_or_404, render -from django.urls import reverse -from django.utils.timezone import now -from django.utils.decorators import method_decorator -from django.utils.encoding import DjangoUnicodeDecodeError -from django.utils.translation import gettext_lazy as _ -from django.views.decorators.cache import never_cache -from django.views.decorators.csrf import ensure_csrf_cookie, csrf_exempt, csrf_protect -from django.views.decorators.http import require_POST -from django.views.generic.detail import DetailView -from django.db import transaction - -# from sortable_listview import SortableListView -from django.views.generic.list import ListView -from plugins.decorators import has_valid_token -from plugins.forms import * -from plugins.models import Plugin, PluginOutstandingToken, PluginVersion, PluginVersionDownload, vjust -from plugins.validator import PLUGIN_REQUIRED_METADATA -from django.contrib.gis.geoip2 import GeoIP2 -from plugins.utils import parse_remote_addr - -from rest_framework_simplejwt.token_blacklist.models import OutstandingToken -from rest_framework_simplejwt.tokens import RefreshToken, api_settings -from rest_framework_simplejwt.exceptions import InvalidToken, TokenError -import time -try: - from urllib import unquote, urlencode - - from urlparse import parse_qs, urlparse -except ImportError: - from urllib.parse import parse_qs, unquote, urlencode, urlparse - -# Decorator -staff_required = user_passes_test(lambda u: u.is_staff) -from plugins.tasks.generate_plugins_xml import generate_plugins_xml - - -def send_mail_wrapper(subject, message, mail_from, recipients, fail_silently=True): - if settings.DEBUG: - logging.debug("Mail not sent (DEBUG=True)") - else: - send_mail(subject, message, mail_from, recipients, fail_silently) - - -def plugin_notify(plugin): - """ - Sends a message to staff on new plugins - """ - recipients = [ - u.email - for u in User.objects.filter(is_staff=True, email__isnull=False).exclude( - email="" - ) - ] - - if recipients: - domain = Site.objects.get_current().domain - mail_from = settings.DEFAULT_FROM_EMAIL - - send_mail_wrapper( - _("A new plugin has been created by %s.") % plugin.created_by, - _( - "\r\nPlugin name is: %s\r\nPlugin description is: %s\r\nLink: http://%s%s\r\n" - ) - % (plugin.name, plugin.description, domain, plugin.get_absolute_url()), - mail_from, - recipients, - fail_silently=True, - ) - logging.debug( - "Sending email notification for %s plugin, recipients: %s" - % (plugin, recipients) - ) - else: - logging.warning("No recipients found for %s plugin notification" % plugin) - - -def version_notify(plugin_version): - """ - Sends a message to staff on new plugin versions - """ - plugin = plugin_version.plugin - - recipients = [ - u.email - for u in User.objects.filter(is_staff=True, email__isnull=False).exclude( - email="" - ) - ] - - if recipients: - domain = Site.objects.get_current().domain - mail_from = settings.DEFAULT_FROM_EMAIL - - send_mail_wrapper( - _("A new plugin version has been uploaded by %s.") % plugin.created_by, - _( - "\r\nPlugin name is: %s\r\nPlugin description is: %s\r\nLink: http://%s%s\r\n" - ) - % ( - plugin.name, - plugin.description, - domain, - plugin_version.get_absolute_url(), - ), - mail_from, - recipients, - fail_silently=True, - ) - logging.debug( - "Sending email notification for %s plugin version, recipients: %s" - % (plugin_version, recipients) - ) - else: - logging.warning( - "No recipients found for %s plugin version notification" % plugin_version - ) - - -def plugin_approve_notify(plugin, msg, user): - """ - Sends a message when a plugin is approved or unapproved. - """ - if settings.DEBUG: - return - recipients = [u.email for u in plugin.editors if u.email] - if settings.QGIS_DEV_MAILING_LIST_ADDRESS: - recipients.append(settings.QGIS_DEV_MAILING_LIST_ADDRESS) - if plugin.approved: - approval_state = "approval" - else: - approval_state = "unapproval" - - if len(recipients): - domain = Site.objects.get_current().domain - mail_from = settings.DEFAULT_FROM_EMAIL - logging.debug( - "Sending email %s notification for %s plugin, recipients: %s" - % (approval_state, plugin, recipients) - ) - send_mail_wrapper( - _("Plugin %s %s notification.") % (plugin, approval_state), - _("\r\nPlugin %s %s by %s.\r\n%s\r\nLink: http://%s%s\r\n") - % ( - plugin.name, - approval_state, - user, - msg, - domain, - plugin.get_absolute_url(), - ), - mail_from, - recipients, - fail_silently=True, - ) - else: - logging.warning( - "No recipients found for %s plugin %s notification" - % (plugin, approval_state) - ) - - -def version_feedback_notify(version, user): - """ - Sends a message when a version is receiving feedback. - """ - if settings.DEBUG: - return - plugin = version.plugin - recipients = [u.email for u in plugin.editors if u.email] - if recipients: - domain = Site.objects.get_current().domain - mail_from = settings.DEFAULT_FROM_EMAIL - logging.debug( - "Sending email feedback notification for %s plugin version %s, recipients: %s" - % (plugin, version.version, recipients) - ) - send_mail_wrapper( - _("Plugin %s feedback notification.") % (plugin, ), - _("\r\nPlugin %s reviewed by %s and received a feedback.\r\nLink: http://%s%sfeedback/\r\n") - % ( - plugin.name, - user, - domain, - version.get_absolute_url(), - ), - mail_from, - recipients, - fail_silently=True, - ) - else: - logging.warning( - "No recipients found for %s plugin feedback notification" - % (plugin, ) - ) - - -def user_trust_notify(user): - """ - Sends a message when an author is trusted or untrusted. - """ - if settings.DEBUG: - return - if user.is_staff: - logging.debug("Skipping trust notification for staff user %s" % user) - else: - if user.email: - recipients = [user.email] - mail_from = settings.DEFAULT_FROM_EMAIL - - if user.has_perm("plugins.can_approve"): - subject = _("User trust notification.") - message = _( - "\r\nYou can now approve your own plugins and the plugins you can edit.\r\n" - ) - else: - subject = _("User untrust notification.") - message = _("\r\nYou cannot approve any plugin.\r\n") - - logging.debug("Sending email trust change notification to %s" % recipients) - send_mail_wrapper( - subject, message, mail_from, recipients, fail_silently=True - ) - else: - logging.warning( - "No email found for %s user trust change notification" % user - ) - - -## Access control ## - - -def check_plugin_access(user, plugin): - """ - Returns true if the user can modify the plugin: - - * is_staff - * is owner - - """ - return user.is_staff or user in plugin.editors - -def check_plugin_token_access(user, plugin): - """ - Returns true if the user can access all the plugin's token: - - * is_staff - * is maintainer - - """ - return user.is_staff or user.pk == plugin.created_by.pk - - -def check_plugin_version_approval_rights(user, plugin): - """ - Returns true if the user can approve the plugin version: - - * is_staff - * is owner and is trusted - - """ - return user.is_staff or ( - user in plugin.editors and user.has_perm("plugins.can_approve") - ) - - -@login_required -def plugin_create(request): - """ - The form will automatically set published flag according to user permissions. - There is a more "automatic" alternative for creating new Plugins in a single step - through package upload - """ - if request.method == "POST": - form = PluginForm(request.POST, request.FILES) - form.fields["owners"].queryset = User.objects.exclude( - pk=request.user.pk - ).order_by("username") - if form.is_valid(): - plugin = form.save(commit=False) - plugin.created_by = request.user - plugin.save() - plugin_notify(plugin) - msg = _("The Plugin has been successfully created.") - messages.success(request, msg, fail_silently=True) - return HttpResponseRedirect(plugin.get_absolute_url()) - else: - form = PluginForm() - form.fields["owners"].queryset = User.objects.exclude( - pk=request.user.pk - ).order_by("username") - - return render( - request, - "plugins/plugin_form.html", - {"form": form, "form_title": _("New plugin")}, - ) - - -@staff_required -@require_POST -def plugin_set_featured(request, package_name): - """ - Set as featured - """ - plugin = get_object_or_404(Plugin, package_name=package_name) - plugin.featured = True - plugin.save() - msg = _("The plugin %s is now a marked as featured." % plugin) - messages.success(request, msg, fail_silently=True) - return HttpResponseRedirect(plugin.get_absolute_url()) - - -@staff_required -@require_POST -def plugin_unset_featured(request, package_name): - """ - Sets as not featured - """ - plugin = get_object_or_404(Plugin, package_name=package_name) - plugin.featured = False - plugin.save() - msg = _("The plugin %s is not marked as featured anymore." % plugin) - messages.success(request, msg, fail_silently=True) - return HttpResponseRedirect(plugin.get_absolute_url()) - - -@login_required -def plugin_upload(request): - """ - This is the "single step" way to create new plugins: - uploads a package and creates a new Plugin with a new PluginVersion - can also update an existing plugin - """ - if request.method == "POST": - form = PackageUploadForm(request.POST, request.FILES) - if form.is_valid(): - try: - plugin_data = { - "name": form.cleaned_data["name"], - "package_name": form.cleaned_data["package_name"], - "description": form.cleaned_data["description"], - "created_by": request.user, - "author": form.cleaned_data["author"], - "email": form.cleaned_data["email"], - "created_by": request.user, - "icon": form.cleaned_data["icon_file"], - } - - # Gets existing plugin - try: - plugin = Plugin.objects.get( - package_name=plugin_data["package_name"] - ) - if not check_plugin_access(request.user, plugin): - return render( - request, "plugins/plugin_permission_deny.html", {} - ) - # Apply new values - plugin.name = plugin_data["name"] - plugin.description = plugin_data["description"] - plugin.author = plugin_data["author"] - plugin.email = plugin_data["email"] - is_new = False - except Plugin.DoesNotExist: - plugin = Plugin(**plugin_data) - is_new = True - - # Check icon, don't change if not valid - if plugin_data["icon"]: - plugin.icon = plugin_data["icon"] - - # Server is optional - plugin.server = form.cleaned_data.get("server", False) - - # Other optional fields - warnings = [] - - if form.cleaned_data.get("homepage"): - plugin.homepage = form.cleaned_data.get("homepage") - elif not plugin.homepage: - warnings.append( - _( - "homepage field is empty, this field is not required but is recommended, please consider adding it to metadata." - ) - ) - if form.cleaned_data.get("tracker"): - plugin.tracker = form.cleaned_data.get("tracker") - elif not plugin.tracker: - raise ValidationError( - _( - '"tracker" metadata is required! Please add it to metadata.txt.' - ) - ) - if form.cleaned_data.get("repository"): - plugin.repository = form.cleaned_data.get("repository") - elif not plugin.repository: - raise ValidationError( - _( - '"repository" metadata is required! Please add it to metadata.txt.' - ) - ) - if form.cleaned_data.get("about"): - plugin.about = form.cleaned_data.get("about") - elif not plugin.about: - raise ValidationError( - _( - '"about" metadata is required! Please add it to metadata.txt.' - ) - ) - - # Save main Plugin object - plugin.save() - - if is_new: - plugin_notify(plugin) - - # Takes care of tags - if form.cleaned_data.get("tags"): - plugin.tags.set( - [ - t.strip().lower() - for t in form.cleaned_data.get("tags").split(",") - ] - ) - - version_data = { - "plugin": plugin, - "min_qg_version": form.cleaned_data.get("qgisMinimumVersion"), - "max_qg_version": form.cleaned_data.get("qgisMaximumVersion"), - "version": form.cleaned_data.get("version"), - "created_by": request.user, - "package": form.cleaned_data.get("package"), - "approved": request.user.has_perm("plugins.can_approve") - or plugin.approved, - "experimental": form.cleaned_data.get("experimental"), - "changelog": form.cleaned_data.get("changelog", ""), - "external_deps": form.cleaned_data.get("external_deps", ""), - } - - new_version = PluginVersion(**version_data) - new_version.save() - msg = _("The Plugin has been successfully created.") - messages.success(request, msg, fail_silently=True) - - # Update plugins cached xml - generate_plugins_xml.delay() - - if not new_version.approved: - msg = _( - "Your plugin is awaiting approval from a staff member and will be approved as soon as possible." - ) - warnings.append(msg) - if not is_new: - version_notify(new_version) - if not form.cleaned_data.get("metadata_source") == "metadata.txt": - msg = _( - "Your plugin does not contain a metadata.txt file, metadata have been read from the __init__.py file. This is deprecated and its support will eventually cease." - ) - warnings.append(msg) - - # Grouped messages: - if warnings: - messages.warning( - request, - _("

Warnings:

") - + "\n".join([("

%s

" % w) for w in warnings]), - fail_silently=True, - ) - - if form.cleaned_data.get("multiple_parent_folders"): - parent_folders = form.cleaned_data.get("multiple_parent_folders") - messages.warning( - request, - _( - f"Your plugin includes multiple parent folders: {parent_folders}. Please be aware that only the first folder has been recognized. It is strongly advised to have a single parent folder." - ), - fail_silently=True, - ) - del form.cleaned_data["multiple_parent_folders"] - - except (IntegrityError, ValidationError, DjangoUnicodeDecodeError) as e: - connection.close() - messages.error(request, e, fail_silently=True) - if not plugin.pk: - return render(request, "plugins/plugin_upload.html", {"form": form}) - return HttpResponseRedirect(plugin.get_absolute_url()) - else: - form = PackageUploadForm() - - return render(request, "plugins/plugin_upload.html", {"form": form}) - - -class PluginDetailView(DetailView): - model = Plugin - queryset = Plugin.objects.all() - - @method_decorator(ensure_csrf_cookie) - def dispatch(self, *args, **kwargs): - return super(PluginDetailView, self).dispatch(*args, **kwargs) - - def get_context_data(self, **kwargs): - plugin = kwargs.get("object") - context = super(PluginDetailView, self).get_context_data(**kwargs) - # Warnings for owners - if check_plugin_access(self.request.user, plugin): - if not plugin.homepage: - msg = _( - 'homepage metadata is missing, this is not required but recommended. Please consider adding "homepage" to metadata.txt.' - ) - messages.warning(self.request, msg, fail_silently=True) - for md in set(PLUGIN_REQUIRED_METADATA) - set( - ("version", "qgisMinimumVersion") - ): - if not getattr(plugin, md, None): - msg = _( - "%s metadata is missing, this metadata entry is required. Please add %s to metadata.txt." - ) % (md, md) - messages.error(self.request, msg, fail_silently=True) - stats_url = f"{settings.METABASE_DOWNLOAD_STATS_URL}?package_name={plugin.package_name}#hide_parameters=package_name" - context.update( - { - "stats_url": stats_url, - "rating": plugin.rating.get_rating(), - "votes": plugin.rating.votes, - } - ) - return context - - -@login_required -def plugin_delete(request, package_name): - plugin = get_object_or_404(Plugin, package_name=package_name) - if not check_plugin_access(request.user, plugin): - return render(request, "plugins/plugin_permission_deny.html", {}) - if "delete_confirm" in request.POST: - plugin.delete() - msg = _("The Plugin has been successfully deleted.") - messages.success(request, msg, fail_silently=True) - return HttpResponseRedirect(reverse("approved_plugins")) - return render(request, "plugins/plugin_delete_confirm.html", {"plugin": plugin}) - - -def _check_optional_metadata(form, request): - """ - Checks for the presence of optional metadata - """ - if not form.cleaned_data.get("homepage"): - messages.warning( - request, - _( - "Homepage field is empty, this field is not required but is recommended, please consider adding it to metadata.txt." - ), - fail_silently=True, - ) - - -@login_required -def plugin_update(request, package_name): - """ - Plugin update form - """ - plugin = get_object_or_404(Plugin, package_name=package_name) - if not check_plugin_access(request.user, plugin): - return render(request, "plugins/plugin_permission_deny.html", {}) - if request.method == "POST": - form = PluginForm(request.POST, request.FILES, instance=plugin) - form.fields["owners"].queryset = User.objects.exclude( - pk=plugin.created_by.pk - ).order_by("username") - if form.is_valid(): - new_object = form.save(commit=False) - new_object.modified_by = request.user - new_object.save() - # Without this next line the tags won't be saved. - form.save_m2m() - new_object.owners.clear() - for o in form.cleaned_data["owners"]: - new_object.owners.add(o) - msg = _("The Plugin has been successfully updated.") - messages.success(request, msg, fail_silently=True) - - # Checks for optional metadata - _check_optional_metadata(form, request) - - return HttpResponseRedirect(new_object.get_absolute_url()) - else: - form = PluginForm(instance=plugin) - form.fields["owners"].queryset = User.objects.exclude( - pk=plugin.created_by.pk - ).order_by("username") - - return render( - request, - "plugins/plugin_form.html", - {"form": form, "form_title": _("Edit plugin"), "plugin": plugin}, - ) - - - -class PluginTokenListView(ListView): - """ - Plugin token list - """ - model = PluginOutstandingToken - queryset = PluginOutstandingToken.objects.all().order_by("-token__created_at") - template_name = "plugins/plugin_token_list.html" - - @method_decorator(ensure_csrf_cookie) - def dispatch(self, *args, **kwargs): - return super(PluginTokenListView, self).dispatch(*args, **kwargs) - - def get_filtered_queryset(self, qs): - package_name = self.kwargs.get('package_name') - plugin = get_object_or_404(Plugin, package_name=package_name) - if not check_plugin_token_access(self.request.user, plugin): - return qs.filter( - plugin__pk=plugin.pk, - is_blacklisted=False, - token__user=self.request.user - ) - return qs.filter( - plugin__pk=plugin.pk, - is_blacklisted=False, - ) - - def get_queryset(self): - qs = super(PluginTokenListView, self).get_queryset() - qs = self.get_filtered_queryset(qs) - return qs - - def get_context_data(self, **kwargs): - package_name = self.kwargs.get('package_name') - plugin = get_object_or_404(Plugin, package_name=package_name) - if not check_plugin_access(self.request.user, plugin): - context = {} - self.template_name = "plugins/plugin_token_permission_deny.html" - return context - context = super(PluginTokenListView, self).get_context_data(**kwargs) - context.update( - { - "plugin": plugin - } - ) - return context - -class PluginTokenDetailView(DetailView): - """ - Plugin token detail - """ - model = OutstandingToken - queryset = OutstandingToken.objects.all() - template_name = "plugins/plugin_token_detail.html" - - @method_decorator(ensure_csrf_cookie) - def dispatch(self, *args, **kwargs): - return super(PluginTokenDetailView, self).dispatch(*args, **kwargs) - - def get_context_data(self, **kwargs): - context = super(PluginTokenDetailView, self).get_context_data(**kwargs) - package_name = self.kwargs.get('package_name') - token_id = self.kwargs.get('pk') - plugin = get_object_or_404(Plugin, package_name=package_name) - if not check_plugin_access(self.request.user, plugin): - context = {} - self.template_name = "plugins/plugin_token_permission_deny.html" - return context - - outstanding_token = get_object_or_404(OutstandingToken, pk=token_id, user=self.request.user) - plugin_token = get_object_or_404( - PluginOutstandingToken, - token__pk=outstanding_token.pk, - is_blacklisted=False, - is_newly_created=True - ) - try: - token = RefreshToken(outstanding_token.token) - token['plugin_id'] = plugin.pk - token['refresh_jti'] = token[api_settings.JTI_CLAIM] - del token['user_id'] - except (InvalidToken, TokenError) as e: - context = {} - self.template_name = "plugins/plugin_token_invalid_or_expired.html" - return context - timestamp_from_last_edit = int(time.time()) - context.update( - { - "access_token": str(token.access_token), - "plugin": plugin, - "object": outstanding_token, - 'timestamp_from_last_edit': timestamp_from_last_edit - } - ) - plugin_token.is_newly_created = False - plugin_token.save() - return context - -@login_required -@transaction.atomic -def plugin_token_create(request, package_name): - if request.method == "POST": - plugin = get_object_or_404(Plugin, package_name=package_name) - user = request.user - if not check_plugin_access(user, plugin): - return render(request, "plugins/plugin_permission_deny.html", {}) - - refresh = RefreshToken.for_user(user) - refresh["plugin_id"] = plugin.pk - - jti = refresh[api_settings.JTI_CLAIM] - - outstanding_token = OutstandingToken.objects.get(jti=jti) - - plugin_token = PluginOutstandingToken.objects.create( - plugin=plugin, - token=outstanding_token, - is_blacklisted=False, - is_newly_created=True - ) - - return HttpResponseRedirect( - reverse("plugin_token_detail", args=(plugin.package_name, plugin_token.pk)) - ) - -@login_required -@transaction.atomic -def plugin_token_update(request, package_name, token_id): - plugin = get_object_or_404(Plugin, package_name=package_name) - outstanding_token = get_object_or_404(OutstandingToken, pk=token_id) - if not check_plugin_token_access(request.user, plugin): - outstanding_token = get_object_or_404(OutstandingToken, pk=token_id, user=request.user) - plugin_token = get_object_or_404( - PluginOutstandingToken, - token__pk=outstanding_token.pk, - is_blacklisted=False - ) - if not check_plugin_access(request.user, plugin): - return render(request, "plugins/version_permission_deny.html", {}) - if request.method == "POST": - form = PluginTokenForm(request.POST, instance=plugin_token) - if form.is_valid(): - form.save() - msg = _("The token description has been successfully updated.") - messages.success(request, msg, fail_silently=True) - return HttpResponseRedirect( - reverse("plugin_token_list", args=(plugin.package_name,)) - ) - else: - form = PluginTokenForm(instance=plugin_token) - - return render( - request, - "plugins/plugin_token_form.html", - {"form": form, "token": plugin_token} - ) - -@login_required -@transaction.atomic -def plugin_token_delete(request, package_name, token_id): - plugin = get_object_or_404(Plugin, package_name=package_name) - outstanding_token = get_object_or_404(OutstandingToken, pk=token_id) - if not check_plugin_token_access(request.user, plugin): - outstanding_token = get_object_or_404(OutstandingToken, pk=token_id, user=request.user) - plugin_token = get_object_or_404( - PluginOutstandingToken, - token__pk=outstanding_token.pk, - is_blacklisted=False - ) - - if not check_plugin_access(request.user, plugin): - return render(request, "plugins/version_permission_deny.html", {}) - if "delete_confirm" in request.POST: - try: - token = RefreshToken(outstanding_token.token) - token.blacklist() - plugin_token.is_blacklisted = True - except (InvalidToken, TokenError) as e: - plugin_token.is_blacklisted = True - plugin_token.save() - - msg = _("The token has been successfully deleted.") - messages.success(request, msg, fail_silently=True) - return HttpResponseRedirect( - reverse("plugin_token_list", args=(plugin.package_name,)) - ) - return render( - request, - "plugins/plugin_token_delete_confirm.html", - {"plugin": plugin, "username": outstanding_token.user}, - ) - - -class PluginsList(ListView): - model = Plugin - queryset = Plugin.approved_objects.all() - title = _("All plugins") - additional_context = {} - paginate_by = settings.PAGINATION_DEFAULT_PAGINATION - - def get_paginate_by(self, queryset): - """ - Paginate by specified value in querystring, or use default class property value. - """ - try: - paginate_by = int(self.request.GET.get("per_page", self.paginate_by)) - except ValueError: - paginate_by = self.paginate_by - return paginate_by - - def get_filtered_queryset(self, qs): - return qs - - def get_queryset(self): - qs = super(PluginsList, self).get_queryset() - qs = self.get_filtered_queryset(qs) - sort_by = self.request.GET.get("sort", None) - if sort_by: - if sort_by[0] == "-": - _sort_by = sort_by[1:] - else: - _sort_by = sort_by - - # Check if the sort criterion is a field or 'average_vote' - # or 'latest_version_date' - try: - ( - _sort_by == "average_vote" - or _sort_by == "latest_version_date" - or self.model._meta.get_field(_sort_by) - ) - except FieldDoesNotExist: - return qs - qs = qs.order_by(sort_by) - else: - # default - if not qs.ordered: - qs = qs.order_by(Lower("name")) - return qs - - def get_context_data(self, **kwargs): - context = super(PluginsList, self).get_context_data(**kwargs) - context.update( - { - "title": self.title, - } - ) - context.update(self.additional_context) - context["current_sort_query"] = self.get_sortstring() - context["current_querystring"] = self.get_querystring() - context["per_page_list"] = [20, 50, 75, 100] - - try: - # Get the next value of per page from per_page_list - next_per_page_id = context["per_page_list"].index(context["paginator"].per_page) + 1 - next_per_page = context["per_page_list"][next_per_page_id] - except (ValueError, IndexError): - # If the 'per_page' value in the request parameter - # is not found in the 'per_page_list' or if the - # next index is out of range, set the 'next_per_page' - # value to a number greater than the total count - # of records. This action effectively disables the button." - next_per_page = context["paginator"].count + 1 - context["show_more_items_number"] = next_per_page - return context - - def get_sortstring(self): - if self.request.GET.get("sort", None): - return "sort=%s" % self.request.GET.get("sort") - return "" - - def get_querystring(self): - """ - Clean existing query string (GET parameters) by removing - arguments that we don't want to preserve (sort parameter, 'page') - """ - to_remove = ["page", "sort"] - query_string = urlparse(self.request.get_full_path()).query - query_dict = parse_qs(query_string) - for arg in to_remove: - if arg in query_dict: - del query_dict[arg] - clean_query_string = urlencode(query_dict, doseq=True) - return clean_query_string - - -class MyPluginsList(PluginsList): - def get_filtered_queryset(self, qs): - return ( - Plugin.base_objects.filter(owners=self.request.user).distinct() - | Plugin.objects.filter(created_by=self.request.user).distinct() - ) - - -class UserPluginsList(PluginsList): - def get_filtered_queryset(self, qs): - user = get_object_or_404(User, username=self.kwargs["username"]) - return qs.filter(created_by=user) - - -class AuthorPluginsList(PluginsList): - def get_filtered_queryset(self, qs): - return qs.filter(author=unquote(self.kwargs["author"])) - - def get_context_data(self, **kwargs): - context = super(AuthorPluginsList, self).get_context_data(**kwargs) - context.update( - { - "title": _("Plugins by %s") % unquote(self.kwargs["author"]), - } - ) - return context - - -class UserDetailsPluginsList(PluginsList): - """ - List plugins created_by OR owned by user - """ - - template_name = "plugins/user.html" - - def get_filtered_queryset(self, qs): - user = get_object_or_404(User, username=self.kwargs["username"]) - return qs.filter(Q(created_by=user) | Q(owners=user)) - - def get_context_data(self, **kwargs): - user = get_object_or_404(User, username=self.kwargs["username"]) - user_is_trusted = user.has_perm("plugins.can_approve") - context = super(UserDetailsPluginsList, self).get_context_data(**kwargs) - context.update( - { - "title": _("Plugins from %s") % user, - "user_is_trusted": user_is_trusted, - "plugin_user": user, - } - ) - return context - - -class TagsPluginsList(PluginsList): - def get_filtered_queryset(self, qs): - response = qs.filter(tagged_items__tag__slug=unquote(self.kwargs["tags"])) - return response - - def get_context_data(self, **kwargs): - context = super(TagsPluginsList, self).get_context_data(**kwargs) - context.update( - { - "title": _("Plugins tagged with: %s") % unquote(self.kwargs["tags"]), - "page_title": _("Tag: %s") % unquote(self.kwargs["tags"]) - } - ) - return context - - -class FeedbackCompletedPluginsList(PluginsList): - """List of Plugins that has feedback resolved in its versions. - - The plugins editor can only see their plugin feedbacks. - The staff can see all plugin feedbacks. - """ - queryset = Plugin.feedback_completed_objects.all().order_by("-latest_version_date") - - def get_filtered_queryset(self, qs): - user = get_object_or_404(User, username=self.request.user) - if not user.is_staff: - raise Http404 - return qs - -class FeedbackReceivedPluginsList(PluginsList): - """List of Plugins that has feedback received in its versions. - - The plugins editor can only see their plugin feedbacks. - The staff can see all plugin feedbacks. - """ - queryset = Plugin.feedback_received_objects.all().order_by("-latest_version_date") - - def get_filtered_queryset(self, qs): - user = get_object_or_404(User, username=self.request.user) - if not user.is_staff: - raise Http404 - return qs - -class FeedbackPendingPluginsList(PluginsList): - """List of Plugins that has feedback pending in its versions. - - Only staff can see plugin feedback list. - """ - queryset = Plugin.feedback_pending_objects.all().order_by("-latest_version_date") - - def get_filtered_queryset(self, qs): - user = get_object_or_404(User, username=self.request.user) - if not user.is_staff: - raise Http404 - return qs - - -@login_required -@require_POST -def plugin_manage(request, package_name): - """ - Entry point for the plugin management functions - """ - if request.POST.get("set_featured"): - return plugin_set_featured(request, package_name) - if request.POST.get("unset_featured"): - return plugin_unset_featured(request, package_name) - if request.POST.get("delete"): - return plugin_delete(request, package_name) - - return HttpResponseRedirect(reverse("user_details", args=[username])) - - -############################################### - -# User management functions - -############################################### - - -@staff_required -@require_POST -def user_block(request, username): - """ - Completely blocks a user - """ - user = get_object_or_404(User, username=username, is_staff=False) - # Disable - user.is_active = False - user.save() - msg = _("The user %s is now blocked." % user) - messages.success(request, msg, fail_silently=True) - return HttpResponseRedirect(reverse("user_details", args=[user.username])) - - -@staff_required -@require_POST -def user_unblock(request, username): - """ - unblocks a user - """ - user = get_object_or_404(User, username=username, is_staff=False) - # Enable - user.is_active = True - user.save() - msg = _("The user %s is now unblocked." % user) - messages.success(request, msg, fail_silently=True) - return HttpResponseRedirect(reverse("user_details", args=[user.username])) - - -@staff_required -@require_POST -def user_trust(request, username): - """ - Assigns can_approve permission to the plugin creator - """ - user = get_object_or_404(User, username=username) - user.user_permissions.add( - Permission.objects.get( - codename="can_approve", - content_type=ContentType.objects.get(app_label="plugins", model="plugin"), - ) - ) - msg = _("The user %s is now a trusted user." % user) - messages.success(request, msg, fail_silently=True) - user_trust_notify(user) - return HttpResponseRedirect(reverse("user_details", args=[user.username])) - - -@staff_required -@require_POST -def user_untrust(request, username): - """ - Revokes can_approve permission to the plugin creator - """ - user = get_object_or_404(User, username=username) - user.user_permissions.remove( - Permission.objects.get( - codename="can_approve", - content_type=ContentType.objects.get(app_label="plugins", model="plugin"), - ) - ) - msg = _("The user %s is now an untrusted user." % user) - messages.success(request, msg, fail_silently=True) - user_trust_notify(user) - return HttpResponseRedirect(reverse("user_details", args=[user.username])) - - -@staff_required -@require_POST -def user_permissions_manage(request, username): - """ - Entry point for the user management functions - """ - if request.POST.get("user_block"): - return user_block(request, username) - if request.POST.get("user_unblock"): - return user_unblock(request, username) - if request.POST.get("user_trust"): - return user_trust(request, username) - if request.POST.get("user_untrust"): - return user_untrust(request, username) - - return HttpResponseRedirect(reverse("user_details", args=[username])) - - -############################################### - -# Version management functions - -############################################### - - -def _main_plugin_update(request, plugin, form): - """ - Updates the main plugin object from version metadata - """ - # Check if update name from metadata is allowed - metadata_fields = ["author", "email", "description", "about", "homepage", "tracker", "repository"] - if plugin.allow_update_name: - metadata_fields.insert(0, "name") - - # Update plugin from metadata - for f in metadata_fields: - if form.cleaned_data.get(f): - setattr(plugin, f, form.cleaned_data.get(f)) - - # Icon has a special treatment - if form.cleaned_data.get("icon_file"): - setattr(plugin, "icon", form.cleaned_data.get("icon_file")) - if form.cleaned_data.get("tags"): - plugin.tags.set( - [t.strip().lower() for t in form.cleaned_data.get("tags").split(",")] - ) - plugin.save() - -@has_valid_token -@csrf_exempt -def version_create_api(request, package_name): - """ - Create a new version using a valid token. - We make sure that the token is valid before - disabling CSRF protection. - """ - plugin = get_object_or_404(Plugin, package_name=package_name) - version = PluginVersion(plugin=plugin, is_from_token=True, token=request.plugin_token) - - return _version_create(request, plugin, version) - - -@login_required -def version_create(request, package_name): - plugin = get_object_or_404(Plugin, package_name=package_name) - if not check_plugin_access(request.user, plugin): - return render( - request, "plugins/version_permission_deny.html", {"plugin": plugin} - ) - version = PluginVersion(plugin=plugin, created_by=request.user) - is_trusted=request.user.has_perm("plugins.can_approve") - return _version_create(request, plugin, version, is_trusted=is_trusted) - -def _version_create(request, plugin, version, is_trusted=False): - """ - The form will create versions according to permissions, - plugin name and description are updated according to the info - contained in the package metadata - """ - if request.method == "POST": - - form = PluginVersionForm( - request.POST, - request.FILES, - instance=version, - is_trusted=is_trusted - ) - if form.is_valid(): - try: - new_object = form.save() - msg = _("The Plugin Version has been successfully created.") - messages.success(request, msg, fail_silently=True) - # The approved flag is also controlled in the form, but we - # are checking it here in any case for additional security - if not is_trusted: - new_object.approved = False - new_object.save() - messages.warning( - request, - _( - "You do not have approval permissions, plugin version has been set unapproved." - ), - fail_silently=True, - ) - version_notify(new_object) - if form.cleaned_data.get("icon_file"): - form.cleaned_data["icon"] = form.cleaned_data.get("icon_file") - - if form.cleaned_data.get("multiple_parent_folders"): - parent_folders = form.cleaned_data.get("multiple_parent_folders") - messages.warning( - request, - _( - f"Your plugin includes multiple parent folders: {parent_folders}. Please be aware that only the first folder has been recognized. It is strongly advised to have a single parent folder." - ), - fail_silently=True, - ) - del form.cleaned_data["multiple_parent_folders"] - - _main_plugin_update(request, new_object.plugin, form) - _check_optional_metadata(form, request) - return HttpResponseRedirect(new_object.plugin.get_absolute_url()) - except (IntegrityError, ValidationError, DjangoUnicodeDecodeError) as e: - messages.error(request, e, fail_silently=True) - connection.close() - return HttpResponseRedirect(plugin.get_absolute_url()) - else: - form = PluginVersionForm( - is_trusted=is_trusted - ) - - return render( - request, - "plugins/version_form.html", - {"form": form, "plugin": plugin, "form_title": _("New version for plugin")}, - ) - - -@has_valid_token -@csrf_exempt -def version_update_api(request, package_name, version): - """ - Update a version using a valid token. - We make sure that the token is valid before - disabling CSRF protection. - """ - plugin = get_object_or_404(Plugin, package_name=package_name) - version = PluginVersion(plugin=plugin, is_from_token=True, token=request.plugin_token) - return _version_update(request, plugin, version) - - -@login_required -def version_update(request, package_name, version): - plugin = get_object_or_404(Plugin, package_name=package_name) - version = get_object_or_404(PluginVersion, plugin=plugin, version=version) - if not check_plugin_access(request.user, plugin): - return render( - request, "plugins/version_permission_deny.html", {"plugin": plugin} - ) - version = PluginVersion(plugin=plugin, created_by=request.user) - is_trusted=request.user.has_perm("plugins.can_approve") - return _version_update(request, plugin, version, is_trusted=is_trusted) - -def _version_update(request, plugin, version, is_trusted=False): - """ - The form will update versions according to permissions - """ - - if request.method == "POST": - form = PluginVersionForm( - request.POST, - request.FILES, - instance=version, - is_trusted=is_trusted, - ) - if form.is_valid(): - try: - new_object = form.save() - # update metadata for the main plugin object - _main_plugin_update(request, new_object.plugin, form) - msg = _("The Plugin Version has been successfully updated.") - messages.success(request, msg, fail_silently=True) - - if form.cleaned_data.get("multiple_parent_folders"): - parent_folders = form.cleaned_data.get("multiple_parent_folders") - messages.warning( - request, - _( - f"Your plugin includes multiple parent folders: {parent_folders}. Please be aware that only the first folder has been recognized. It is strongly advised to have a single parent folder." - ), - fail_silently=True, - ) - del form.cleaned_data["multiple_parent_folders"] - - except (IntegrityError, ValidationError, DjangoUnicodeDecodeError) as e: - messages.error(request, e, fail_silently=True) - connection.close() - return HttpResponseRedirect(plugin.get_absolute_url()) - else: - form = PluginVersionForm( - instance=version, is_trusted=is_trusted - ) - - return render( - request, - "plugins/version_form.html", - { - "form": form, - "plugin": plugin, - "version": version, - "form_title": _("Edit version for plugin"), - }, - ) - - -@login_required -def version_delete(request, package_name, version): - plugin = get_object_or_404(Plugin, package_name=package_name) - version = get_object_or_404(PluginVersion, plugin=plugin, version=version) - if not check_plugin_access(request.user, plugin): - return render(request, "plugins/version_permission_deny.html", {}) - if "delete_confirm" in request.POST: - version.delete() - msg = _("The Plugin Version has been successfully deleted.") - messages.success(request, msg, fail_silently=True) - return HttpResponseRedirect( - reverse("plugin_detail", args=(plugin.package_name,)) - ) - return render( - request, - "plugins/version_delete_confirm.html", - {"plugin": plugin, "version": version}, - ) - - -@login_required -@require_POST -def version_approve(request, package_name, version): - """ - Approves the plugin version - """ - plugin = get_object_or_404(Plugin, package_name=package_name) - version = get_object_or_404(PluginVersion, plugin=plugin, version=version) - if not check_plugin_version_approval_rights(request.user, version.plugin): - msg = _("You do not have approval rights for this plugin.") - messages.error(request, msg, fail_silently=True) - return HttpResponseRedirect(version.get_absolute_url()) - version.approved = True - version.save() - msg = _( - "The plugin version '%s' is now approved. " - "Please note that there may be a delay of up to 15 minutes " - "between the approval of the plugin and its actual availability in the XML." - ) % version - messages.success(request, msg, fail_silently=True) - plugin_approve_notify(version.plugin, msg, request.user) - try: - redirect_to = request.META["HTTP_REFERER"] - except: - redirect_to = version.get_absolute_url() - return HttpResponseRedirect(redirect_to) - - -@login_required -@require_POST -def version_unapprove(request, package_name, version): - """ - unapproves the plugin version - """ - plugin = get_object_or_404(Plugin, package_name=package_name) - version = get_object_or_404(PluginVersion, plugin=plugin, version=version) - if not check_plugin_version_approval_rights(request.user, version.plugin): - msg = _("You do not have approval rights for this plugin.") - messages.error(request, msg, fail_silently=True) - return HttpResponseRedirect(version.get_absolute_url()) - version.approved = False - version.save() - msg = _('The plugin version "%s" is now unapproved' % version) - messages.success(request, msg, fail_silently=True) - plugin_approve_notify(version.plugin, msg, request.user) - try: - redirect_to = request.META["HTTP_REFERER"] - except: - redirect_to = version.get_absolute_url() - return HttpResponseRedirect(redirect_to) - - -@login_required -@require_POST -def version_manage(request, package_name, version): - """ - Entry point for the user management functions - """ - if "version_approve" in request.POST: - return version_approve(request, package_name, version) - if "version_unapprove" in request.POST: - return version_unapprove(request, package_name, version) - - return HttpResponseRedirect(reverse("plugin_detail", args=[package_name])) - - -@login_required -@never_cache -def version_feedback(request, package_name, version): - """ - The form will add a comment/ feedback for the package version. - """ - plugin = get_object_or_404(Plugin, package_name=package_name) - version = get_object_or_404(PluginVersion, plugin=plugin, version=version) - is_user_plugin_owner: bool = request.user in plugin.editors - is_user_has_approval_rights: bool = check_plugin_version_approval_rights( - request.user, plugin) - if not is_user_plugin_owner and not is_user_has_approval_rights: - return render( - request, - template_name="plugins/version_permission_deny.html", - context={}, - status=403 - ) - if request.method == "POST": - form = VersionFeedbackForm(request.POST) - if form.is_valid(): - tasks = form.cleaned_data['tasks'] - for task in tasks: - PluginVersionFeedback.objects.create( - version=version, - reviewer=request.user, - task=task - ) - version_feedback_notify(version, request.user) - form = VersionFeedbackForm() - feedbacks = PluginVersionFeedback.objects.filter(version=version) - return render( - request, - "plugins/plugin_feedback.html", - { - "feedbacks": feedbacks, - "form": form, - "version": version, - "is_user_has_approval_rights": is_user_has_approval_rights, - "is_user_plugin_owner": is_user_plugin_owner - } - ) - - -@login_required -@require_POST -def version_feedback_update(request, package_name, version): - plugin = get_object_or_404(Plugin, package_name=package_name) - version = get_object_or_404(PluginVersion, plugin=plugin, version=version) - has_update_permission: bool = ( - request.user in plugin.editors - or check_plugin_version_approval_rights(request.user, plugin) - ) - if not has_update_permission: - return JsonResponse({"success": False}, status=401) - completed_tasks = request.POST.getlist('completed_tasks') - for task_id in completed_tasks: - try: - task_id = int(task_id) - except ValueError: - continue - feedback = PluginVersionFeedback.objects.filter( - version=version, pk=task_id).first() - feedback.is_completed = True - feedback.save() - return JsonResponse({"success": True}, status=201) - - -@login_required -@require_POST -def version_feedback_edit(request, package_name, version, feedback): - feedback = get_object_or_404( - PluginVersionFeedback, - version__plugin__package_name=package_name, - version__version=version, - pk=feedback - ) - plugin = feedback.version.plugin - - has_update_permission: bool = ( - request.user in plugin.editors - or check_plugin_version_approval_rights(request.user, plugin) - ) - if not has_update_permission: - return JsonResponse({"success": False}, status=401) - task = request.POST.get('task') - feedback.task = str(task) - feedback.modified_on = datetime.datetime.now() - feedback.save() - return JsonResponse({"success": True, "modified_on": feedback.modified_on}, status=201) - -@login_required -@require_POST -def version_feedback_delete(request, package_name, version, feedback): - feedback = get_object_or_404( - PluginVersionFeedback, - version__plugin__package_name=package_name, - version__version=version, - pk=feedback - ) - plugin = feedback.version.plugin - status = request.POST.get('status_feedback') - is_update_succeed: bool = False - is_user_can_update_feedback: bool = ( - request.user in plugin.editors - or check_plugin_version_approval_rights(request.user, plugin) - ) - if status == "deleted" and feedback.reviewer == request.user: - feedback.delete() - is_update_succeed: bool = True - elif (status == "completed" or status == "uncompleted") and ( - is_user_can_update_feedback): - feedback.is_completed = (status == "completed") - feedback.save() - is_update_succeed: bool = True - return JsonResponse({"success": is_update_succeed}) - - -def version_download(request, package_name, version): - """ - Update download counter(s) - """ - plugin = get_object_or_404(Plugin, package_name=package_name) - version = get_object_or_404(PluginVersion, plugin=plugin, version=version) - version.downloads = version.downloads + 1 - version.save() - plugin = version.plugin - plugin.downloads = plugin.downloads + 1 - plugin.save(keep_date=True) - - remote_addr = parse_remote_addr(request) - g = GeoIP2() - - if remote_addr: - try: - country_data = g.country(remote_addr) - country_code = country_data['country_code'] - country_name = country_data['country_name'] - except Exception as e: # AddressNotFoundErrors: - country_code = 'N/D' - country_name = 'N/D' - - # Handle null values - country_code = country_code or 'N/D' - country_name = country_name or 'N/D' - - download_record, created = PluginVersionDownload.objects.get_or_create( - plugin_version = version, - country_code = country_code, - country_name = country_name, - download_date = now().date(), - defaults = {'download_count': 1} - ) - if not created: - download_record.download_count = ( - download_record.download_count + 1 - ) - download_record.save() - - if not version.package.file.file.closed: - version.package.file.file.close() - zipfile = open(version.package.file.name, "rb") - file_content = zipfile.read() - response = HttpResponse(file_content, content_type="application/zip") - response["Content-Disposition"] = "attachment; filename=%s-%s.zip" % ( - version.plugin.package_name, - version.version, - ) - return response - - -def version_detail(request, package_name, version): - """ - Show version details - """ - plugin = get_object_or_404(Plugin, package_name=package_name) - version = get_object_or_404(PluginVersion, plugin=plugin, version=version) - return render(request, "plugins/version_detail.html", {"version": version}) - - -############################################### - -# Misc functions - -############################################### - -from django.views.decorators.cache import cache_page - - -def _add_patch_version(version: str, additional_patch: str ) -> str: - """To add patch number in version. - - e.g qgis version = 3.16 we add patch number (99) in versioning -> 3.16.99 - We use this versioning to query against PluginVersion min_qg_version, - so that the query result will include all PluginVersion with - minimum QGIS version 3.16 regardless of the patch number. - """ - - if not version: - return version - separator = '.' - v = version.split(separator) - if len(v) == 2: - two_first_segment = separator.join(v[:2]) - version = f'{two_first_segment}.{additional_patch}' - return version - - -@cache_page(60 * 15) -def xml_plugins(request, qg_version=None, stable_only=None, package_name=None): - """ - The XML file - - accepted parameters: - - * qgis: qgis version - * stable_only: 0/1 - * package_name: Plugin.package_name - - """ - request_version = request.GET.get("qgis", "1.8.0") - version_level = len(str(request_version).split('.')) - 1 - qg_version = ( - qg_version - if qg_version is not None - else vjust( - request_version, fillchar="0", level=version_level, force_zero=True - ) - ) - stable_only = ( - stable_only if stable_only is not None else request.GET.get("stable_only", "0") - ) - package_name = ( - package_name - if package_name is not None - else request.GET.get("package_name", None) - ) - - filters = {} - version_filters = {} - object_list = [] - - if qg_version: - filters.update({'pluginversion__min_qg_version__lte' : _add_patch_version(qg_version, '99')}) - version_filters.update({'min_qg_version__lte' : _add_patch_version(qg_version, '99')}) - filters.update({'pluginversion__max_qg_version__gte' : _add_patch_version(qg_version, '0')}) - version_filters.update({'max_qg_version__gte' : _add_patch_version(qg_version, '0')}) - - # Get all versions for the given plugin) - if package_name: - filters.update({"package_name": package_name}) - try: - plugin = Plugin.approved_objects.get(**filters) - plugin_version_filters = copy.copy(version_filters) - plugin_version_filters.update({"plugin": plugin}) - for plugin_version in PluginVersion.stable_objects.filter( - **plugin_version_filters - ): - object_list.append(plugin_version) - if stable_only != "1": - for plugin_version in PluginVersion.experimental_objects.filter( - **plugin_version_filters - ): - object_list.append(plugin_version) - except Plugin.DoesNotExist: - pass - else: - - # Checked the cached plugins - qgis_version = request.GET.get("qgis", None) - qgis_filename = "plugins_{}.xml".format(qgis_version) - folder_name = os.path.join(settings.MEDIA_ROOT, "cached_xmls") - path_file = os.path.join(folder_name, qgis_filename) - if os.path.exists(path_file): - return HttpResponse(open(path_file).read(), content_type="application/xml") - - trusted_users_ids = list( - zip( - *User.objects.filter( - Q( - user_permissions__codename="can_approve", - user_permissions__content_type__app_label="plugins", - ) - | Q(is_superuser=True) - ) - .distinct() - .values_list("id") - ) - )[0] - qs = Plugin.approved_objects.filter(**filters).annotate( - is_trusted=RawSQL( - "%s.created_by_id in (%s)" - % ( - Plugin._meta.db_table, - (",").join([str(tu) for tu in trusted_users_ids]), - ), - (), - ) - ) - for plugin in qs: - plugin_version_filters = copy.copy(version_filters) - plugin_version_filters.update({"plugin_id": plugin.pk}) - try: - data = PluginVersion.stable_objects.filter(**plugin_version_filters)[0] - setattr(data, "is_trusted", plugin.is_trusted) - object_list.append(data) - except IndexError: - pass - if stable_only != "1": - try: - data = PluginVersion.experimental_objects.filter( - **plugin_version_filters - )[0] - setattr(data, "is_trusted", plugin.is_trusted) - object_list.append(data) - except IndexError: - pass - - return render( - request, - "plugins/plugins.xml", - {"object_list": object_list}, - content_type="text/xml", - ) - - -@cache_page(60 * 15) -def xml_plugins_new(request, qg_version=None, stable_only=None, package_name=None): - """ - The XML file - - accepted parameters: - - * qgis: qgis version - * stable_only: 0/1 - * package_name: Plugin.package_name - - """ - request_version = request.GET.get("qgis", "1.8.0") - version_level = len(str(request_version).split('.')) - 1 - qg_version = ( - qg_version - if qg_version is not None - else vjust( - request_version, fillchar="0", level=version_level, force_zero=True - ) - ) - stable_only = ( - stable_only if stable_only is not None else request.GET.get("stable_only", "0") - ) - package_name = ( - package_name - if package_name is not None - else request.GET.get("package_name", None) - ) - - filters = {} - version_filters = {} - object_list = [] - - if qg_version: - filters.update({'pluginversion__min_qg_version__lte' : _add_patch_version(qg_version, '99')}) - version_filters.update({'min_qg_version__lte' : _add_patch_version(qg_version, '99')}) - filters.update({'pluginversion__max_qg_version__gte' : _add_patch_version(qg_version, '0')}) - version_filters.update({'max_qg_version__gte' : _add_patch_version(qg_version, '0')}) - - # Get all versions for the given plugin - if package_name: - filters.update({"package_name": package_name}) - try: - plugin = Plugin.approved_objects.get(**filters) - plugin_version_filters = copy.copy(version_filters) - plugin_version_filters.update({"plugin": plugin}) - for plugin_version in PluginVersion.stable_objects.filter( - **plugin_version_filters - ): - object_list.append(plugin_version) - if stable_only != "1": - for plugin_version in PluginVersion.experimental_objects.filter( - **plugin_version_filters - ): - object_list.append(plugin_version) - except Plugin.DoesNotExist: - pass - object_list_new = object_list - else: - - # Fast lane: uses raw queries - - trusted_users_ids = """ - (SELECT DISTINCT "auth_user"."id" - FROM "auth_user" - LEFT OUTER JOIN "auth_user_user_permissions" - ON ( "auth_user"."id" = "auth_user_user_permissions"."user_id" ) - LEFT OUTER JOIN "auth_permission" - ON ( "auth_user_user_permissions"."permission_id" = "auth_permission"."id" ) - LEFT OUTER JOIN "django_content_type" - ON ( "auth_permission"."content_type_id" = "django_content_type"."id" ) - WHERE (("auth_permission"."codename" = 'can_approve' - AND "django_content_type"."app_label" = 'plugins') - OR "auth_user"."is_superuser" = True)) - """ - - sql = """ - SELECT DISTINCT ON (pv.plugin_id) pv.*, - pv.created_by_id IN %(trusted_users_ids)s AS is_trusted - FROM %(pv_table)s pv - WHERE ( - pv.approved = True - AND pv."max_qg_version" >= '%(qg_version_with_patch_0)s' - AND pv."min_qg_version" <= '%(qg_version_with_patch_99)s' - AND pv.experimental = %(experimental)s - ) - ORDER BY pv.plugin_id, pv.version DESC - """ - - object_list_new = PluginVersion.objects.raw( - sql - % { - "pv_table": PluginVersion._meta.db_table, - "p_table": Plugin._meta.db_table, - "qg_version": qg_version, - "qg_version_with_patch_0": _add_patch_version(qg_version, '0'), - "qg_version_with_patch_99": _add_patch_version(qg_version, '99'), - "experimental": "False", - "trusted_users_ids": str(trusted_users_ids), - } - ) - - object_list_new = PluginVersion.objects.raw(sql % { - 'pv_table': PluginVersion._meta.db_table, - 'p_table': Plugin._meta.db_table, - 'qg_version_with_patch_0': _add_patch_version(qg_version, '0'), - 'qg_version_with_patch_99': _add_patch_version(qg_version, '99'), - 'experimental': 'False', - 'trusted_users_ids': str(trusted_users_ids), - }) - - - if stable_only != '1': - # Do the query - object_list_new = [o for o in object_list_new] - object_list_new += [o for o in PluginVersion.objects.raw(sql % { - 'pv_table': PluginVersion._meta.db_table, - 'p_table': Plugin._meta.db_table, - 'qg_version_with_patch_0': _add_patch_version(qg_version, '0'), - 'qg_version_with_patch_99': _add_patch_version(qg_version, '99'), - 'experimental': 'True', - 'trusted_users_ids': str(trusted_users_ids), - })] - - return render( - request, - "plugins/plugins.xml", - {"object_list": object_list_new}, - content_type="text/xml", - ) diff --git a/qgis-app/settings.py b/qgis-app/settings.py index e24d7767..08ed308d 100644 --- a/qgis-app/settings.py +++ b/qgis-app/settings.py @@ -80,7 +80,7 @@ "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", # Needed by rpc4django - "plugins.middleware.HttpAuthMiddleware", + "base.middleware.HttpAuthMiddleware", "django.contrib.auth.middleware.RemoteUserMiddleware", "django.contrib.flatpages.middleware.FlatpageFallbackMiddleware", # Added by Tim for advanced loggin options @@ -148,7 +148,6 @@ "djangoratings", "lib", "endless_pagination", - "userexport", "bootstrap_pagination", "sortable_listview", "user_map", diff --git a/qgis-app/settings_docker.py b/qgis-app/settings_docker.py index 5dd7c061..32e87270 100644 --- a/qgis-app/settings_docker.py +++ b/qgis-app/settings_docker.py @@ -53,8 +53,6 @@ "django.contrib.flatpages", # full text search postgres "django.contrib.postgres", - # ABP: - "plugins", "django.contrib.humanize", "django.contrib.syndication", "bootstrap_pagination", @@ -69,7 +67,6 @@ "simplemenu", "tinymce", "rpc4django", - "feedjack", "preferences", "rest_framework", 'rest_framework.authtoken', @@ -141,30 +138,7 @@ ) CELERY_RESULT_BACKEND = 'rpc://' CELERY_BROKER_URL = os.environ.get('BROKER_URL', 'amqp://rabbitmq:5672') -CELERY_BEAT_SCHEDULE = { - 'generate_plugins_xml': { - 'task': 'plugins.tasks.generate_plugins_xml.generate_plugins_xml', - 'schedule': crontab(minute='*/10'), # Execute every 10 minutes. - 'kwargs': { - 'site': DEFAULT_PLUGINS_SITE - } - }, - 'update_feedjack': { - 'task': 'plugins.tasks.update_feedjack.update_feedjack', - 'schedule': crontab(minute='*/30'), # Execute every 30 minutes. - }, - 'update_qgis_versions': { - 'task': 'plugins.tasks.update_qgis_versions.update_qgis_versions', - 'schedule': crontab(minute='*/30'), # Execute every 30 minutes. - }, - # Index synchronization sometimes fails when deleting - # a plugin and None is listed in the search list. So I think - # it would be better if we rebuild the index frequently - 'rebuild_search_index': { - 'task': 'plugins.tasks.rebuild_search_index.rebuild_search_index', - 'schedule': crontab(minute=0, hour=3), # Execute every day at 3 AM. - } -} +CELERY_BEAT_SCHEDULE = {} # Set plugin token access and refresh validity to a very long duration SIMPLE_JWT = { 'ACCESS_TOKEN_LIFETIME': timedelta(days=365*1000), diff --git a/qgis-app/templates/admin/auth/change_list.html b/qgis-app/templates/admin/auth/change_list.html deleted file mode 100644 index 4a149659..00000000 --- a/qgis-app/templates/admin/auth/change_list.html +++ /dev/null @@ -1,14 +0,0 @@ -{% extends "admin/change_list.html" %} -{% load i18n admin_urls %} -{% block object-tools-items %} -{{ block.super }} -
  • - {% trans "Export users" %} -
  • -
  • - {% trans "Export bad plugins list" %} -
  • -
  • - {% trans "Export plugin maintainers" %} -
  • -{% endblock %} diff --git a/qgis-app/urls.py b/qgis-app/urls.py index 90acac68..ce304aa3 100644 --- a/qgis-app/urls.py +++ b/qgis-app/urls.py @@ -2,18 +2,11 @@ from django.conf import settings from django.urls import re_path as url -# Uncomment the next two lines to enable the admin: from django.contrib import admin from django.contrib.flatpages.models import FlatPage from django.urls import include, path -from django.views.generic.base import RedirectView -from django.views.static import serve from drf_yasg import openapi from drf_yasg.views import get_schema_view - -# to find users app views -# from users.views import * -from homepage import homepage from rest_framework import permissions admin.autodiscover() @@ -39,23 +32,10 @@ # (r'^admin/doc/', include('django.contrib.admindocs.urls')), # Uncomment the next line to enable the admin: url(r"^admin/", admin.site.urls), - # ABP: plugins app - url(r"^plugins/", include("plugins.urls")), - # (r'^tags/', include('cab.urls.tags')), - # (r'^bookmarks/', include('cab.urls.bookmarks')), - # (r'^languages/', include('cab.urls.languages')), - # (r'^popular/', include('cab.urls.popular')), url(r"^search/", include("custom_haystack_urls")), url(r"^search/", include("haystack.urls")), - # AG: User Map - # url(r'^community-map/', include('user_map.urls', namespace='user_map')), - # Fix broken URLS in feedjack - # url(r'^planet/feed/$', RedirectView.as_view(url='/planet/feed/atom/')), - # Tim: Feedjack feed aggregator / planet - url(r"^planet/", include("feedjack.urls")), # ABP: autosuggest for tags url(r"^taggit_autosuggest/", include("taggit_autosuggest.urls")), - url(r"^userexport/", include("userexport.urls")), # Styles and other files sharing url(r"^styles/", include("styles.urls")), url(r"^geopackages/", include("geopackages.urls")), @@ -88,11 +68,6 @@ ] -# Home -urlpatterns += [ - url(r"^$", homepage), -] - # API urlpatterns += [ url(r"^api/v1/", include("api.urls")), @@ -114,9 +89,6 @@ simplemenu.register( "/admin/", - "/planet/", - # '/community-map/', - "/plugins/", "/styles/?order_by=-upload_date&&is_gallery=true", "/geopackages/?order_by=-upload_date&&is_gallery=true", "/layerdefinitions/?order_by=-upload_date&&is_gallery=true", diff --git a/qgis-app/userexport/__init__.py b/qgis-app/userexport/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/qgis-app/userexport/admin.py b/qgis-app/userexport/admin.py deleted file mode 100644 index 8c38f3f3..00000000 --- a/qgis-app/userexport/admin.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.contrib import admin - -# Register your models here. diff --git a/qgis-app/userexport/models.py b/qgis-app/userexport/models.py deleted file mode 100644 index 71a83623..00000000 --- a/qgis-app/userexport/models.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.db import models - -# Create your models here. diff --git a/qgis-app/userexport/tests.py b/qgis-app/userexport/tests.py deleted file mode 100644 index 7ce503c2..00000000 --- a/qgis-app/userexport/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/qgis-app/userexport/urls.py b/qgis-app/userexport/urls.py deleted file mode 100755 index 45ac6d08..00000000 --- a/qgis-app/userexport/urls.py +++ /dev/null @@ -1,14 +0,0 @@ -# -* coding:utf-8 *- # -from django.urls import re_path as url -from userexport.views import * - -urlpatterns = [ - url(r"^export$", export, {}, name="userexport"), - url(r"^export_bad$", export_bad, {}, name="userexport-bad"), - url( - r"^export_plugin_maintainers$", - export_plugin_maintainers, - {}, - name="userexport-plugin-maintainers", - ), -] diff --git a/qgis-app/userexport/views.py b/qgis-app/userexport/views.py deleted file mode 100644 index f278c6a6..00000000 --- a/qgis-app/userexport/views.py +++ /dev/null @@ -1,83 +0,0 @@ -# Create your views here. -from django.contrib.auth.models import User -from django.core.exceptions import PermissionDenied -from django.db.models import Q -from django.http import HttpResponse -from plugins.models import * - - -def export(request, **kwargs): - if not request.user.is_superuser: - raise PermissionDenied() - import csv - - response = HttpResponse(content_type="text/csv") - response["Content-Disposition"] = "attachment; filename=plugins_users_list.csv" - writer = csv.writer(response) - for u in User.objects.all(): - writer.writerow([u.username, u.email, u.get_full_name(), u.date_joined]) - return response - - -def export_bad(request, **kwargs): - """Plugin with missing metadata""" - if not request.user.is_superuser: - raise PermissionDenied() - import csv - - response = HttpResponse(content_type="text/csv") - response["Content-Disposition"] = "attachment; filename=bad_plugins_users_list.csv" - writer = csv.writer(response, dialect="excel-tab") - writer.writerow( - [ - "Name", - "Author email", - "Maintainer email", - "Approved", - "Deprecated", - "Tracker", - "Repository", - "About", - ] - ) - for p in Plugin.approved_objects.filter( - Q(about__isnull=True) - | Q(about="") - | Q(description__isnull=True) - | Q(description="") - | Q(tracker__isnull=True) - | Q(tracker="") - ): - writer.writerow( - [ - p.name, - p.created_by.email, - p.email, - p.approved, - p.deprecated, - p.tracker, - p.repository, - p.about, - ] - ) - return response - - -def export_plugin_maintainers(request, **kwargs): - """Plugin maintainers""" - if not request.user.is_superuser: - raise PermissionDenied() - import csv - - response = HttpResponse(content_type="text/csv") - response["Content-Disposition"] = "attachment; filename=plugin_maintainers.csv" - writer = csv.writer(response, dialect="excel-tab") - # writer.writerow(['email']) - for u in ( - User.objects.filter(plugins_created_by__isnull=False, email__isnull=False) - .exclude(email="") - .order_by("email") - .distinct() - ): - writer.writerow([u.email]) - return response From 882bf398edfd34388f510743cdf8576f8e338f5a Mon Sep 17 00:00:00 2001 From: Lova ANDRIARIMALALA <43842786+Xpirix@users.noreply.github.com> Date: Mon, 12 Aug 2024 12:36:20 +0300 Subject: [PATCH 2/9] Fix navigation bar, redirect root url to /styles --- qgis-app/templates/base.html | 30 +++++++++++------------------- qgis-app/urls.py | 6 ++++++ 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/qgis-app/templates/base.html b/qgis-app/templates/base.html index 74b94bd1..4c5e38ec 100644 --- a/qgis-app/templates/base.html +++ b/qgis-app/templates/base.html @@ -61,27 +61,19 @@ {% get_namedmenu Navigation as menu %}

    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 diff --git a/qgis-app/plugins/tests/testfiles/plugin_without_license.zip_ b/qgis-app/plugins/tests/testfiles/plugin_without_license.zip_ deleted file mode 100644 index 1f93a0418775ea6e6f9acc436ca5c23fd101e522..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 39590 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 diff --git a/qgis-app/plugins/tests/testfiles/valid_plugin.zip_ b/qgis-app/plugins/tests/testfiles/valid_plugin.zip_ deleted file mode 100644 index 82210e3347272e48a4798fcd2d2577de8cf8d64f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45559 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