Skip to content

Commit a39a9da

Browse files
committed
Individual file deletion
1 parent 154fc9f commit a39a9da

File tree

3 files changed

+217
-5
lines changed

3 files changed

+217
-5
lines changed

tests/unit/manage/test_views.py

+145
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,151 @@ def test_delete_project_release_bad_confirm(self):
351351
)
352352
]
353353

354+
def test_delete_project_release_file(self, monkeypatch):
355+
release_file = pretend.stub(
356+
filename='foo-bar.tar.gz',
357+
id=str(uuid.uuid4()),
358+
)
359+
release = pretend.stub(
360+
version='1.2.3',
361+
project=pretend.stub(name='foobar'),
362+
)
363+
request = pretend.stub(
364+
POST={
365+
'confirm_filename': release_file.filename,
366+
'file_id': release_file.id,
367+
},
368+
method="POST",
369+
db=pretend.stub(
370+
delete=pretend.call_recorder(lambda a: None),
371+
add=pretend.call_recorder(lambda a: None),
372+
query=lambda a: pretend.stub(
373+
filter=lambda *a: pretend.stub(one=lambda: release_file),
374+
),
375+
),
376+
route_path=pretend.call_recorder(lambda *a, **kw: '/the-redirect'),
377+
session=pretend.stub(
378+
flash=pretend.call_recorder(lambda *a, **kw: None)
379+
),
380+
user=pretend.stub(),
381+
remote_addr=pretend.stub(),
382+
)
383+
journal_obj = pretend.stub()
384+
journal_cls = pretend.call_recorder(lambda **kw: journal_obj)
385+
monkeypatch.setattr(views, 'JournalEntry', journal_cls)
386+
387+
view = views.ManageProjectRelease(release, request)
388+
389+
result = view.delete_project_release_file()
390+
391+
assert isinstance(result, HTTPSeeOther)
392+
assert result.headers["Location"] == "/the-redirect"
393+
394+
assert request.session.flash.calls == [
395+
pretend.call(
396+
f"Successfully deleted file {release_file.filename!r}.",
397+
queue="success",
398+
)
399+
]
400+
assert request.db.delete.calls == [pretend.call(release_file)]
401+
assert request.db.add.calls == [pretend.call(journal_obj)]
402+
assert journal_cls.calls == [
403+
pretend.call(
404+
name=release.project.name,
405+
action=f"remove file {release_file.filename}",
406+
version=release.version,
407+
submitted_by=request.user,
408+
submitted_from=request.remote_addr,
409+
),
410+
]
411+
assert request.route_path.calls == [
412+
pretend.call(
413+
'manage.project.release',
414+
project_name=release.project.name,
415+
version=release.version,
416+
)
417+
]
418+
419+
def test_delete_project_release_file_no_confirm(self):
420+
release = pretend.stub(
421+
version='1.2.3',
422+
project=pretend.stub(name='foobar'),
423+
)
424+
request = pretend.stub(
425+
POST={'confirm_filename': ''},
426+
method="POST",
427+
db=pretend.stub(delete=pretend.call_recorder(lambda a: None)),
428+
route_path=pretend.call_recorder(lambda *a, **kw: '/the-redirect'),
429+
session=pretend.stub(
430+
flash=pretend.call_recorder(lambda *a, **kw: None)
431+
),
432+
)
433+
view = views.ManageProjectRelease(release, request)
434+
435+
result = view.delete_project_release_file()
436+
437+
assert isinstance(result, HTTPSeeOther)
438+
assert result.headers["Location"] == "/the-redirect"
439+
440+
assert request.db.delete.calls == []
441+
assert request.session.flash.calls == [
442+
pretend.call(
443+
"Must confirm the request.", queue='error'
444+
)
445+
]
446+
assert request.route_path.calls == [
447+
pretend.call(
448+
'manage.project.release',
449+
project_name=release.project.name,
450+
version=release.version,
451+
)
452+
]
453+
454+
def test_delete_project_release_file_bad_confirm(self):
455+
release_file = pretend.stub(
456+
filename='foo-bar.tar.gz',
457+
id=str(uuid.uuid4()),
458+
)
459+
release = pretend.stub(
460+
version='1.2.3',
461+
project=pretend.stub(name='foobar'),
462+
)
463+
request = pretend.stub(
464+
POST={'confirm_filename': 'invalid'},
465+
method="POST",
466+
db=pretend.stub(
467+
delete=pretend.call_recorder(lambda a: None),
468+
query=lambda a: pretend.stub(
469+
filter=lambda *a: pretend.stub(one=lambda: release_file),
470+
),
471+
),
472+
route_path=pretend.call_recorder(lambda *a, **kw: '/the-redirect'),
473+
session=pretend.stub(
474+
flash=pretend.call_recorder(lambda *a, **kw: None)
475+
),
476+
)
477+
view = views.ManageProjectRelease(release, request)
478+
479+
result = view.delete_project_release_file()
480+
481+
assert isinstance(result, HTTPSeeOther)
482+
assert result.headers["Location"] == "/the-redirect"
483+
484+
assert request.db.delete.calls == []
485+
assert request.session.flash.calls == [
486+
pretend.call(
487+
f"'invalid' is not the same as {release_file.filename!r}",
488+
queue="error",
489+
)
490+
]
491+
assert request.route_path.calls == [
492+
pretend.call(
493+
'manage.project.release',
494+
project_name=release.project.name,
495+
version=release.version,
496+
)
497+
]
498+
354499

355500
class TestManageProjectRoles:
356501

warehouse/manage/views.py

+66-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from warehouse.manage.forms import (
2424
CreateRoleForm, ChangeRoleForm, SaveProfileForm
2525
)
26-
from warehouse.packaging.models import JournalEntry, Role
26+
from warehouse.packaging.models import JournalEntry, Role, File
2727
from warehouse.utils.project import confirm_project, remove_project
2828

2929

@@ -186,6 +186,71 @@ def delete_project_release(self):
186186
)
187187
)
188188

189+
@view_config(
190+
request_method="POST",
191+
request_param=["confirm_filename", "file_id"]
192+
)
193+
def delete_project_release_file(self):
194+
filename = self.request.POST.get('confirm_filename')
195+
if not filename:
196+
self.request.session.flash(
197+
"Must confirm the request.", queue='error'
198+
)
199+
return HTTPSeeOther(
200+
self.request.route_path(
201+
'manage.project.release',
202+
project_name=self.release.project.name,
203+
version=self.release.version,
204+
)
205+
)
206+
207+
release_file = (
208+
self.request.db.query(File)
209+
.filter(
210+
File.name == self.release.project.name,
211+
File.id == self.request.POST.get('file_id'),
212+
)
213+
.one()
214+
)
215+
216+
if filename != release_file.filename:
217+
self.request.session.flash(
218+
f"{filename!r} is not the same as {release_file.filename!r}",
219+
queue="error",
220+
)
221+
return HTTPSeeOther(
222+
self.request.route_path(
223+
'manage.project.release',
224+
project_name=self.release.project.name,
225+
version=self.release.version,
226+
)
227+
)
228+
229+
self.request.db.add(
230+
JournalEntry(
231+
name=self.release.project.name,
232+
action=f"remove file {release_file.filename}",
233+
version=self.release.version,
234+
submitted_by=self.request.user,
235+
submitted_from=self.request.remote_addr,
236+
),
237+
)
238+
239+
self.request.db.delete(release_file)
240+
241+
self.request.session.flash(
242+
f"Successfully deleted file {release_file.filename!r}.",
243+
queue="success",
244+
)
245+
246+
return HTTPSeeOther(
247+
self.request.route_path(
248+
'manage.project.release',
249+
project_name=self.release.project.name,
250+
version=self.release.version,
251+
)
252+
)
253+
189254

190255
@view_config(
191256
route_name="manage.project.roles",

warehouse/templates/manage/release.html

+6-4
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,10 @@ <h3 class="modal__title">Delete Release {{ release.version }}?</h3>
139139
<div id="delete-file-modal-{{ loop.index }}" class="modal">
140140
{% set project_name = project.normalized_name %}
141141
<div class="modal__content" role="dialog">
142-
<form method="POST" action="" class="modal__form">
142+
<form method="POST" class="modal__form">
143+
<input name="csrf_token" type="hidden" value="{{ request.session.get_csrf_token() }}">
144+
<input name="file_id" type="hidden" value="{{ file.id }}">
145+
{{ file.id }}
143146
<a href="#modal-close" title="Close" class="modal__close">
144147
<i class="fa fa-times" aria-hidden="true"></i>
145148
<span class="sr-only">close</span>
@@ -150,9 +153,8 @@ <h3 class="modal__title">Delete {{ file.filename }}?</h3>
150153
<p>Warning: This action cannot be undone!</p>
151154
</div>
152155
<p>Confirm the project name to continue.</p>
153-
<input name="csrf_token" type="hidden" value="{{ request.session.get_csrf_token() }}">
154-
<label for="confirm">Project Name</label>
155-
<input name="confirm" type="text" placeholder="Confirm project name" autocomplete="off" autocorrect="off" autocapitalize="off">
156+
<label for="confirm_filename">File name</label>
157+
<input name="confirm_filename" type="text" placeholder="Confirm file name" autocomplete="off" autocorrect="off" autocapitalize="off">
156158
</div>
157159
<div class="modal__footer">
158160
<a href="#modal-close" class="button modal__action">Cancel</a>

0 commit comments

Comments
 (0)