diff --git a/qgis-app/plugins/migrations/0012_pluginversionfeedback_modified_on.py b/qgis-app/plugins/migrations/0012_pluginversionfeedback_modified_on.py new file mode 100644 index 00000000..2e637f4e --- /dev/null +++ b/qgis-app/plugins/migrations/0012_pluginversionfeedback_modified_on.py @@ -0,0 +1,18 @@ +# 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/models.py b/qgis-app/plugins/models.py index aedfd35c..763dba05 100644 --- a/qgis-app/plugins/models.py +++ b/qgis-app/plugins/models.py @@ -890,6 +890,13 @@ class PluginVersionFeedback(models.Model): 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, diff --git a/qgis-app/plugins/templates/plugins/plugin_feedback.html b/qgis-app/plugins/templates/plugins/plugin_feedback.html index 79b64f94..300e5ff3 100644 --- a/qgis-app/plugins/templates/plugins/plugin_feedback.html +++ b/qgis-app/plugins/templates/plugins/plugin_feedback.html @@ -18,17 +18,32 @@

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

Please tick the checkbox when the task is completed and click the "Update" button to update status.

{% for feedback in feedbacks %} -
- - - {% if feedback.reviewer == request.user %} - - {% endif %} +
+ + {% if feedback.reviewer.first_name %} + {{ feedback.reviewer.first_name }} {{ feedback.reviewer.last_name }} + {% else %} + {{ feedback.reviewer.username }} + {% endif %} + + +
+ + + {% if feedback.reviewer == request.user %} +
+ + +
+ {% endif %} +
{% endfor %} {% if feedbacks %} @@ -151,7 +166,7 @@

{% trans "Feedback Plugin" %} {{ version.plugin.name }} {{ version.version } if (confirm(msg)) { $.ajax({ type: 'POST', - url: url + feedbackId + '/', + url: url + feedbackId + '/delete/', data: formData, success: function (response) { if (response.success) { @@ -165,6 +180,56 @@

{% trans "Feedback Plugin" %} {{ version.plugin.name }} {{ version.version } }); } } + $(document).on('click', '#editButton', function() { + var $feedbackDiv = $(this).closest('.previous-feedback'); + var feedbackId = $feedbackDiv.data('feedback-id'); + var feedbackTask = $feedbackDiv.find('#feedbackTask').text(); + + // Store the original content + var originalContent = $feedbackDiv.html(); + + var inputForm = ` + +
+ + +
+ `; + + $feedbackDiv.html(inputForm); + $feedbackDiv.attr("style", "display: flex;justify-content: space-between;align-items: center;gap: 10px;"); + + $(document).on('click', '#cancelButton', function() { + // Restore the original content + $feedbackDiv.html(originalContent); + $feedbackDiv.attr("style", ""); + }); + + $(document).on('click', '#saveButton', function() { + var newFeedbackTask = $feedbackDiv.find('#editFeedbackInput').val(); + + $.ajax({ + url: url + feedbackId + '/edit/', + method: 'POST', + data: { + 'csrfmiddlewaretoken': '{{ csrf_token }}', + 'task': newFeedbackTask + }, + success: function(response) { + if (response.success) { + $feedbackDiv.html(originalContent); + $feedbackDiv.attr("style", ""); + $feedbackDiv.find('#feedbackTask').html(newFeedbackTask); + $feedbackDiv.find('#editedOn').html( + `— (edited)` + ); + } else { + alert('Failed to update feedback.'); + } + } + }); + }); + }); }) {% endblock %} \ No newline at end of file diff --git a/qgis-app/plugins/tests/test_plugin_version_feedback.py b/qgis-app/plugins/tests/test_plugin_version_feedback.py index 8f7c7702..e770dcae 100644 --- a/qgis-app/plugins/tests/test_plugin_version_feedback.py +++ b/qgis-app/plugins/tests/test_plugin_version_feedback.py @@ -10,6 +10,8 @@ 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"] @@ -399,3 +401,43 @@ def test_non_staff_and_non_editor_cannot_update_feedback(self): 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/urls.py b/qgis-app/plugins/urls.py index 3d1c5da3..af5f08d5 100644 --- a/qgis-app/plugins/urls.py +++ b/qgis-app/plugins/urls.py @@ -313,11 +313,17 @@ name="version_feedback_update", ), url( - r"^(?P[A-Za-z][A-Za-z0-9-_]+)/version/(?P[^\/]+)/feedback/(?P[0-9]+)/$", + 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 diff --git a/qgis-app/plugins/views.py b/qgis-app/plugins/views.py index 35f1c2e4..99994ce0 100644 --- a/qgis-app/plugins/views.py +++ b/qgis-app/plugins/views.py @@ -2,6 +2,7 @@ import copy import logging import os +import datetime from django.conf import settings from django.contrib import messages @@ -1458,6 +1459,29 @@ def version_feedback_update(request, package_name, version): 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):