diff options
author | Daniel Schadt <kingdread@gmx.de> | 2022-12-29 14:58:24 +0100 |
---|---|---|
committer | Daniel Schadt <kingdread@gmx.de> | 2022-12-29 14:58:24 +0100 |
commit | 92199846b87f1736b9f807bfd8093bff7a67922e (patch) | |
tree | 69d108f8cd9add0c074776502b2eeb27c2ac5df1 /fietsboek/actions.py | |
parent | 2aee4fe00400ae34350ed50a05fa5c3ac30b1eac (diff) | |
download | fietsboek-92199846b87f1736b9f807bfd8093bff7a67922e.tar.gz fietsboek-92199846b87f1736b9f807bfd8093bff7a67922e.tar.bz2 fietsboek-92199846b87f1736b9f807bfd8093bff7a67922e.zip |
start to move out high-level actions
This is code that needs to be repeated in possibly several places
(website, API, tests), so it makes sense to have those "high level
actions" a bit abstracted. edit.edit_images was already doing that to a
certain degree, but the code shouldn't have stayed in the view.
Diffstat (limited to 'fietsboek/actions.py')
-rw-r--r-- | fietsboek/actions.py | 157 |
1 files changed, 157 insertions, 0 deletions
diff --git a/fietsboek/actions.py b/fietsboek/actions.py new file mode 100644 index 0000000..dff512f --- /dev/null +++ b/fietsboek/actions.py @@ -0,0 +1,157 @@ +"""High-level actions. + +This module implements the basic logic for high level intents such as "add a +track", "delete a track", ... It combines the low-level APIs of the ORM and the +data manager, and provides functions that can be used by the views, the API and +the test functions. +""" +import datetime +import logging +import re +from typing import List + +from pyramid.request import Request +from sqlalchemy import select +from sqlalchemy.orm.session import Session + +from fietsboek import models, util +from fietsboek.models.track import Visibility, TrackType +from fietsboek.data import DataManager + + +LOGGER = logging.getLogger(__name__) + + +def add_track( + dbsession: Session, + data_manager: DataManager, + owner: models.User, + title: str, + date: datetime.datetime, + visibility: Visibility, + track_type: TrackType, + description: str, + badges: List[models.Badge], + tagged_people: List[models.User], + tags: List[str], + gpx_data: bytes, +) -> models.Track: + """Adds a track to the database. + + Note that this function does not do any authorization checking, and as + such, expects the caller to ensure that everything is in order. + + Most of the parameters correspond to the attributes of + :class:`~fietsboek.models.track.Track` objects. + + :param dbsession: The database session. + :param data_manager: The data manager. + :param owner: The owner of the track. + :param title: Title of the track. + :param date: Date of the track, should be timezone-aware. + :param visibility: Track visibility. + :param track_type: Type of the track. + :param description: Track description. + :param badges: Badges to attach to the track. + :param tagged_people: List of people to tag. + :param tags: List of text tags for the track. + :param gpx_data: Actual GPX data (uncompressed, straight from the source). + :return: The track object that has been inserted into the database. Useful + for its ``id`` attribute. + """ + # pylint: disable=too-many-arguments + LOGGER.debug("Inserting new track...") + track = models.Track( + owner=owner, + title=title, + visibility=visibility, + type=track_type, + description=description, + badges=badges, + link_secret=util.random_link_secret(), + tagged_people=tagged_people, + ) + track.date = date + track.sync_tags(tags) + dbsession.add(track) + dbsession.flush() + + # Best time to build the cache is right after the upload + track.ensure_cache(gpx_data) + dbsession.add(track.cache) + + # Save the GPX data + LOGGER.debug("Creating a new data folder for %d", track.id) + manager = data_manager.initialize(track.id) + LOGGER.debug("Saving GPX to %s", manager.gpx_path()) + manager.compress_gpx(gpx_data) + manager.backup() + + manager.engrave_metadata( + title=track.title, + description=track.description, + author_name=track.owner.name, + time=track.date, + ) + + return track + + +def edit_images(request: Request, track: models.Track): + """Edit the images according to the given request. + + This deletes and adds images and image descriptions as needed, based on the + ``image[...]`` and ``image-description[...]`` fields. + + :param request: The request. + :param track: The track to edit. + """ + LOGGER.debug("Editing images for %d", track.id) + manager = request.data_manager.open(track.id) + + # Delete requested images + for image in request.params.getall("delete-image[]"): + manager.delete_image(image) + image_meta = request.dbsession.execute( + select(models.ImageMetadata).filter_by(track_id=track.id, image_name=image) + ).scalar_one_or_none() + LOGGER.debug("Deleted image %s %s (metadata: %s)", track.id, image, image_meta) + if image_meta: + request.dbsession.delete(image_meta) + + # Add new images + set_descriptions = set() + for param_name, image in request.params.items(): + match = re.match("image\\[(\\d+)\\]$", param_name) + if not match: + continue + # Sent for the multi input + if image == b"": + continue + + upload_id = match.group(1) + image_name = manager.add_image(image.file, image.filename) + image_meta = models.ImageMetadata(track=track, image_name=image_name) + image_meta.description = request.params.get(f"image-description[{upload_id}]", "") + request.dbsession.add(image_meta) + LOGGER.debug("Uploaded image %s %s", track.id, image_name) + set_descriptions.add(upload_id) + + images = manager.images() + # Set image descriptions + for param_name, description in request.params.items(): + match = re.match("image-description\\[(.+)\\]$", param_name) + if not match: + continue + image_id = match.group(1) + # Descriptions that we already set while adding new images can be + # ignored + if image_id in set_descriptions: + continue + # Did someone give us a wrong ID?! + if image_id not in images: + LOGGER.info("Got a ghost image description for track %s: %s", track.id, image_id) + continue + image_meta = models.ImageMetadata.get_or_create(request.dbsession, track, image_id) + image_meta.description = description + request.dbsession.add(image_meta) |