aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Schadt <kingdread@gmx.de>2025-12-26 00:13:52 +0100
committerDaniel Schadt <kingdread@gmx.de>2025-12-30 19:16:32 +0100
commit4f41f3bc47746d867feedbd4ab16d8a6b53fd4d2 (patch)
tree2172920a8d237d723d66f0b5f5734368e340f93a
parent0c00bc442eabe4e42617ca2cf281496f871b3590 (diff)
downloadfietsboek-4f41f3bc47746d867feedbd4ab16d8a6b53fd4d2.tar.gz
fietsboek-4f41f3bc47746d867feedbd4ab16d8a6b53fd4d2.tar.bz2
fietsboek-4f41f3bc47746d867feedbd4ab16d8a6b53fd4d2.zip
implement journey editing
-rw-r--r--fietsboek/models/journey.py2
-rw-r--r--fietsboek/models/track.py4
-rw-r--r--fietsboek/routes.py3
-rw-r--r--fietsboek/templates/journey_details.jinja253
-rw-r--r--fietsboek/templates/journey_edit.jinja221
-rw-r--r--fietsboek/templates/journey_form.jinja2222
-rw-r--r--fietsboek/templates/journey_new.jinja2203
-rw-r--r--fietsboek/views/journey.py33
8 files changed, 342 insertions, 199 deletions
diff --git a/fietsboek/models/journey.py b/fietsboek/models/journey.py
index c403a61..512146f 100644
--- a/fietsboek/models/journey.py
+++ b/fietsboek/models/journey.py
@@ -137,6 +137,8 @@ class Journey(Base):
:param track_ids: The IDs of the tracks that should be in this journey.
"""
session = inspect(self).session
+ stmt = delete(journey_track_assoc).where(journey_track_assoc.c.journey_id == self.id)
+ session.execute(stmt)
for index, track_id in enumerate(track_ids, 1):
stmt = insert(journey_track_assoc).values(
journey_id=self.id,
diff --git a/fietsboek/models/track.py b/fietsboek/models/track.py
index cd99f4c..fc5a68b 100644
--- a/fietsboek/models/track.py
+++ b/fietsboek/models/track.py
@@ -581,6 +581,10 @@ class Track(Base):
self.cache.start_time = self.date
self.cache.end_time = self.date + datetime.timedelta(seconds=meta.duration)
+ def with_metadata(self) -> "TrackWithMetadata":
+ """Returns this track with attached path metadata."""
+ return TrackWithMetadata(self)
+
def text_tags(self):
"""Returns a set of textual tags.
diff --git a/fietsboek/routes.py b/fietsboek/routes.py
index bafb4b3..c7bce62 100644
--- a/fietsboek/routes.py
+++ b/fietsboek/routes.py
@@ -65,6 +65,9 @@ def includeme(config):
)
config.add_route("journey-gpx", "/journey/{journey_id}/gpx", factory="fietsboek.models.Journey.factory")
config.add_route("journey-details", "/journey/{journey_id}/", factory="fietsboek.models.Journey.factory")
+ config.add_route("journey-edit", "/journey/{journey_id}/edit", factory="fietsboek.models.Journey.factory")
+ config.add_route("journey-invalidate-share", "/journey/{journey_id}/invalidate-link", factory="fietsboek.models.Journey.factory")
+ config.add_route("delete-journey", "/journey/{journey_id}/delete", factory="fietsboek.models.Journey.factory")
config.add_route("journey-new", "/journey/new")
config.add_route("badge", "/badge/{badge_id}", factory="fietsboek.models.Badge.factory")
diff --git a/fietsboek/templates/journey_details.jinja2 b/fietsboek/templates/journey_details.jinja2
index 624f78c..bc8137e 100644
--- a/fietsboek/templates/journey_details.jinja2
+++ b/fietsboek/templates/journey_details.jinja2
@@ -1,8 +1,61 @@
{% extends "layout.jinja2" %}
+{% import "util.jinja2" as util with context %}
+
{% block content %}
<div class="container">
<h1>{{ journey.title }}</h1>
+ {% if show_edit_link %}
+ <div class="btn-group mb-3" role="group">
+ <a class="btn btn-success ui-element" href="{{ request.route_path('journey-edit', journey_id=journey.id) }}"><i class="bi-pencil-square"></i> {{ _("journey.edit") }}</a>
+ <button type="button" class="btn btn-info ui-element" id="showShareLink" data-bs-toggle="modal" data-bs-target="#shareLinkModal"><i class="bi-share"></i> {{ _("journey.share") }}</button>
+ <button type="button" class="btn btn-danger ui-element" id="deleteLink" data-bs-toggle="modal" data-bs-target="#deleteModal"><i class="bi bi-trash"></i> {{ _("journey.delete") }}</button>
+ </div>
+ <div class="modal fade" id="shareLinkModal" tabindex="-1" aria-hidden="true">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <div class="modal-header">
+ <h5 class="modal-title">{{ _("journey.sharelink.title") }}</h5>
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
+ </div>
+ <div class="modal-body">
+ <p>{{ _("journey.sharelink.info") }}</p>
+ {% set share_link = request.route_url('journey-details', journey_id=journey.id, _query=[("secret", journey.link_secret)]) %}
+ <a href="{{ share_link }}">{{ share_link }}</a>
+ </div>
+ <div class="modal-footer">
+ <form method="POST" action="{{ request.route_url('journey-invalidate-share', journey_id=journey.id) }}">
+ {{ util.hidden_csrf_input() }}
+ <button type="submit" class="btn btn-warning ui-element">{{ _("journey.sharelink.invalidate") }}</button>
+ </form>
+ <button type="button" class="btn btn-secondary ui-element" data-bs-dismiss="modal">{{ _("journey.sharelink.close") }}</button>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div class="modal fade" id="deleteModal" tabindex="-1" aria-hidden="true">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <div class="modal-header">
+ <h5 class="modal-title">{{ _("journey.delete.title") }}</h5>
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
+ </div>
+ <div class="modal-body">
+ <p>{{ _("journey.delete.info") }}</p>
+ </div>
+ <div class="modal-footer">
+ <form method="POST" action="{{ request.route_url('delete-journey', journey_id=journey.id) }}">
+ {{ util.hidden_csrf_input() }}
+ <button type="submit" class="btn btn-danger ui-element">{{ _("journey.delete.delete") }}</button>
+ </form>
+ <button type="button" class="btn btn-secondary ui-element" data-bs-dismiss="modal">{{ _("journey.delete.close") }}</button>
+ </div>
+ </div>
+ </div>
+ </div>
+ {% endif %}
+
{% set gpx_url = request.route_path("journey-gpx", journey_id=journey.id) %}
<div id="mainmap" class="gpxview:{{ gpx_url }}:OSM" style="width:100%;height:600px">
<noscript><p>{{ _("page.noscript") }}<p></noscript>
diff --git a/fietsboek/templates/journey_edit.jinja2 b/fietsboek/templates/journey_edit.jinja2
new file mode 100644
index 0000000..fa39228
--- /dev/null
+++ b/fietsboek/templates/journey_edit.jinja2
@@ -0,0 +1,21 @@
+{% extends "layout.jinja2" %}
+{% import "journey_form.jinja2" as form with context %}
+
+{% block extrahead %}
+{{ form.journey_css() }}
+{% endblock %}
+
+{% block content %}
+<div class="container">
+ <h1>{{ journey.title }}</h1>
+
+ <form method="POST">
+ {{ form.journey_form(journey) }}
+ </form>
+</div>
+
+{% endblock %}
+
+{% block latescripts %}
+{{ form.journey_js() }}
+{% endblock %}
diff --git a/fietsboek/templates/journey_form.jinja2 b/fietsboek/templates/journey_form.jinja2
new file mode 100644
index 0000000..6a5df1f
--- /dev/null
+++ b/fietsboek/templates/journey_form.jinja2
@@ -0,0 +1,222 @@
+{% import "util.jinja2" as util with context %}
+
+{% macro journey_css() %}
+<style>
+.track-query-response, .journey-track {
+ background-color: var(--bs-body-bg);
+ padding: 0.375rem;
+ margin-bottom: 0.1rem;
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+
+ .track-title {
+ font-weight: 450;
+ font-size: 110%;
+ }
+
+ .track-date {
+ color: #808080;
+ }
+
+ .track-length {
+ color: #808080;
+ }
+}
+
+.journey-track {
+ cursor: grab;
+}
+
+.dragging {
+ opacity: 0.7;
+}
+</style>
+{% endmacro %}
+
+
+{% 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 }}">
+</div>
+<div class="mb-3">
+ <label for="journeyDescription" class="form-label">{{ _("journeys.new.form.description") }}</label>
+ <textarea class="form-control" id="journeyDescription" name="journeyDescription">{{ journey.description }}</textarea>
+</div>
+<div class="mb-3">
+ <label for="journeyVisibility" class="form-label">{{ _("journeys.new.form.visibility") }}</label>
+ <select class="form-select" id="journeyVisibility" name="journeyVisibility">
+ {% set visibility = journey.visibility.name if journey else "" %}
+ <option value="PRIVATE"{% if visibility== "PRIVATE" %} selected{% endif %}>{{ _("journeys.new.form.visibility.private") }}</option>
+ <option value="FRIENDS"{% if visibility== "FRIENDS" %} selected{% endif %}>{{ _("journeys.new.form.visibility.friends") }}</option>
+ <option value="FRIENDS_TAGGED"{% if visibility== "FRIENDS_TAGGED" %} selected{% endif %}>{{ _("journeys.new.form.visibility.friends_tagged") }}</option>
+ <option value="LOGGED_IN"{% if visibility== "LOGGED_IN" %} selected{% endif %}>{{ _("journeys.new.form.visibility.logged_in") }}</option>
+ <option value="PUBLIC"{% if visibility== "PUBLIC" %} selected{% endif %}>{{ _("journeys.new.form.visibility.public") }}</option>
+ </select>
+</div>
+<div class="mb-3">
+ <p>
+ {{ _("journeys.new.form.tracksearch") }}
+ </p>
+ <div class="input-group">
+ <input type="text" id="trackSearch" placeholder="Title" class="form-control">
+ <button class="btn btn-secondary" id="trackSearchButton"><i class="bi bi-search"></i></button>
+ </div>
+</div>
+<div class="mb-3" id="trackSearchResults"></div>
+<div class="mb-3">
+ <p>{{ _("journeys.new.form.tracks") }}<p>
+</div>
+<div class="mb-3" id="journeyTracks">
+ {% for track in journey.tracks %}
+ <div class="journey-track" draggable="true">
+ <input type="hidden" name="journeyTrack[]" value="{{ track.id }}">
+ <button class="btn btn-danger btn-sm"><i class="bi bi-x-circle-fill"></i></button>
+ <div class="track-title">{{ track.title }}</div>
+ <div class="track-length">{{ (track.with_metadata().length / 1000) | round(2) }} km</div>
+ <div class="track-date">{{ track.date | format_datetime }}</div>
+ </div>
+ {% endfor %}
+</div>
+
+{{ util.hidden_csrf_input() }}
+
+<button class="btn btn-primary" type="submit">
+ <i class="bi bi-save"></i>
+ {{ _("journeys.new.form.submit") }}
+</button>
+
+<template id="queryResponse">
+ <div class="track-query-response">
+ <button class="btn btn-success btn-sm"><i class="bi bi-plus-square-fill"></i></button>
+ <div class="track-title"></div>
+ <div class="track-length"></div>
+ <div class="track-date"></div>
+ </div>
+</template>
+
+<template id="journeyTrack">
+ <div class="journey-track" draggable="true">
+ <input type="hidden" name="journeyTrack[]">
+ <button class="btn btn-danger btn-sm"><i class="bi bi-x-circle-fill"></i></button>
+ <div class="track-title"></div>
+ <div class="track-length"></div>
+ <div class="track-date"></div>
+ </div>
+</template>
+{% endmacro %}
+
+
+{% macro journey_js() %}
+<script>
+// Make sure the mouse pointer stays "grab", even when leaving the list of
+// tracks.
+document.addEventListener("dragover", (event) => event.preventDefault());
+
+let trDrag;
+
+function trDragStart(event) {
+ trDrag = event.target;
+ event.target.closest(".journey-track").classList.add("dragging");
+ event.dataTransfer.effectAllowed = "move";
+}
+
+function trDragOver(event) {
+ let target = event.target.closest(".journey-track");
+
+ // Check whether we are in the top of bottom half of the element
+ let rect = target.getBoundingClientRect();
+ let is_top_half = event.clientY < rect.top + rect.height / 2;
+
+ if (is_top_half) {
+ target.insertAdjacentElement("beforebegin", trDrag);
+ } else {
+ target.insertAdjacentElement("afterend", trDrag);
+ }
+ event.preventDefault();
+}
+
+function trDragLeave(event) {
+ let target = event.target.closest(".journey-track");
+ target.style.marginTop = "";
+ target.style.marginBottom = "";
+ event.preventDefault();
+}
+
+function trDragEnd(event) {
+ trDrag.closest(".journey-track").classList.remove("dragging");
+ trDrag = null;
+}
+
+function removeTrackFromJourney(event) {
+ let track = event.target.closest("div");
+ track.parentNode.removeChild(track);
+ event.preventDefault();
+}
+
+function addTrackToJourney(event) {
+ let track = event.target.closest("div");
+ let template = document.getElementById("journeyTrack");
+ let clone = document.importNode(template.content, true);
+
+ clone.querySelector("input").setAttribute("value", track.getAttribute("data-track-id"));
+ for (let sel of [".track-title", ".track-length", ".track-date"]) {
+ clone.querySelector(sel).textContent = track.querySelector(sel).textContent;
+ }
+ clone.querySelector("button").addEventListener("click", removeTrackFromJourney);
+ clone.querySelector(".journey-track").addEventListener("dragstart", trDragStart);
+ clone.querySelector(".journey-track").addEventListener("dragover", trDragOver);
+ clone.querySelector(".journey-track").addEventListener("dragleave", trDragLeave);
+ clone.querySelector(".journey-track").addEventListener("dragend", trDragEnd);
+
+ document.getElementById("journeyTracks").appendChild(clone);
+ track.parentElement.removeChild(track);
+ event.preventDefault();
+}
+
+addHandler(".journey-track", "dragstart", trDragStart);
+addHandler(".journey-track", "dragover", trDragOver);
+addHandler(".journey-track", "dragleave", trDragLeave);
+addHandler(".journey-track", "dragend", trDragEnd);
+
+function hasTrack(id) {
+ for (let track of document.querySelectorAll(".journey-track")) {
+ let tid = track.querySelector("input").value;
+ if (parseInt(tid) == id) {
+ return true;
+ }
+ }
+ return false;
+}
+
+function searchTracks() {
+ let template = document.getElementById("queryResponse");
+ let results = document.getElementById("trackSearchResults");
+ let pattern = document.getElementById("trackSearch").value;
+ let url = makeUrl(`/track/?format=json&search-terms=${encodeURIComponent(pattern)}`);
+ fetch(url)
+ .then((response) => response.json())
+ .then((response) => {
+ results.replaceChildren();
+ for (let track of response) {
+ if (hasTrack(track.id)) {
+ continue;
+ }
+ let clone = document.importNode(template.content, true);
+ clone.firstElementChild.setAttribute("data-track-id", track.id);
+ clone.querySelector(".track-title").textContent = track.title;
+ clone.querySelector(".track-date").textContent = formatTimestamp(track.date * 1000);
+ clone.querySelector(".track-length").textContent = `${(track.length / 1000).toFixed(2)} km`;
+ clone.querySelector("button").addEventListener("click", addTrackToJourney);
+ results.appendChild(clone);
+ }
+ });
+}
+
+document.querySelector("#trackSearchButton").addEventListener("click", (event) => {
+ searchTracks();
+ event.preventDefault();
+});
+</script>
+{% endmacro %}
diff --git a/fietsboek/templates/journey_new.jinja2 b/fietsboek/templates/journey_new.jinja2
index 9e8bb5c..37fbb76 100644
--- a/fietsboek/templates/journey_new.jinja2
+++ b/fietsboek/templates/journey_new.jinja2
@@ -1,38 +1,8 @@
{% extends "layout.jinja2" %}
-{% import "util.jinja2" as util with context %}
+{% import "journey_form.jinja2" as form with context %}
{% block extrahead %}
-<style>
-.track-query-response, .journey-track {
- background-color: var(--bs-body-bg);
- padding: 0.375rem;
- margin-bottom: 0.1rem;
- display: flex;
- align-items: center;
- gap: 1rem;
-
- .track-title {
- font-weight: 450;
- font-size: 110%;
- }
-
- .track-date {
- color: #808080;
- }
-
- .track-length {
- color: #808080;
- }
-}
-
-.journey-track {
- cursor: grab;
-}
-
-.dragging {
- opacity: 0.7;
-}
-</style>
+{{ form.journey_css() }}
{% endblock %}
{% block content %}
@@ -40,177 +10,12 @@
<h1>{{ _("journeys.new.title") }}</h1>
<form method="POST">
- <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">
- </div>
- <div class="mb-3">
- <label for="journeyDescription" class="form-label">{{ _("journeys.new.form.description") }}</label>
- <textarea class="form-control" id="journeyDescription" name="journeyDescription"></textarea>
- </div>
- <div class="mb-3">
- <label for="journeyVisibility" class="form-label">{{ _("journeys.new.form.visibility") }}</label>
- <select class="form-select" id="journeyVisibility" name="journeyVisibility">
- <option value="PRIVATE"{% if visibility== "PRIVATE" %} selected{% endif %}>{{ _("journeys.new.form.visibility.private") }}</option>
- <option value="FRIENDS"{% if visibility== "FRIENDS" %} selected{% endif %}>{{ _("journeys.new.form.visibility.friends") }}</option>
- <option value="FRIENDS_TAGGED"{% if visibility== "FRIENDS_TAGGED" %} selected{% endif %}>{{ _("journeys.new.form.visibility.friends_tagged") }}</option>
- <option value="LOGGED_IN"{% if visibility== "LOGGED_IN" %} selected{% endif %}>{{ _("journeys.new.form.visibility.logged_in") }}</option>
- <option value="PUBLIC"{% if visibility== "PUBLIC" %} selected{% endif %}>{{ _("journeys.new.form.visibility.public") }}</option>
- </select>
- </div>
- <div class="mb-3">
- <p>
- {{ _("journeys.new.form.tracksearch") }}
- </p>
- <div class="input-group">
- <input type="text" id="trackSearch" placeholder="Title" class="form-control">
- <button class="btn btn-secondary" id="trackSearchButton"><i class="bi bi-search"></i></button>
- </div>
- </div>
- <div class="mb-3" id="trackSearchResults"></div>
- <div class="mb-3">
- <p>{{ _("journeys.new.form.tracks") }}<p>
- </div>
- <div class="mb-3" id="journeyTracks"></div>
-
- {{ util.hidden_csrf_input() }}
-
- <button class="btn btn-primary" type="submit">
- <i class="bi bi-save"></i>
- {{ _("journeys.new.form.submit") }}
- </button>
+ {{ form.journey_form(none) }}
</form>
</div>
-<template id="queryResponse">
- <div class="track-query-response">
- <button class="btn btn-success btn-sm"><i class="bi bi-plus-square-fill"></i></button>
- <div class="track-title"></div>
- <div class="track-length"></div>
- <div class="track-date"></div>
- </div>
-</template>
-
-<template id="journeyTrack">
- <div class="journey-track" draggable="true">
- <input type="hidden" name="journeyTrack[]">
- <button class="btn btn-danger btn-sm"><i class="bi bi-x-circle-fill"></i></button>
- <div class="track-title"></div>
- <div class="track-length"></div>
- <div class="track-date"></div>
- </div>
-</template>
{% endblock %}
{% block latescripts %}
- <script>
- // Make sure the mouse pointer stays "grab", even when leaving the list of
- // tracks.
- document.addEventListener("dragover", (event) => event.preventDefault());
-
- let trDrag;
-
- function trDragStart(event) {
- trDrag = event.target;
- event.target.closest(".journey-track").classList.add("dragging");
- event.dataTransfer.effectAllowed = "move";
- }
-
- function trDragOver(event) {
- let target = event.target.closest(".journey-track");
-
- // Check whether we are in the top of bottom half of the element
- let rect = target.getBoundingClientRect();
- let is_top_half = event.clientY < rect.top + rect.height / 2;
-
- if (is_top_half) {
- target.insertAdjacentElement("beforebegin", trDrag);
- } else {
- target.insertAdjacentElement("afterend", trDrag);
- }
- event.preventDefault();
- }
-
- function trDragLeave(event) {
- let target = event.target.closest(".journey-track");
- target.style.marginTop = "";
- target.style.marginBottom = "";
- event.preventDefault();
- }
-
- function trDragEnd(event) {
- trDrag.closest(".journey-track").classList.remove("dragging");
- trDrag = null;
- }
-
- function removeTrackFromJourney(event) {
- let track = event.target.closest("div");
- track.parentNode.removeChild(track);
- event.preventDefault();
- }
-
- function addTrackToJourney(event) {
- let track = event.target.closest("div");
- let template = document.getElementById("journeyTrack");
- let clone = document.importNode(template.content, true);
-
- clone.querySelector("input").setAttribute("value", track.getAttribute("data-track-id"));
- for (let sel of [".track-title", ".track-length", ".track-date"]) {
- clone.querySelector(sel).textContent = track.querySelector(sel).textContent;
- }
- clone.querySelector("button").addEventListener("click", removeTrackFromJourney);
- clone.querySelector(".journey-track").addEventListener("dragstart", trDragStart);
- clone.querySelector(".journey-track").addEventListener("dragover", trDragOver);
- clone.querySelector(".journey-track").addEventListener("dragleave", trDragLeave);
- clone.querySelector(".journey-track").addEventListener("dragend", trDragEnd);
-
- document.getElementById("journeyTracks").appendChild(clone);
- track.parentElement.removeChild(track);
- event.preventDefault();
- }
-
- addHandler(".journey-track", "dragstart", trDragStart);
- addHandler(".journey-track", "dragover", trDragOver);
- addHandler(".journey-track", "dragleave", trDragLeave);
- addHandler(".journey-track", "dragend", trDragEnd);
-
- function hasTrack(id) {
- for (let track of document.querySelectorAll(".journey-track")) {
- let tid = track.querySelector("input").value;
- if (parseInt(tid) == id) {
- return true;
- }
- }
- return false;
- }
-
- function searchTracks() {
- let template = document.getElementById("queryResponse");
- let results = document.getElementById("trackSearchResults");
- let pattern = document.getElementById("trackSearch").value;
- let url = makeUrl(`/track/?format=json&search-terms=${encodeURIComponent(pattern)}`);
- fetch(url)
- .then((response) => response.json())
- .then((response) => {
- results.replaceChildren();
- for (let track of response) {
- if (hasTrack(track.id)) {
- continue;
- }
- let clone = document.importNode(template.content, true);
- clone.firstElementChild.setAttribute("data-track-id", track.id);
- clone.querySelector(".track-title").textContent = track.title;
- clone.querySelector(".track-date").textContent = formatTimestamp(track.date * 1000);
- clone.querySelector(".track-length").textContent = `${(track.length / 1000).toFixed(2)} km`;
- clone.querySelector("button").addEventListener("click", addTrackToJourney);
- results.appendChild(clone);
- }
- });
- }
-
- document.querySelector("#trackSearchButton").addEventListener("click", (event) => {
- searchTracks();
- event.preventDefault();
- });
- </script>
+{{ form.journey_js() }}
{% endblock %}
diff --git a/fietsboek/views/journey.py b/fietsboek/views/journey.py
index ee1a44e..2b14c22 100644
--- a/fietsboek/views/journey.py
+++ b/fietsboek/views/journey.py
@@ -35,12 +35,14 @@ def journey_details(request: Request):
journey: Journey = request.context
tracks = [TrackWithMetadata(track) for track in journey.tracks]
movement_data = journey.path().movement_data()
+ show_edit_link = request.identity == journey.owner
return {
"journey": journey,
"tracks": tracks,
"movement_data": movement_data,
"mps_to_kph": util.mps_to_kph,
"md_to_html": util.safe_markdown,
+ "show_edit_link": show_edit_link,
}
@@ -100,3 +102,34 @@ def do_journey_new(request: Request):
journey.set_track_ids(track_ids)
return HTTPFound(request.route_url("journey-details", journey_id=journey.id))
+
+
+@view_config(
+ route_name="journey-edit",
+ renderer="fietsboek:templates/journey_edit.jinja2",
+ permission="journey.edit",
+)
+def journey_edit(request: Request):
+ journey: Journey = request.context
+ return {
+ "journey": journey,
+ }
+
+
+@view_config(
+ route_name="journey-edit",
+ permission="journey.edit",
+ request_method="POST",
+)
+def do_journey_edit(request: Request):
+ journey: Journey = request.context
+
+ journey.title = request.params.get("journeyTitle")
+ journey.description = request.params.get("journeyDescription")
+
+ track_ids = [int(tid) for tid in request.params.getall("journeyTrack[]")]
+ journey.set_track_ids(track_ids)
+
+ request.dbsession.add(journey)
+
+ return HTTPFound(request.route_url("journey-details", journey_id=journey.id))