Skip to content

Commit a32d9a3

Browse files
committed
Add file unpacking form to editor tools.
1 parent 97ddcd1 commit a32d9a3

File tree

2 files changed

+144
-13
lines changed

2 files changed

+144
-13
lines changed

physionet-django/console/templates/console/submission_info_card.html

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
<li class="nav-item">
2323
<a class="nav-link {% if passphrase %} active {% endif %}" id="embargo-tab" data-toggle="tab" href="#embargo" role="tab" aria-controls="reassign" aria-selected="false">Embargo</a>
2424
</li>
25+
<li class="nav-item">
26+
<a class="nav-link" id="manage-files-tab" data-toggle="tab" href="#manage-files" role="tab" aria-controls="manage-files" aria-selected="false">Manage Files</a>
27+
</li>
2528
{% endif %}
2629
{% if project.submission_status >= SubmissionStatus.NEEDS_COPYEDIT %}
2730
<li class="nav-item">
@@ -230,6 +233,90 @@ <h5 class="modal-title" id="exampleModalLongTitle">Embargo files</h5>
230233
</div>
231234
{% endif %}
232235

236+
{# Manage Files #}
237+
{% if project.editor == user %}
238+
<div class="tab-pane fade" id="manage-files" role="tabpanel" aria-labelledby="manage-files-tab">
239+
<h5 class="card-title">File Management</h5>
240+
<p class="card-text">
241+
This tool allows you to unpack compressed files (tar.gz, zip, gz, bz2, xz) within the project directory.
242+
This is useful for extracting archives that authors have uploaded.
243+
</p>
244+
245+
<div class="alert alert-info">
246+
<strong>Project files are located at:</strong> {{ project.file_root }}
247+
</div>
248+
249+
<div class="alert alert-info">
250+
<strong>Supported formats:</strong> .tar.gz, .tgz, .tar.bz2, .tar.xz, .zip, .gz, .bz2, .xz
251+
</div>
252+
253+
<form action="{% url 'submission_info' project.slug %}" method="POST" id="file_unpack_form">
254+
{% csrf_token %}
255+
<div class="form-group">
256+
<label for="{{ file_unpack_form.file_path.id_for_label }}">{{ file_unpack_form.file_path.label }}</label>
257+
{{ file_unpack_form.file_path }}
258+
{% if file_unpack_form.file_path.help_text %}
259+
<small class="form-text text-muted">{{ file_unpack_form.file_path.help_text }}</small>
260+
{% endif %}
261+
<small class="form-text text-muted">
262+
<strong>Tip:</strong> Use relative paths from the project root. For example, if your file is in a subdirectory called "data", enter "data/CFPFlyer.zip"
263+
</small>
264+
{% if file_unpack_form.file_path.errors %}
265+
<div class="invalid-feedback d-block">
266+
{% for error in file_unpack_form.file_path.errors %}
267+
{{ error }}
268+
{% endfor %}
269+
</div>
270+
{% endif %}
271+
</div>
272+
273+
<div class="form-group">
274+
<label for="{{ file_unpack_form.target_directory.id_for_label }}">{{ file_unpack_form.target_directory.label }}</label>
275+
{{ file_unpack_form.target_directory }}
276+
{% if file_unpack_form.target_directory.help_text %}
277+
<small class="form-text text-muted">{{ file_unpack_form.target_directory.help_text }}</small>
278+
{% endif %}
279+
{% if file_unpack_form.target_directory.errors %}
280+
<div class="invalid-feedback d-block">
281+
{% for error in file_unpack_form.target_directory.errors %}
282+
{{ error }}
283+
{% endfor %}
284+
</div>
285+
{% endif %}
286+
</div>
287+
288+
<div class="form-group">
289+
<div class="form-check">
290+
{{ file_unpack_form.overwrite_existing }}
291+
<label class="form-check-label" for="{{ file_unpack_form.overwrite_existing.id_for_label }}">
292+
{{ file_unpack_form.overwrite_existing.label }}
293+
</label>
294+
{% if file_unpack_form.overwrite_existing.help_text %}
295+
<small class="form-text text-muted">{{ file_unpack_form.overwrite_existing.help_text }}</small>
296+
{% endif %}
297+
</div>
298+
{% if file_unpack_form.overwrite_existing.errors %}
299+
<div class="invalid-feedback d-block">
300+
{% for error in file_unpack_form.overwrite_existing.errors %}
301+
{{ error }}
302+
{% endfor %}
303+
</div>
304+
{% endif %}
305+
</div>
306+
307+
<button type="submit" class="btn btn-primary" name="unpack_file">
308+
<i class="fas fa-archive"></i> Unpack File
309+
</button>
310+
</form>
311+
312+
<hr>
313+
314+
<div class="alert alert-warning">
315+
<strong>Note:</strong> This operation will modify the project files. Make sure you have the necessary permissions and that the file paths are correct.
316+
</div>
317+
</div>
318+
{% endif %}
319+
233320
{# Draft DOI #}
234321
<div class="tab-pane fade" id="doi" role="tabpanel" aria-labelledby="doi-tab">
235322
<p>The following DOIs have been registered for the project. These may be useful

physionet-django/console/views.py

Lines changed: 57 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
from physionet.enums import LogCategory
7878
from console import forms, utility, services
7979
from console.forms import ProjectFilterForm, UserFilterForm
80+
from console.file_utils import FileUnpacker
8081
from project.cloud.s3 import (
8182
create_s3_bucket,
8283
upload_project_to_S3,
@@ -282,7 +283,8 @@ def submission_info_card_params(request,
282283
embargo_form,
283284
internal_note_form,
284285
bulk_download,
285-
force_calculate):
286+
force_calculate,
287+
file_unpack_form=None):
286288
"""
287289
Parameters used across submission_info_card.html pages, including:
288290
@@ -311,6 +313,7 @@ def submission_info_card_params(request,
311313
'embargo_form': embargo_form,
312314
'notes': notes,
313315
'internal_note_form': internal_note_form,
316+
'file_unpack_form': file_unpack_form,
314317
}
315318

316319

@@ -328,6 +331,7 @@ def submission_info(request, project_slug):
328331
reassign_editor_form = forms.ReassignEditorForm(project=project, data=data)
329332
internal_note_form = forms.InternalNoteForm(data)
330333
embargo_form = forms.EmbargoFilesDaysForm()
334+
file_unpack_form = forms.FileUnpackForm(data)
331335
passphrase = ''
332336
anonymous_url = project.get_anonymous_url()
333337

@@ -376,6 +380,40 @@ def submission_info(request, project_slug):
376380
else:
377381
messages.error(request, "You are not authorized to delete this note.")
378382
return redirect(f'{request.path}?tab=notes')
383+
elif 'unpack_file' in request.POST and user == project.editor:
384+
if file_unpack_form.is_valid():
385+
try:
386+
project_root = project.file_root()
387+
388+
LOGGER.info(f"Project root: {project_root}")
389+
LOGGER.info(f"File path: {file_unpack_form.cleaned_data['file_path']}")
390+
LOGGER.info(f"Full file path: {os.path.join(project_root, file_unpack_form.cleaned_data['file_path'])}")
391+
392+
result = FileUnpacker.unpack_file(
393+
project_root=project_root,
394+
file_path=file_unpack_form.cleaned_data['file_path'],
395+
target_directory=file_unpack_form.cleaned_data['target_directory'] or None,
396+
overwrite_existing=file_unpack_form.cleaned_data['overwrite_existing']
397+
)
398+
399+
if result['success']:
400+
messages.success(
401+
request,
402+
f"Successfully unpacked {len(result['extracted_files'])} files to {result['extract_directory']}"
403+
)
404+
# Reset form after successful unpacking
405+
file_unpack_form = forms.FileUnpackForm()
406+
else:
407+
messages.error(request, "File unpacking failed")
408+
409+
except Exception as e:
410+
error_msg = f"Error unpacking file: {str(e)}"
411+
if "File not found" in str(e):
412+
error_msg += f" (searched in: {project_root})"
413+
messages.error(request, error_msg)
414+
LOGGER.error(f"File unpacking error for project {project.slug}: {str(e)}")
415+
else:
416+
messages.error(request, 'Invalid file unpacking submission. See errors below.')
379417

380418
return render(request, 'console/submission_info.html',
381419
{**submission_info_card_params(
@@ -385,11 +423,13 @@ def submission_info(request, project_slug):
385423
embargo_form,
386424
internal_note_form,
387425
bulk_download=True,
388-
force_calculate=False
426+
force_calculate=False,
427+
file_unpack_form=file_unpack_form
389428
),
390429
'copyedit_logs': copyedit_logs,
391430
'passphrase': passphrase,
392431
'anonymous_url': anonymous_url,
432+
'file_unpack_form': file_unpack_form,
393433
}
394434
)
395435

@@ -449,7 +489,8 @@ def edit_submission(request, project_slug, *args, **kwargs):
449489
embargo_form,
450490
internal_note_form,
451491
bulk_download=True,
452-
force_calculate=False
492+
force_calculate=False,
493+
file_unpack_form=None
453494
),
454495
'edit_submission_form': edit_submission_form,
455496
'editor_home': True,
@@ -590,15 +631,16 @@ def copyedit_submission(request, project_slug, *args, **kwargs):
590631
response = render(
591632
request,
592633
'console/copyedit_submission.html',
593-
{**submission_info_card_params(
594-
request,
595-
project,
596-
reassign_editor_form,
597-
embargo_form,
598-
internal_note_form,
599-
bulk_download=False,
600-
force_calculate=True,
601-
),
634+
{**submission_info_card_params(
635+
request,
636+
project,
637+
reassign_editor_form,
638+
embargo_form,
639+
internal_note_form,
640+
bulk_download=False,
641+
force_calculate=True,
642+
file_unpack_form=None
643+
),
602644
'description_form': description_form,
603645
'ethics_form': ethics_form,
604646
'individual_size_limit': readable_size(ActiveProject.INDIVIDUAL_FILE_SIZE_LIMIT),
@@ -674,6 +716,7 @@ def awaiting_authors(request, project_slug, *args, **kwargs):
674716
internal_note_form,
675717
bulk_download=True,
676718
force_calculate=False,
719+
file_unpack_form=None
677720
),
678721
'copyedit_logs': copyedit_logs,
679722
'outstanding_emails': outstanding_emails,
@@ -790,7 +833,8 @@ def publish_submission(request, project_slug, *args, **kwargs):
790833
embargo_form,
791834
internal_note_form,
792835
bulk_download=True,
793-
force_calculate=True
836+
force_calculate=True,
837+
file_unpack_form=None
794838
),
795839
'publishable': publishable,
796840
'copyedit_logs': copyedit_logs,

0 commit comments

Comments
 (0)