From 7527c5bea5b1910373a4b9204861f0a40bf6ba37 Mon Sep 17 00:00:00 2001 From: Lova ANDRIARIMALALA <43842786+Xpirix@users.noreply.github.com> Date: Wed, 29 Nov 2023 10:33:48 +0300 Subject: [PATCH 1/2] Add maintainer field to plugin update --- qgis-app/plugins/forms.py | 14 ++++++++++++++ .../plugins/templates/plugins/plugin_form.html | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/qgis-app/plugins/forms.py b/qgis-app/plugins/forms.py index 8dabcf5f..fb673d42 100644 --- a/qgis-app/plugins/forms.py +++ b/qgis-app/plugins/forms.py @@ -43,10 +43,24 @@ class Meta: "tracker", "repository", "owners", + "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['created_by'].choices = choices + self.fields['created_by'].label = "Maintainer" + def clean(self): """ Check author diff --git a/qgis-app/plugins/templates/plugins/plugin_form.html b/qgis-app/plugins/templates/plugins/plugin_form.html index 4555a745..86edcd27 100644 --- a/qgis-app/plugins/templates/plugins/plugin_form.html +++ b/qgis-app/plugins/templates/plugins/plugin_form.html @@ -66,7 +66,7 @@

{{ form_title }} {{ plugin }}

let element = document.getElementById('id_owners'); if(element) { $('#id_owners').chosen({ - placeholder_text_multiple: "Select Some Owners", + placeholder_text_multiple: "Select Some Collaborators", no_results_text: "Oops, nothing found!" }); clearInterval(checkElement); From eb6f1619e8ae70c691a5db314f20663724714298 Mon Sep 17 00:00:00 2001 From: Lova ANDRIARIMALALA <43842786+Xpirix@users.noreply.github.com> Date: Thu, 30 Nov 2023 09:05:47 +0300 Subject: [PATCH 2/2] Add maintainer and display_created_by fields --- qgis-app/plugins/forms.py | 7 +- .../migrations/0004_merge_20231122_0223.py | 14 +++ .../migrations/0005_plugin_maintainer.py | 29 +++++ .../0006_plugin_display_created_by.py | 18 ++++ qgis-app/plugins/models.py | 19 ++++ .../templates/plugins/plugin_detail.html | 9 +- .../plugins/tests/test_change_maintainer.py | 101 ++++++++++++++++++ 7 files changed, 193 insertions(+), 4 deletions(-) create mode 100644 qgis-app/plugins/migrations/0004_merge_20231122_0223.py create mode 100644 qgis-app/plugins/migrations/0005_plugin_maintainer.py create mode 100644 qgis-app/plugins/migrations/0006_plugin_display_created_by.py create mode 100644 qgis-app/plugins/tests/test_change_maintainer.py diff --git a/qgis-app/plugins/forms.py b/qgis-app/plugins/forms.py index fb673d42..0361d287 100644 --- a/qgis-app/plugins/forms.py +++ b/qgis-app/plugins/forms.py @@ -43,7 +43,8 @@ class Meta: "tracker", "repository", "owners", - "created_by", + "maintainer", + "display_created_by", "tags", "server", ) @@ -58,8 +59,8 @@ def __init__(self, *args, **kwargs): for owner in self.instance.owners.exclude(pk=self.instance.created_by.pk): choices += ((owner.pk, owner.username + " (Collaborator)"),) - self.fields['created_by'].choices = choices - self.fields['created_by'].label = "Maintainer" + self.fields['maintainer'].choices = choices + self.fields['maintainer'].label = "Maintainer" def clean(self): """ diff --git a/qgis-app/plugins/migrations/0004_merge_20231122_0223.py b/qgis-app/plugins/migrations/0004_merge_20231122_0223.py new file mode 100644 index 00000000..f7e5c5be --- /dev/null +++ b/qgis-app/plugins/migrations/0004_merge_20231122_0223.py @@ -0,0 +1,14 @@ +# Generated by Django 2.2.25 on 2023-11-23 07:56 + +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_plugin_maintainer.py b/qgis-app/plugins/migrations/0005_plugin_maintainer.py new file mode 100644 index 00000000..dfeffa0b --- /dev/null +++ b/qgis-app/plugins/migrations/0005_plugin_maintainer.py @@ -0,0 +1,29 @@ +# 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/0006_plugin_display_created_by.py b/qgis-app/plugins/migrations/0006_plugin_display_created_by.py new file mode 100644 index 00000000..85af8484 --- /dev/null +++ b/qgis-app/plugins/migrations/0006_plugin_display_created_by.py @@ -0,0 +1,18 @@ +# 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/models.py b/qgis-app/plugins/models.py index eeba37d2..db95fda6 100644 --- a/qgis-app/plugins/models.py +++ b/qgis-app/plugins/models.py @@ -322,6 +322,22 @@ class Plugin(models.Model): 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=_( @@ -529,6 +545,7 @@ 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 @@ -537,6 +554,8 @@ def save(self, keep_date=False, *args, **kwargs): 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) diff --git a/qgis-app/plugins/templates/plugins/plugin_detail.html b/qgis-app/plugins/templates/plugins/plugin_detail.html index 951862d0..5cc7a7e1 100644 --- a/qgis-app/plugins/templates/plugins/plugin_detail.html +++ b/qgis-app/plugins/templates/plugins/plugin_detail.html @@ -126,9 +126,16 @@

{{ object.name }}
{% trans "Author's email"%}
{{ object.email }}
{% endif %} + {% if object.display_created_by %} +
{% trans "Created by"%}
+
+ {{ object.created_by }} +
+ + {% endif %}
{% trans "Maintainer"%}
- {{ object.created_by }} + {{ object.maintainer }}
{% if object.owners.count %}
{% trans "Collaborators"%}
diff --git a/qgis-app/plugins/tests/test_change_maintainer.py b/qgis-app/plugins/tests/test_change_maintainer.py new file mode 100644 index 00000000..a9357f6f --- /dev/null +++ b/qgis-app/plugins/tests/test_change_maintainer.py @@ -0,0 +1,101 @@ +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.delay", 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()