aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Schadt <kingdread@gmx.de>2025-12-27 12:04:14 +0100
committerDaniel Schadt <kingdread@gmx.de>2025-12-30 19:16:32 +0100
commit1d23ffd3ad68a880a0d63a8e90902f5536915cd3 (patch)
tree1d879a2805b9dac494b07e927d944c3b77b200c6
parent1fa875181f7ab321bb1fad7a929f05dfcd083f17 (diff)
downloadfietsboek-1d23ffd3ad68a880a0d63a8e90902f5536915cd3.tar.gz
fietsboek-1d23ffd3ad68a880a0d63a8e90902f5536915cd3.tar.bz2
fietsboek-1d23ffd3ad68a880a0d63a8e90902f5536915cd3.zip
check input validity in journey form
-rw-r--r--fietsboek/templates/journey_form.jinja248
-rw-r--r--fietsboek/templates/journey_new.jinja22
-rw-r--r--fietsboek/views/journey.py32
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