aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--fietsboek/models/user.py30
-rw-r--r--fietsboek/routes.py2
-rw-r--r--fietsboek/templates/profile.jinja236
-rw-r--r--fietsboek/views/profile.py63
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"]