Make it possible to fully delete unlisted/unrated extensions #81
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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())
|
||||
|
Loading…
Reference in New Issue
Block a user