Make it possible to fully delete unlisted/unrated extensions #81

Merged
Anna Sirota merged 24 commits from fully-delete-extension into main 2024-04-19 11:00:19 +02:00
3 changed files with 84 additions and 3 deletions
Showing only changes of commit 5c7a4d1703 - Show all commits

View File

@ -236,11 +236,32 @@ class Extension(
self.status = self.STATUSES.APPROVED
self.save()
@property
def cannot_be_deleted_reasons(self) -> List[str]:
"""Return a list of reasons why this extension cannot be fully deleted."""
reasons = []
if self.ratings.count() > 0:
reasons.append('has_ratings')
if self.is_listed:
reasons.append('is_listed')
return reasons
@transaction.atomic
def delete(self, hard=False):
versions = self.versions.filter(date_deleted__isnull=True)
def delete(self):
hard = self.cannot_be_deleted_reasons == []
versions = self.versions.all()
previews = self.previews.all()
# When soft-deleting, filter out already soft-deleted versions and previews
if not hard:
previews = self.previews.filter(date_deleted__isnull=True)
versions = self.versions.filter(date_deleted__isnull=True)
mode = not hard and 'soft-' or ''
for p in previews:
p.delete(hard=hard)
log.warning('%(mode)sdeleting preview file pk=% source=%s', mode, p.pk, p.source.name)
for v in versions:
v.delete(hard=hard)
log.warning('%(mode)sdeleting extension pk=%s%sdeleted', mode, self.pk)
super().delete(hard=hard)
def get_absolute_url(self):
@ -542,6 +563,22 @@ class Version(CreatedModifiedMixin, RatingMixin, TrackChangesMixin, SoftDeleteMi
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def delete(self, hard=False):
file = self.file
mode = not hard and 'soft-' or ''
args = {
'extension_id': self.extension_id,
'file_id': file.pk,
'mode': mode,
'source': file.source.name,
'version_id': self.pk,
'version': self.version,
}
log.warning('%(mode)sdeleting file pk=%(file_id)s source=%(source)s', args)
file.delete(hard=hard)
log.warning('%(mode)sdeleting version pk=%(version_id)s "%(version)s"', args)
super().delete(hard=hard)
def set_initial_permissions(self, _permissions):
if not _permissions:
return

View File

@ -90,6 +90,11 @@
<i class="i-send"></i>
<span>{% trans 'Submit for Approval' %}</span>
</button>
<a href="{{ extension.get_delete_url }}" class="btn btn-danger">
<i class="i-trash"></i>
<span>{% trans 'Delete Extension' %}</span>
</a>
</div>
</section>
</div>

View File

@ -1,7 +1,10 @@
from django.test import TestCase
from common.tests.factories.extensions import create_approved_version
from common.tests.factories.extensions import create_approved_version, create_version
from common.tests.factories.files import FileFactory
from common.tests.factories.users import UserFactory
import extensions.models
import files.models
class DeleteTest(TestCase):
@ -32,3 +35,39 @@ class DeleteTest(TestCase):
extension.refresh_from_db()
self.assertIsNone(extension.date_deleted)
self.assertTrue(all(v.date_deleted is None for v in extension.versions.all()))
def test_incomplete_unrated_extension_can_be_fully_deleted_by_author(self):
version = create_version(
ratings=[],
extension__previews=[
FileFactory(
type=files.models.File.TYPES.IMAGE,
source='images/b0/b03fa981527593fbe15b28cf37c020220c3d83021999eab036b87f3bca9c9168.png',
)
],
)
extension = version.extension
version_file = version.file
self.assertEqual(extension.status, extension.STATUSES.INCOMPLETE)
preview_file = extension.previews.first()
self.assertIsNotNone(preview_file)
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)
# All relevant records should have been deleted
with self.assertRaises(extensions.models.Extension.DoesNotExist):
extension.refresh_from_db()
with self.assertRaises(extensions.models.Version.DoesNotExist):
version.refresh_from_db()
with self.assertRaises(files.models.File.DoesNotExist):
version_file.refresh_from_db()
with self.assertRaises(files.models.File.DoesNotExist):
preview_file.refresh_from_db()
self.assertIsNone(extensions.models.Extension.objects.filter(pk=extension.pk).first())
self.assertIsNone(extensions.models.Version.objects.filter(pk=version.pk).first())
self.assertIsNone(files.models.File.objects.filter(pk=version_file.pk).first())
self.assertIsNone(files.models.File.objects.filter(pk=preview_file.pk).first())