Skip to content

Commit

Permalink
YDA-5721: add admin configuration of preservable file formats lists
Browse files Browse the repository at this point in the history
  • Loading branch information
leonidastri authored Oct 8, 2024
1 parent 23ca082 commit ba6449a
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 1 deletion.
71 changes: 71 additions & 0 deletions admin/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@
url_for,
)
from flask import current_app as app
from irods.message import iRODSMessage
from jinja2 import ChoiceLoader, FileSystemLoader
from markupsafe import escape
from werkzeug.utils import secure_filename

import api
from util import get_theme_directories, length_check
Expand Down Expand Up @@ -271,3 +273,72 @@ def get_publication_terms() -> Optional[str]:
flash("Failed to load publication terms from API", "error")

return "Error: failed to read publication terms"


@admin_bp.route('/upload_file_formats', methods=['POST'])
@admin_required
def upload_file_formats() -> Response:
file = request.files['file']
filename = secure_filename(request.files['file'].filename)

if not filename.endswith('.json'):
flash(f"File format list '{filename}' is not a JSON file.", "danger")
return redirect(url_for("admin_bp.index"))

if request.content_length > 1 * 1024 * 1024:
flash(f"File format list '{filename}' exceeds the 1 MB size limit.", "danger")
return redirect(url_for("admin_bp.index"))

try:
file_content = file.read().decode('utf-8')
data = json.loads(file_content)
except (json.JSONDecodeError, UnicodeDecodeError):
flash(f"File format list '{filename}' contains invalid JSON.", "danger")
return redirect(url_for("admin_bp.index"))

required_keys = ["name", "help", "advice", "formats"]
if not all(key in data for key in required_keys):
flash(f"File format list '{filename}' is missing required keys.", "danger")
return redirect(url_for("admin_bp.index"))

if not isinstance(data['name'], str) or not isinstance(data['help'], str) or not isinstance(data['advice'], str):
flash(f"File format list '{filename}' has invalid types for 'name', 'help', or 'advice'.", "danger")
return redirect(url_for("admin_bp.index"))

if not isinstance(data['formats'], list) or not all(isinstance(ext, str) for ext in data['formats']):
flash(f"File format list '{filename}' has an invalid 'formats' field. It should be a list of extensions.", "danger")
return redirect(url_for("admin_bp.index"))

file_path = path.join("/" + g.irods.zone, 'yoda', 'file_formats', filename)

encode_unicode_content = iRODSMessage.encode_unicode(file_content)

try:
with g.irods.data_objects.open(file_path, 'w') as obj_desc:
obj_desc.write(encode_unicode_content)
obj_desc.close()
flash(f"File format list '{filename}' uploaded successfully.", "success")
except Exception:
flash(f"Failed to upload file format list '{filename}'.", "danger")

return redirect(url_for("admin_bp.index"))


@admin_bp.route('/delete_file_formats', methods=['POST'])
@admin_required
def delete_file_formats() -> Response:
filename = request.form.get('filename')

if not filename:
flash("No file format list specified for deletion.", "danger")
return redirect(url_for("admin_bp.index"))

file_path = path.join("/" + g.irods.zone, 'yoda', 'file_formats', filename + '.json')

try:
g.irods.data_objects.unlink(file_path, force=False)
flash(f"File format list '{filename}.json' deleted successfully.", "success")
except Exception:
flash(f"Failed to delete file format list '{filename}.json'.", "danger")

return redirect(url_for("admin_bp.index"))
31 changes: 31 additions & 0 deletions admin/static/admin/js/file_formats.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/* global Option */
'use strict'

$(function () {
Yoda.call('vault_preservable_formats_lists').then((data) => {
$('#file-formats-list').html("<option value='' disabled selected>Select file format list to delete</option>")
for (const list in data) {
if (Object.prototype.hasOwnProperty.call(data, list)) {
$('#file-formats-list').append(new Option(data[list].name, list))
}
}
})

document.getElementById('upload-button').addEventListener('click', function () {
document.getElementById('file').click()
})

document.getElementById('file').addEventListener('change', function () {
if (this.files.length > 0) {
this.form.submit()
}
})

$('#file-formats-list').on('change', function () {
if ($(this).val()) {
$('#delete-format-button').prop('disabled', false)
} else {
$('#delete-format-button').prop('disabled', true)
}
})
})
34 changes: 33 additions & 1 deletion admin/templates/admin/admin.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
{% block scripts %}
<script src="{{ url_for('static', filename='lib/purify-3.1.7/js/purify.min.js') }}"></script>
<script src="{{ url_for('admin_bp.static', filename='js/create_preview.js') }}"></script>
<script src="{{ url_for('admin_bp.static', filename='js/file_formats.js') }}"></script>
{% endblock scripts %}

{% block content %}
Expand Down Expand Up @@ -63,7 +64,7 @@ <h2 class="card-title">Portal Theme</h2>
</div>

<!-- Publication Terms Section -->
<div>
<div class="mb-4">
<h2 class="card-title">Publication Terms</h2>
<div class="d-flex justify-content-start align-items-end">
<form action="{{ url_for('admin_bp.set_publication_terms') }}" method="post"
Expand All @@ -80,6 +81,37 @@ <h2 class="card-title">Publication Terms</h2>
</form>
</div>
</div>

<!-- Preservable file formats Section -->
<div class="mb-4">
<h2 class="card-title">Preservable file formats</h2>
<p>
Preservable file formats define how files are stored in Yoda for long-term accessibility and usability. Functional admin can also manage file format lists using iRODS commands. For more details, refer to the
<a href="https://utrechtuniversity.github.io/yoda/administration/installing-preservable-file-formats.html" target="_blank">technical documentation</a>.
</p>
<div class="mb-3">
<label>Current file format lists:</label>
<form action="{{ url_for('admin_bp.delete_file_formats') }}" method="post" class="flex-fill me-2 needs-validation" novalidate>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="mb-3">
<select class="form-control" id="file-formats-list" name="filename" required>
<option value="" disabled selected>Select file format list to delete</option>
</select>
</div>
<button type="submit" class="btn btn-danger" id="delete-format-button" disabled>Delete file format list</button>
</form>
</div>
<div>
<div class="mb-3">
<label>Upload new file format list:</label>
</div>
<form action="{{ url_for('admin_bp.upload_file_formats') }}" method="post" enctype="multipart/form-data" class="flex-fill me-2 needs-validation" novalidate>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<input type="file" name="file" id="file" class="d-none" required>
<button type="button" class="btn btn-primary" id="upload-button">Upload file format list</button>
</form>
</div>
</div>
</div>
</section>
</main>
Expand Down

0 comments on commit ba6449a

Please sign in to comment.