diff options
| author | Daniel Schadt <kingdread@gmx.de> | 2025-12-30 21:58:15 +0100 |
|---|---|---|
| committer | Daniel Schadt <kingdread@gmx.de> | 2025-12-30 21:58:15 +0100 |
| commit | 5a71f663371381d47ad9da6b9439fcba649cc5b1 (patch) | |
| tree | 2bd5b8dcc61e3b2def991cf37c3a709130cbcfa5 | |
| parent | fb5eebc473fa4d1ce9adf70fe7afcc9b3ae8a6b6 (diff) | |
| download | fietsboek-5a71f663371381d47ad9da6b9439fcba649cc5b1.tar.gz fietsboek-5a71f663371381d47ad9da6b9439fcba649cc5b1.tar.bz2 fietsboek-5a71f663371381d47ad9da6b9439fcba649cc5b1.zip | |
fix lint
| -rw-r--r-- | fietsboek/models/__init__.py | 2 | ||||
| -rw-r--r-- | fietsboek/models/journey.py | 43 | ||||
| -rw-r--r-- | fietsboek/models/track.py | 2 | ||||
| -rw-r--r-- | fietsboek/models/user.py | 15 | ||||
| -rw-r--r-- | fietsboek/routes.py | 26 | ||||
| -rw-r--r-- | fietsboek/views/journey.py | 51 |
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)) |
