From 1cab51193b46d6d6ce7eedb124e6f66efea8608b Mon Sep 17 00:00:00 2001 From: Oleg Komarov Date: Thu, 4 Apr 2024 13:00:26 +0200 Subject: [PATCH 1/7] delete extension by a maintainer --- common/model_mixins.py | 10 +++--- extensions/forms.py | 8 ++++- extensions/models.py | 10 ++++++ .../templates/extensions/confirm_delete.html | 32 +++++++++++++++++++ .../templates/extensions/manage/update.html | 5 +++ extensions/tests/test_delete.py | 20 ++++++++++++ extensions/urls.py | 5 +++ extensions/views/manage.py | 30 +++++++++++++++-- 8 files changed, 112 insertions(+), 8 deletions(-) create mode 100644 extensions/templates/extensions/confirm_delete.html create mode 100644 extensions/tests/test_delete.py diff --git a/common/model_mixins.py b/common/model_mixins.py index 398d9f92..9d74d8c6 100644 --- a/common/model_mixins.py +++ b/common/model_mixins.py @@ -166,13 +166,13 @@ class SoftDeleteMixin(models.Model): if hard: super().delete() else: + if hasattr(self, 'file'): + # TODO(oleg) move to an archived directory, add random suffix + self.file.delete() self.date_deleted = timezone.now() self.save() - if hasattr(self, 'file'): - # .file should always exist but we don't want to break delete regardless - self.file.delete() - logger.warning('%r pk=%r deleted', self.__class__, self.pk) + logger.info('%r pk=%r deleted', self.__class__, self.pk) def delete_queryset(self, request, queryset): """Given a queryset, soft-delete it from the database.""" @@ -186,4 +186,4 @@ class SoftDeleteMixin(models.Model): if save: self.save() - logger.warning('%r pk=%r deleted', self.__class__, self.pk) + logger.info('%r pk=%r undeleted', self.__class__, self.pk) diff --git a/extensions/forms.py b/extensions/forms.py index d93be8a9..85a07ae2 100644 --- a/extensions/forms.py +++ b/extensions/forms.py @@ -109,6 +109,12 @@ class ExtensionUpdateForm(forms.ModelForm): ) +class ExtensionDeleteForm(forms.ModelForm): + class Meta: + model = extensions.models.Extension + fields = [] + + class VersionForm(forms.ModelForm): class Meta: model = extensions.models.Version @@ -129,7 +135,7 @@ class VersionForm(forms.ModelForm): return self.initial['file'] -class DeleteViewForm(forms.ModelForm): +class VersionDeleteForm(forms.ModelForm): class Meta: model = extensions.models.Version fields = [] diff --git a/extensions/models.py b/extensions/models.py index a6521a13..8cd79d8c 100644 --- a/extensions/models.py +++ b/extensions/models.py @@ -234,6 +234,13 @@ class Extension( self.status = self.STATUSES.APPROVED self.save() + @transaction.atomic + def delete(self, hard=False): + versions = self.versions.filter(date_deleted__isnull=True) + for v in versions: + v.delete(hard=hard) + super().delete(hard=hard) + def get_absolute_url(self): return reverse('extensions:detail', args=[self.type_slug, self.slug]) @@ -246,6 +253,9 @@ class Extension( def get_manage_versions_url(self): return reverse('extensions:manage-versions', args=[self.type_slug, self.slug]) + def get_delete_url(self): + return reverse('extensions:delete', args=[self.type_slug, self.slug]) + def get_new_version_url(self): return reverse('extensions:new-version', args=[self.type_slug, self.slug]) diff --git a/extensions/templates/extensions/confirm_delete.html b/extensions/templates/extensions/confirm_delete.html new file mode 100644 index 00000000..b5c7e4f2 --- /dev/null +++ b/extensions/templates/extensions/confirm_delete.html @@ -0,0 +1,32 @@ +{% extends "common/base.html" %} +{% load i18n %} +{% block content %} +
+
+
+

+ {% blocktranslate with extension_name=extension_name %} Delete {{ extension_name }}?{% endblocktranslate %} +

+

+ {% blocktranslate with extension_name=extension_name %} + By deleting {{ extension_name }} you will lose the files, + download count, reviews as well ratings for the whole extension. + {% endblocktranslate %} +

+
+ + + {% trans 'Cancel' %} + +
+ {% csrf_token %} + +
+
+
+
+
+{% endblock content %} diff --git a/extensions/templates/extensions/manage/update.html b/extensions/templates/extensions/manage/update.html index 8087b57e..c37ac88b 100644 --- a/extensions/templates/extensions/manage/update.html +++ b/extensions/templates/extensions/manage/update.html @@ -115,6 +115,11 @@ {% trans 'Version History' %} + + + {% trans 'Delete Extension' %} + + {% if request.user.is_staff %} Admin diff --git a/extensions/tests/test_delete.py b/extensions/tests/test_delete.py new file mode 100644 index 00000000..b446ceee --- /dev/null +++ b/extensions/tests/test_delete.py @@ -0,0 +1,20 @@ +from django.test import TestCase + +from common.tests.factories.extensions import create_approved_version + + +class DeleteTest(TestCase): + fixtures = ['dev', 'licenses'] + + def test_happy_path(self): + extension = create_approved_version().extension + + url = extension.get_delete_url() + user = extension.authors.first() + self.client.force_login(user) + response = self.client.post(url) + + self.assertEqual(response.status_code, 302) + extension.refresh_from_db() + self.assertIsNotNone(extension.date_deleted) + self.assertTrue(all(v.date_deleted is not None for v in extension.versions.all())) diff --git a/extensions/urls.py b/extensions/urls.py index d03358d6..a44d54d6 100644 --- a/extensions/urls.py +++ b/extensions/urls.py @@ -44,6 +44,11 @@ urlpatterns = [ manage.UpdateExtensionView.as_view(), name='manage', ), + path( + '/delete/', + manage.DeleteExtensionView.as_view(), + name='delete', + ), path( '/manage/versions/', manage.ManageVersionsView.as_view(), diff --git a/extensions/views/manage.py b/extensions/views/manage.py index 8b77a12c..281eb15f 100644 --- a/extensions/views/manage.py +++ b/extensions/views/manage.py @@ -18,9 +18,10 @@ from .mixins import ( from extensions.forms import ( EditPreviewFormSet, AddPreviewFormSet, + ExtensionDeleteForm, ExtensionUpdateForm, VersionForm, - DeleteViewForm, + VersionDeleteForm, ) from extensions.models import Extension, Version from files.forms import FileForm @@ -159,6 +160,31 @@ class UpdateExtensionView( return self.form_invalid(form, edit_preview_formset, add_preview_formset) +class DeleteExtensionView( + LoginRequiredMixin, + MaintainedExtensionMixin, + DeleteView, +): + model = Extension + template_name = 'extensions/confirm_delete.html' + form_class = ExtensionDeleteForm + + def get_success_url(self): + return reverse('extensions:manage-list') + + def get_object(self, queryset=None): + return get_object_or_404( + Extension, + slug=self.kwargs['slug'], + ) + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['extension_name'] = self.object.name + context['confirm_url'] = self.object.get_delete_url() + return context + + class VersionDeleteView( LoginRequiredMixin, MaintainedExtensionMixin, @@ -166,7 +192,7 @@ class VersionDeleteView( ): model = Version template_name = 'extensions/version_confirm_delete.html' - form_class = DeleteViewForm + form_class = VersionDeleteForm def get_success_url(self): return reverse( -- 2.30.2 From 8ed5f4add86183d0344dfa6e042cc83a5ffccd13 Mon Sep 17 00:00:00 2001 From: Oleg Komarov Date: Thu, 4 Apr 2024 16:17:41 +0200 Subject: [PATCH 2/7] allow extension deletion for moderators, add buttons --- extensions/templates/extensions/base.html | 6 ++++ extensions/tests/test_delete.py | 31 ++++++++++++++++++- extensions/views/manage.py | 8 +++-- .../reviewers/extensions_review_detail.html | 6 ++++ 4 files changed, 48 insertions(+), 3 deletions(-) diff --git a/extensions/templates/extensions/base.html b/extensions/templates/extensions/base.html index 2cbc038a..86935aff 100644 --- a/extensions/templates/extensions/base.html +++ b/extensions/templates/extensions/base.html @@ -80,6 +80,12 @@ {% endif %} + {% if user.is_moderator %} + + {% trans 'Delete Extension' %} + + {% endif %} + {% if request.user.is_staff %}