Skip to content

Commit

Permalink
check edit media form validity before allowing changes to be saved
Browse files Browse the repository at this point in the history
This commit adds client-side JavaScript code that checks the edit-media
form as the user is changing the form. It then performs a server-side
check that the provided values are valid. If they are not valid, the
client-side code updates the form to show the errors and disables the
submit button.

This stops the user from creating new media files that will fail the
validation checks.
  • Loading branch information
Alex Ashley authored and asrashley committed Sep 26, 2024
1 parent 885ab23 commit 503adc2
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 1 deletion.
48 changes: 47 additions & 1 deletion dashlive/server/requesthandler/media_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

import flask
from flask_login import current_user
from langcodes import tag_is_valid
from werkzeug.datastructures import FileStorage

from dashlive.mpeg import mp4
Expand All @@ -47,7 +48,7 @@
)
from .exceptions import CsrfFailureException
from .manifest_context import ManifestContext
from .utils import is_ajax, jsonify
from .utils import is_ajax, jsonify, jsonify_no_content

class UploadHandler(RequestHandlerBase):
decorators = [uses_stream, login_required(permission=models.Group.MEDIA)]
Expand Down Expand Up @@ -562,3 +563,48 @@ def filter_atom(self, atom: JsonObject) -> None:
else:
for dsc in atom['descriptors']:
self.filter_object(dsc)


class ValidateMediaChanges(HTMLHandlerBase):
"""
Handler used for form validation when editing a media file
"""
decorators = [
uses_media_file,
uses_stream,
login_required(permission=models.Group.MEDIA),
]

def post(self, spk: int, mfid: int) -> flask.Response:
if not is_ajax():
return flask.make_response('Invalid request', 400)
data = flask.request.json
try:
lang = data['lang']
track_id = int(data['track_id'], 10)
except (KeyError, ValueError):
return jsonify_no_content(400)
errors: dict[str, str] = {
"lang": '',
"track_id": '',
}
if not tag_is_valid(lang):
errors["lang"] = f'"{lang}" is not a valid BCP-47 language tag'
codecs: set(str) = set()
if current_media_file.codec_fourcc is not None:
codecs.add(current_media_file.codec_fourcc)
for item in models.MediaFile.search(
stream_pk=current_media_file.stream_pk, track_id=track_id):
if item.codec_fourcc is not None:
codecs.add(item.codec_fourcc)
if len(codecs) > 1:
codecs.remove(current_media_file.codec_fourcc)
track_names: list[str] = []
for item in models.MediaFile.search(
stream_pk=current_media_file.stream_pk, track_id=track_id):
if item.codec_fourcc in codecs:
track_names.append(f'"{item.name}"')
errors['track_id'] = (
f'Track ID {track_id} is already in use for {" ".join(codecs)} ' +
f'tracks {", ".join(track_names)}')
return jsonify(dict(errors=errors))
5 changes: 5 additions & 0 deletions dashlive/server/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,11 @@ def __str__(self) -> str:
handler='media_management.EditMedia',
title='Edit Media',
parent='list-streams'),
"check-media-changes": Route(
r'/stream/<int:spk>/<int:mfid>/validate',
handler='media_management.ValidateMediaChanges',
title='Edit Media',
parent='list-streams'),
"delete-media": Route(
r'/stream/<int:spk>/<int:mfid>/delete',
handler='media_management.DeleteMedia',
Expand Down
72 changes: 72 additions & 0 deletions static/js/edit_media.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/* globals console */
import $ from '/libs/jquery.js';
import { enableTooltips } from './tooltips.js';

async function checkFormValues() {
const form = document.getElementById('edit_media');
if (form === null) {
console.error('Failed to find edit media form');
return;
}
const url = $(form).data('validate-url');
if (!url) {
console.error('Failed to find URL for form validation');
return;
}
const data = Object.fromEntries(
$(form).serializeArray().
filter(item => item.name !== 'csrf_token').
map(item => [item.name, item.value,]));
$('form').addClass('needs-validation').removeClass('was-validated');
const result = await fetch(url, {
body: JSON.stringify(data),
cache: 'no-cache',
credentials: 'same-origin',
headers: {
"Content-Type": "application/json",
},
method: 'POST',
mode: 'same-origin',
});
if (!result.ok) {
console.error(`Failed to check form validitity: ${ result.status }`);
return;
}
const { errors } = await result.json();
$('#edit_media input').each((_index, elt) => {
const $elt = $(elt);
const name = $elt.attr('name');
const parent = $elt.parent();
const feedback = parent.find('.invalid-feedback');
const err = errors[name] || '';
if (err === '') {
$elt.addClass('is-valid').removeClass('is-invalid');
} else {
$elt.removeClass('is-valid').addClass('is-invalid');
}
feedback.text(err);
elt.setCustomValidity(err);
});
$('form').removeClass('needs-validation').addClass('was-validated');
const submit = $('#edit_media button[type="submit"]');
if (form.checkValidity()) {
submit.attr('disabled', false);
} else {
submit.attr('disabled', 'disabled');
}
}

function setupValidation() {
$('#edit_media input').on('change', checkFormValues);

$('#edit_media input').on('invalid', (ev) => {
$(ev.target).removeClass('is-valid').addClass('is-invalid');
});

$('#edit_media input').on('valid', (ev) => {
$(ev.target).addClass('is-valid').removeClass('is-invalid');
});
}

enableTooltips();
setupValidation();
2 changes: 2 additions & 0 deletions templates/edit-model.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
{%- if validation %}
class="{{ validation }}"
{% endif -%}
{% block form_element %}
{% endblock %}
>
{% if error %}
<div class="alert alert-danger" role="alert">{{error}}</div>
Expand Down
8 changes: 8 additions & 0 deletions templates/media/edit_media.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,11 @@
class="btn btn-danger delete-file"
data-id="{{model.pk}}">Delete file &quot;{{model.name}}&quot;</a>
{% endblock %}

{% block form_element %}
data-validate-url="{{url_for('check-media-changes', spk=model.stream.pk, mfid=model.pk)}}"
{% endblock %}

{% block extrascripts %}
<script src="{{url_for('static', filename='js/edit_media.js')}}" type="module" ></script>
{% endblock %}

0 comments on commit 503adc2

Please sign in to comment.