aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Schadt <kingdread@gmx.de>2025-12-30 21:58:15 +0100
committerDaniel Schadt <kingdread@gmx.de>2025-12-30 21:58:15 +0100
commit5a71f663371381d47ad9da6b9439fcba649cc5b1 (patch)
tree2bd5b8dcc61e3b2def991cf37c3a709130cbcfa5
parentfb5eebc473fa4d1ce9adf70fe7afcc9b3ae8a6b6 (diff)
downloadfietsboek-5a71f663371381d47ad9da6b9439fcba649cc5b1.tar.gz
fietsboek-5a71f663371381d47ad9da6b9439fcba649cc5b1.tar.bz2
fietsboek-5a71f663371381d47ad9da6b9439fcba649cc5b1.zip
fix lint
-rw-r--r--fietsboek/models/__init__.py2
-rw-r--r--fietsboek/models/journey.py43
-rw-r--r--fietsboek/models/track.py2
-rw-r--r--fietsboek/models/user.py15
-rw-r--r--fietsboek/routes.py26
-rw-r--r--fietsboek/views/journey.py51
6 files changed, 107 insertions, 32 deletions
diff --git a/fietsboek/models/__init__.py b/fietsboek/models/__init__.py
index 74cf145..2130901 100644
--- a/fietsboek/models/__init__.py
+++ b/fietsboek/models/__init__.py
@@ -11,8 +11,8 @@ from sqlalchemy.orm import configure_mappers, sessionmaker
from .badge import Badge # flake8: noqa
from .comment import Comment # flake8: noqa
from .image import ImageMetadata # flake8: noqa
-from .track import Tag, Track, TrackCache, Upload, Waypoint # flake8: noqa
from .journey import Journey
+from .track import Tag, Track, TrackCache, Upload, Waypoint # flake8: noqa
# Import or define all models here to ensure they are attached to the
# ``Base.metadata`` prior to any initialization routines.
diff --git a/fietsboek/models/journey.py b/fietsboek/models/journey.py
index 89bf0d2..5b75878 100644
--- a/fietsboek/models/journey.py
+++ b/fietsboek/models/journey.py
@@ -1,13 +1,16 @@
-import datetime
+"""Journey model definition.
+
+A Journey is an ordered collection of tracks, with a title and (optionally) a description.
+"""
+
import dataclasses
-import io
-import logging
+import datetime
import enum
-from typing import Self, TYPE_CHECKING
+import logging
+from typing import TYPE_CHECKING, Self
+
from pyramid.authorization import (
ALL_PERMISSIONS,
- ACLAllowed,
- ACLHelper,
Allow,
Authenticated,
Everyone,
@@ -16,12 +19,9 @@ from pyramid.httpexceptions import HTTPNotFound
from pyramid.request import Request
from sqlalchemy import (
Column,
- DateTime,
Enum,
- Float,
ForeignKey,
Integer,
- LargeBinary,
Table,
Text,
delete,
@@ -31,7 +31,7 @@ from sqlalchemy import (
)
from sqlalchemy.orm import Mapped, mapped_column, relationship
-from .. import geo, util
+from .. import geo
from .meta import Base
if TYPE_CHECKING:
@@ -67,6 +67,8 @@ journey_track_assoc = Table(
class Journey(Base):
+ """A :class:`Journey` represents a collection of tracks, with a title and description."""
+
__tablename__ = "journeys"
id: Mapped[int] = mapped_column(Integer, primary_key=True)
owner_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), nullable=False)
@@ -111,7 +113,13 @@ class Journey(Base):
(
Allow,
f"user:{self.owner_id}",
- ["journey.view", "journey.edit", "journey.unshare", "journey.comment", "journey.delete"],
+ [
+ "journey.view",
+ "journey.edit",
+ "journey.unshare",
+ "journey.comment",
+ "journey.delete",
+ ],
),
(Allow, f"secret:{self.link_secret}", "journey.view"),
]
@@ -154,21 +162,26 @@ class Journey(Base):
offset = 0.0
points = []
for track in self.tracks:
+ point = None
for point in track.path().points:
new_point = dataclasses.replace(point, time_offset=point.time_offset + offset)
points.append(new_point)
- offset += point.time_offset
+ if point:
+ offset += point.time_offset
return geo.Path(points)
def gpx_xml(self) -> bytes:
+ """Returns a GPX XML that represents this journey.
+
+ :return: The XML file.
+ """
return geo.gpx_xml(
self.title,
self.description,
datetime.datetime.fromtimestamp(0).replace(tzinfo=datetime.UTC),
self.path().points,
- []
+ [],
)
-__all__ = [
-]
+__all__ = ["Journey", "Visibility"]
diff --git a/fietsboek/models/track.py b/fietsboek/models/track.py
index 2fec844..95e341f 100644
--- a/fietsboek/models/track.py
+++ b/fietsboek/models/track.py
@@ -328,7 +328,7 @@ class Track(Base):
"User", secondary=track_favourite_assoc, back_populates="favourite_tracks"
)
journeys: Mapped[list["models.Journey"]] = relationship(
- "Journey", secondary="journey_track_assoc", back_populates="tracks",
+ "Journey", secondary="journey_track_assoc", back_populates="tracks"
)
@classmethod
diff --git a/fietsboek/models/user.py b/fietsboek/models/user.py
index 45bc8d5..551d920 100644
--- a/fietsboek/models/user.py
+++ b/fietsboek/models/user.py
@@ -40,8 +40,8 @@ from .meta import Base
if TYPE_CHECKING:
from .comment import Comment
- from .track import Track, Upload
from .journey import Journey
+ from .track import Track, Upload
class PasswordMismatch(Exception):
@@ -141,9 +141,7 @@ class User(Base):
comments: Mapped[list["Comment"]] = relationship(
"Comment", back_populates="author", cascade="all, delete-orphan"
)
- journeys: Mapped[list["Journey"]] = relationship(
- "Journey", back_populates="owner",
- )
+ journeys: Mapped[list["Journey"]] = relationship("Journey", back_populates="owner")
# We don't use them, but include them to ensure our cascading works
friends_1: Mapped[list["User"]] = relationship(
@@ -402,8 +400,15 @@ class User(Base):
@staticmethod
def visible_journeys_query(user: Optional["User"] = None) -> CompoundSelect:
+ """Returns a query that returns the visible journeys for a user.
+
+ If the user is ``None``, only public journeys will be returned.
+
+ :param user: The user which to query the journeys for.
+ :return: The query that selects all fitting journeys.
+ """
# Late import to avoid cycles
- # pylint: disable=import-outside-toplevel
+ # pylint: disable=import-outside-toplevel,protected-access
from .journey import Journey, Visibility
queries = []
diff --git a/fietsboek/routes.py b/fietsboek/routes.py
index c7bce62..7042415 100644
--- a/fietsboek/routes.py
+++ b/fietsboek/routes.py
@@ -2,7 +2,7 @@
def includeme(config):
- # pylint: disable=missing-function-docstring
+ # pylint: disable=missing-function-docstring,too-many-statements
config.add_static_view("static", "static", cache_max_age=3600)
config.add_route("home", "/")
config.add_route("login", "/login")
@@ -63,11 +63,25 @@ def includeme(config):
"/journey/{journey_id}/preview",
factory="fietsboek.models.Journey.factory",
)
- 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-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/views/journey.py b/fietsboek/views/journey.py
index 3ee9d3e..74b0fad 100644
--- a/fietsboek/views/journey.py
+++ b/fietsboek/views/journey.py
@@ -1,6 +1,9 @@
+"""Views relating to journeys."""
+
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
@@ -11,9 +14,9 @@ from sqlalchemy.orm import aliased
from .. import trackmap, util
from ..data import JourneyDataDir
+from ..models import User
from ..models.journey import Journey, Visibility
from ..models.track import Track, TrackWithMetadata
-from ..models import User
from .tileproxy import ITileRequester
LOGGER = logging.getLogger(__name__)
@@ -24,6 +27,10 @@ LOGGER = logging.getLogger(__name__)
renderer="fietsboek:templates/journey_list.jinja2",
)
def journey_list(request: Request):
+ """Lists the available journeys.
+
+ :param request: The pyramid request.
+ """
query = select(aliased(Journey, User.visible_journeys_query(request.identity).subquery()))
journeys = request.dbsession.execute(query).scalars()
show_new_button = request.identity is not None
@@ -40,6 +47,10 @@ def journey_list(request: Request):
permission="journey.view",
)
def journey_details(request: Request):
+ """Shows details for a single journey.
+
+ :param request: The pyramid request.
+ """
journey: Journey = request.context
tracks = [TrackWithMetadata(track) for track in journey.tracks]
movement_data = journey.path().movement_data()
@@ -57,6 +68,10 @@ def journey_details(request: Request):
@view_config(route_name="journey-gpx", http_cache=3600, permission="journey.view")
def journey_gpx(request: Request):
+ """The view that returns the journey's GPX.
+
+ :param request: The pyramid request.
+ """
gpx_xml = request.context.gpx_xml()
response = Response(gpx_xml, content_type="application/gpx+xml")
response.md5_etag()
@@ -65,6 +80,10 @@ def journey_gpx(request: Request):
@view_config(route_name="journey-map", http_cache=3600, permission="journey.view")
def journey_map(request: Request):
+ """The journey preview map image.
+
+ :param request: The pyramid request.
+ """
journey: Journey = request.context
journey_data: JourneyDataDir = request.data_manager.open_journey(journey.id)
preview_path = journey_data.preview_path()
@@ -97,7 +116,11 @@ def journey_map(request: Request):
renderer="fietsboek:templates/journey_new.jinja2",
permission="new-journey",
)
-def journey_new(request: Request):
+def journey_new(_request: Request):
+ """The form to add a new journey.
+
+ :param request: The pyramid request.
+ """
return {}
@@ -107,6 +130,10 @@ def journey_new(request: Request):
request_method="POST",
)
def do_journey_new(request: Request):
+ """Handler for submitting the new-journey form.
+
+ :param request: The pyramid request.
+ """
journey = Journey(
owner=request.identity,
title=request.params.get("journeyTitle"),
@@ -133,6 +160,10 @@ def do_journey_new(request: Request):
permission="journey.edit",
)
def journey_edit(request: Request):
+ """The form to edit a journey.
+
+ :param request: The pyramid request.
+ """
journey: Journey = request.context
return {
"journey": journey,
@@ -145,6 +176,10 @@ def journey_edit(request: Request):
request_method="POST",
)
def do_journey_edit(request: Request):
+ """Handler for submitting the edit-journey form.
+
+ :param request: The pyramid request.
+ """
journey: Journey = request.context
request.data_manager.open_journey(journey.id).remove_preview()
@@ -165,7 +200,7 @@ def _extract_visibility(request: Request) -> Visibility:
try:
return Visibility[key]
except KeyError:
- raise HTTPBadRequest("Invalid visibility")
+ raise HTTPBadRequest("Invalid visibility") from None
def _extract_valid_tracks(request: Request, current_ids: set[int]) -> list[int]:
@@ -178,7 +213,7 @@ def _extract_valid_tracks(request: Request, current_ids: set[int]) -> list[int]:
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")
+ raise HTTPBadRequest("Invalid track ID") from None
if not track_ids:
raise HTTPBadRequest("No track IDs given")
@@ -206,6 +241,10 @@ def _extract_valid_tracks(request: Request, current_ids: set[int]) -> list[int]:
request_method="POST",
)
def do_journey_delete(request: Request):
+ """Handler to delete a journey.
+
+ :param request: The pyramid request.
+ """
journey: Journey = request.context
request.data_manager.open_journey(journey.id).purge()
request.dbsession.delete(journey)
@@ -219,6 +258,10 @@ def do_journey_delete(request: Request):
request_method="POST",
)
def do_journey_invalidate_share(request: Request):
+ """Handler to invalidate a journey share link.
+
+ :param request: The pyramid request.
+ """
journey: Journey = request.context
journey.link_secret = util.random_link_secret()
return HTTPFound(request.route_url("journey-details", journey_id=journey.id))