diff options
| author | Daniel Schadt <kingdread@gmx.de> | 2025-12-27 12:04:14 +0100 |
|---|---|---|
| committer | Daniel Schadt <kingdread@gmx.de> | 2025-12-30 19:16:32 +0100 |
| commit | 1d23ffd3ad68a880a0d63a8e90902f5536915cd3 (patch) | |
| tree | 1d879a2805b9dac494b07e927d944c3b77b200c6 | |
| parent | 1fa875181f7ab321bb1fad7a929f05dfcd083f17 (diff) | |
| download | fietsboek-1d23ffd3ad68a880a0d63a8e90902f5536915cd3.tar.gz fietsboek-1d23ffd3ad68a880a0d63a8e90902f5536915cd3.tar.bz2 fietsboek-1d23ffd3ad68a880a0d63a8e90902f5536915cd3.zip | |
check input validity in journey form
| -rw-r--r-- | fietsboek/templates/journey_form.jinja2 | 48 | ||||
| -rw-r--r-- | fietsboek/templates/journey_new.jinja2 | 2 | ||||
| -rw-r--r-- | fietsboek/views/journey.py | 32 |
3 files changed, 73 insertions, 9 deletions
diff --git a/fietsboek/templates/journey_form.jinja2 b/fietsboek/templates/journey_form.jinja2 index 6a5df1f..c641648 100644 --- a/fietsboek/templates/journey_form.jinja2 +++ b/fietsboek/templates/journey_form.jinja2 @@ -38,7 +38,10 @@ {% macro journey_form(journey) %} <div class="mb-3"> <label for="journeyTitle" class="form-label">{{ _("journeys.new.form.title") }}</label> - <input type="text" class="form-control" id="journeyTitle" name="journeyTitle" value="{{ journey.title }}"> + <input type="text" class="form-control" id="journeyTitle" name="journeyTitle" value="{{ journey.title }}" onchange="checkTitleValidity()"> + <div class="invalid-feedback"> + {{ _("journeys.new.form.requires_title") }} + </div> </div> <div class="mb-3"> <label for="journeyDescription" class="form-label">{{ _("journeys.new.form.description") }}</label> @@ -82,10 +85,15 @@ {{ util.hidden_csrf_input() }} -<button class="btn btn-primary" type="submit"> - <i class="bi bi-save"></i> - {{ _("journeys.new.form.submit") }} -</button> +<div> + <button class="btn btn-primary" type="submit" id="journeySubmit"> + <i class="bi bi-save"></i> + {{ _("journeys.new.form.submit") }} + </button> + <div class="invalid-feedback"> + {{ _("journeys.new.form.requires_tracks") }} + </div> +</div> <template id="queryResponse"> <div class="track-query-response"> @@ -152,6 +160,9 @@ function trDragEnd(event) { function removeTrackFromJourney(event) { let track = event.target.closest("div"); track.parentNode.removeChild(track); + + checkJourneyValidity(); + event.preventDefault(); } @@ -172,6 +183,9 @@ function addTrackToJourney(event) { document.getElementById("journeyTracks").appendChild(clone); track.parentElement.removeChild(track); + + checkJourneyValidity(); + event.preventDefault(); } @@ -214,6 +228,30 @@ function searchTracks() { }); } +function checkTitleValidity() { + let title = document.querySelector("#journeyTitle"); + if (title.value.length > 0) { + title.setCustomValidity(""); + } else { + title.setCustomValidity("title missing"); + } +} + +checkTitleValidity(); + +function checkJourneyValidity() { + let btn = document.querySelector("#journeySubmit"); + let track_count = document.querySelectorAll(".journey-track").length; + + if (track_count == 0) { + btn.setCustomValidity("no tracks"); + } else { + btn.setCustomValidity(""); + } +} + +checkJourneyValidity(); + document.querySelector("#trackSearchButton").addEventListener("click", (event) => { searchTracks(); event.preventDefault(); diff --git a/fietsboek/templates/journey_new.jinja2 b/fietsboek/templates/journey_new.jinja2 index 37fbb76..b1cfffb 100644 --- a/fietsboek/templates/journey_new.jinja2 +++ b/fietsboek/templates/journey_new.jinja2 @@ -9,7 +9,7 @@ <div class="container"> <h1>{{ _("journeys.new.title") }}</h1> - <form method="POST"> + <form method="POST" class="needs-validation" novalidate> {{ form.journey_form(none) }} </form> </div> diff --git a/fietsboek/views/journey.py b/fietsboek/views/journey.py index 28df29e..b763317 100644 --- a/fietsboek/views/journey.py +++ b/fietsboek/views/journey.py @@ -2,6 +2,7 @@ import io import logging from datetime import timedelta from pyramid.httpexceptions import HTTPBadRequest, HTTPFound +from pyramid.i18n import TranslationString as _ from pyramid.request import Request from pyramid.response import Response from pyramid.view import view_config @@ -9,7 +10,8 @@ from sqlalchemy import select from .. import trackmap, util from ..models.journey import Journey, Visibility -from ..models.track import TrackWithMetadata +from ..models.track import Track, TrackWithMetadata +from ..models import User from .tileproxy import ITileRequester LOGGER = logging.getLogger(__name__) @@ -100,7 +102,7 @@ def do_journey_new(request: Request): request.dbsession.add(journey) request.dbsession.flush() - track_ids = [int(tid) for tid in request.params.getall("journeyTrack[]")] + track_ids = _extract_valid_tracks(request) journey.set_track_ids(track_ids) return HTTPFound(request.route_url("journey-details", journey_id=journey.id)) @@ -129,9 +131,33 @@ def do_journey_edit(request: Request): journey.title = request.params.get("journeyTitle") journey.description = request.params.get("journeyDescription") - track_ids = [int(tid) for tid in request.params.getall("journeyTrack[]")] + track_ids = _extract_valid_tracks(request) journey.set_track_ids(track_ids) request.dbsession.add(journey) return HTTPFound(request.route_url("journey-details", journey_id=journey.id)) + + +def _extract_valid_tracks(request: Request) -> list[int]: + user: User = request.identity + + if not request.params.get("journeyTitle"): + raise HTTPBadRequest("Needs a title") + + try: + track_ids = [int(tid) for tid in request.params.getall("journeyTrack[]")] + except ValueError: + # Shouldn't happen if users don't tamper with the requests manually, so we don't translate + raise HTTPBadRequest("Invalid track ID") + + if not track_ids: + raise HTTPBadRequest("No track IDs given") + + for track_id in track_ids: + query = select(Track).filter_by(id=track_id) + track = request.dbsession.execute(query).one_or_none() + if track is None: + raise HTTPBadRequest("Invalid track ID") + + return track_ids |
