diff options
-rw-r--r-- | fietsboek/models/user.py | 30 | ||||
-rw-r--r-- | fietsboek/routes.py | 2 | ||||
-rw-r--r-- | fietsboek/templates/profile.jinja2 | 36 | ||||
-rw-r--r-- | fietsboek/views/profile.py | 63 |
4 files changed, 131 insertions, 0 deletions
diff --git a/fietsboek/models/user.py b/fietsboek/models/user.py index 62c935e..82be8d7 100644 --- a/fietsboek/models/user.py +++ b/fietsboek/models/user.py @@ -7,6 +7,9 @@ from functools import reduce from cryptography.exceptions import InvalidKey from cryptography.hazmat.primitives.kdf.scrypt import Scrypt +from pyramid.authorization import ALL_PERMISSIONS, Allow +from pyramid.httpexceptions import HTTPNotFound +from pyramid.request import Request from sqlalchemy import ( Boolean, Column, @@ -134,6 +137,33 @@ class User(Base): """ return select(cls).filter(func.lower(email) == func.lower(cls.email)) + @classmethod + def factory(cls, request: Request) -> "User": + """Factory method to pass to a route definition. + + This factory retrieves the track based on the ``track_id`` matched + route parameter, and returns the track. If the track is not found, + ``HTTPNotFound`` is raised. + + :raises pyramid.httpexception.NotFound: If the track is not found. + :param request: The pyramid request. + :return: The track. + """ + user_id = request.matchdict["user_id"] + query = select(cls).filter_by(id=user_id) + track = request.dbsession.execute(query).scalar_one_or_none() + if track is None: + raise HTTPNotFound() + return track + + def __acl__(self): + # Basic ACL: Permissions for the admin, the owner and the share link + acl = [ + (Allow, "group:admins", ALL_PERMISSIONS), + (Allow, f"user:{self.id}", ALL_PERMISSIONS), + ] + return acl + def set_password(self, new_password): """Sets a new password for the user. diff --git a/fietsboek/routes.py b/fietsboek/routes.py index cf6af82..5e05269 100644 --- a/fietsboek/routes.py +++ b/fietsboek/routes.py @@ -59,4 +59,6 @@ def includeme(config): config.add_route("accept-friend", "/me/accept-friend") config.add_route("json-friends", "/me/friends.json") + config.add_route("profile", "/user/{user_id}", factory="fietsboek.models.User.factory") + config.add_route("tile-proxy", "/tile/{provider}/{z:\\d+}/{x:\\d+}/{y:\\d+}") diff --git a/fietsboek/templates/profile.jinja2 b/fietsboek/templates/profile.jinja2 new file mode 100644 index 0000000..da4d732 --- /dev/null +++ b/fietsboek/templates/profile.jinja2 @@ -0,0 +1,36 @@ +{% extends "layout.jinja2" %} + +{% block content %} +<div class="container"> + <h1>{{ user.name }}</h1> + <table class="table table-hover table-sm"> + <th scope="row">{{ _("page.details.length") }}</th> + <td id="detailsLength">{{ (total_length / 1000) | round(2) | format_decimal }} km</td> + </tr> + <tr> + <th scope="row">{{ _("page.details.uphill") }}</th> + <td id="detailsUphill">{{ total_uphill | round(2) | format_decimal }} m</td> + </tr> + <tr> + <th scope="row">{{ _("page.details.downhill") }}</th> + <td id="detailsDownhill">{{ total_downhill | round(2) | format_decimal }} m</td> + </tr> + <tr> + <th scope="row">{{ _("page.details.moving_time") }}</th> + <td id="detailsMovingTime">{{ total_moving_time }}</td> + </tr> + <tr> + <th scope="row">{{ _("page.details.stopped_time") }}</th> + <td id="detailsStoppedTime">{{ total_stopped_time }}</td> + </tr> + <tr> + <th scope="row">{{ _("page.details.max_speed") }}</th> + <td id="detailsMaxSpeed">{{ mps_to_kph(max_speed) | round(2) | format_decimal }} km/h</td> + </tr> + <tr> + <th scope="row">{{ _("page.details.avg_speed") }}</th> + <td id="detailsAvgSpeed">{{ mps_to_kph(avg_speed) | round(2) | format_decimal }} km/h</td> + </tr> + </table> +</div> +{% endblock %} diff --git a/fietsboek/views/profile.py b/fietsboek/views/profile.py new file mode 100644 index 0000000..488ffb0 --- /dev/null +++ b/fietsboek/views/profile.py @@ -0,0 +1,63 @@ +import datetime + +from pyramid.request import Request +from pyramid.view import view_config + +from .. import models, util +from ..models.track import TrackType, TrackWithMetadata + + +def round_to_seconds(value: datetime.timedelta) -> datetime.timedelta: + """Round a timedelta to full seconds. + + :param value: The input value. + :return: The rounded value. + """ + return util.round_timedelta_to_multiple(value, datetime.timedelta(seconds=1)) + + +@view_config( + route_name="profile", + renderer="fietsboek:templates/profile.jinja2", + request_method="GET", +) +def profile(request: Request) -> dict: + total_length = 0.0 + total_uphill = 0.0 + total_downhill = 0.0 + total_moving_time = datetime.timedelta(0) + total_stopped_time = datetime.timedelta(0) + max_speed = 0.0 + + track: models.Track + for track in request.context.tracks: + if track.type != TrackType.ORGANIC: + continue + + meta = TrackWithMetadata(track, request.data_manager) + + total_length += meta.length + total_uphill += meta.uphill + total_downhill += meta.downhill + total_moving_time += meta.moving_time + total_stopped_time += meta.stopped_time + max_speed = max(max_speed, meta.max_speed) + + avg_speed = total_length / total_moving_time.total_seconds() + total_moving_time = round_to_seconds(total_moving_time) + total_stopped_time = round_to_seconds(total_stopped_time) + + return { + "user": request.context, + "total_length": total_length, + "total_uphill": total_uphill, + "total_downhill": total_downhill, + "total_moving_time": total_moving_time, + "total_stopped_time": total_stopped_time, + "max_speed": max_speed, + "avg_speed": avg_speed, + "mps_to_kph": util.mps_to_kph, + } + + +__all__ = ["profile"] |