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()