Skip to content

Commit dbc37ee

Browse files
authored
Deleted release artifact files when they're cleared in admin
1 parent 1612897 commit dbc37ee

File tree

2 files changed

+67
-11
lines changed

2 files changed

+67
-11
lines changed

releases/admin.py

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,33 @@
1+
from django import forms
12
from django.contrib import admin
23

34
from .models import Release
45

6+
_ARTIFACTS = ["checksum", "tarball", "wheel"]
7+
8+
9+
class ReleaseAdminForm(forms.ModelForm):
10+
def __init__(self, *args, **kwargs):
11+
super().__init__(*args, **kwargs)
12+
13+
# Add `accept` attributes to the artifact file fields to make it a bit
14+
# easier to pick the right files in the browser's 'filepicker
15+
extensions = {"tarball": ".gz", "wheel": ".whl", "checksum": ".asc,.txt"}
16+
for field, accept in extensions.items():
17+
widget = self.fields[field].widget
18+
widget.attrs.setdefault("accept", accept)
19+
20+
self._previous_file_fields = {a: getattr(self.instance, a) for a in _ARTIFACTS}
21+
22+
# Extending _save_m2m() instead of save() lets us support both save(commit=True/False)
23+
def _save_m2m(self):
24+
super()._save_m2m()
25+
26+
# Delete any files from storage that might have been cleared
27+
for a in _ARTIFACTS:
28+
if self._previous_file_fields[a] and not getattr(self.instance, a):
29+
self._previous_file_fields[a].delete(save=False)
30+
531

632
@admin.register(Release)
733
class ReleaseAdmin(admin.ModelAdmin):
@@ -10,6 +36,7 @@ class ReleaseAdmin(admin.ModelAdmin):
1036
("Dates", {"fields": ["date", "eol_date"]}),
1137
("Artifacts", {"fields": ["tarball", "wheel", "checksum"]}),
1238
]
39+
form = ReleaseAdminForm
1340
list_display = (
1441
"version",
1542
"show_is_published",
@@ -25,16 +52,6 @@ class ReleaseAdmin(admin.ModelAdmin):
2552
list_filter = ("status", "is_lts", "is_active")
2653
ordering = ("-major", "-minor", "-micro", "-status", "-iteration")
2754

28-
def get_form(self, request, obj=None, change=False, **kwargs):
29-
form_class = super().get_form(request, obj=obj, change=change, **kwargs)
30-
# Add `accept` attributes to the artifact file fields to make it a bit
31-
# easier to pick the right files in the browser's 'filepicker
32-
extensions = {"tarball": ".tar.gz", "wheel": ".whl", "checksum": ".asc,.txt"}
33-
for field, accept in extensions.items():
34-
widget = form_class.base_fields[field].widget
35-
widget.attrs.setdefault("accept", accept)
36-
return form_class
37-
3855
@admin.display(
3956
description="status",
4057
ordering="status",

releases/tests.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import datetime
22
import re
3+
import shutil
4+
import tempfile
5+
from pathlib import Path
36

47
from django.contrib import admin
58
from django.core.exceptions import ValidationError
@@ -424,7 +427,7 @@ def test_artifact_file_inputs_have_extension_hint(self):
424427
form = self.form_class(auto_id=None) # auto_id=None makes testing easier
425428
self.assertHTMLEqual(
426429
form["tarball"].as_widget(),
427-
'<input type="file" name="tarball" accept=".tar.gz">',
430+
'<input type="file" name="tarball" accept=".gz">',
428431
)
429432
self.assertHTMLEqual(
430433
form["wheel"].as_widget(), '<input type="file" name="wheel" accept=".whl">'
@@ -451,6 +454,42 @@ def test_file_upload_renames_correctly(self):
451454
)
452455
self.assertEqual(release.checksum.name, "pgp/Django-1.2.3.checksum.txt")
453456

457+
def test_clearing_also_deletes_file(self, commit_save=True):
458+
tempdir = Path(tempfile.mkdtemp(prefix="djangoprojectcom_"))
459+
self.addCleanup(shutil.rmtree, tempdir, ignore_errors=True)
460+
461+
files = {
462+
"checksum": tempdir / "checksum.txt",
463+
"tarball": tempdir / "tarball.tar.gz",
464+
"wheel": tempdir / "wheel.whl",
465+
}
466+
# Create the files on disk:
467+
for f in files.values():
468+
f.touch()
469+
470+
with override_settings(MEDIA_ROOT=tempdir):
471+
release = Release.objects.create(
472+
version="1.0", **{a: f.name for a, f in files.items()}
473+
)
474+
data = {"version": "2.0", **{f"{a}-clear": True for a in files.keys()}}
475+
form = self.form_class(instance=release, data=data)
476+
self.assertTrue(form.is_valid(), form.errors)
477+
form.save(commit=commit_save)
478+
if not commit_save:
479+
for artifact, tmpfile in files.items():
480+
with self.subTest(artifact=artifact):
481+
self.assertTrue(tmpfile.exists())
482+
release.save()
483+
form.save_m2m()
484+
485+
for artifact, tmpfile in files.items():
486+
with self.subTest(artifact=artifact):
487+
self.assertFalse(getattr(release, artifact))
488+
self.assertFalse(tmpfile.exists())
489+
490+
def test_clearing_also_deletes_file_commit_false(self):
491+
self.test_clearing_also_deletes_file(commit_save=False)
492+
454493

455494
class RedirectViewTestCase(TestCase):
456495
def test_redirect(self):

0 commit comments

Comments
 (0)