aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/index.rst3
-rw-r--r--fietsboek/locale/de/LC_MESSAGES/messages.mobin17256 -> 17659 bytes
-rw-r--r--fietsboek/locale/de/LC_MESSAGES/messages.po108
-rw-r--r--fietsboek/locale/en/LC_MESSAGES/messages.mobin16183 -> 16586 bytes
-rw-r--r--fietsboek/locale/en/LC_MESSAGES/messages.po108
-rw-r--r--fietsboek/locale/fietslog.pot108
-rw-r--r--fietsboek/routes.py12
-rw-r--r--fietsboek/static/DeadEnd.svg44
-rw-r--r--fietsboek/static/NoEntry.svg37
-rw-r--r--fietsboek/templates/403.jinja216
-rw-r--r--fietsboek/templates/404.jinja214
-rw-r--r--fietsboek/templates/layout.jinja22
-rw-r--r--fietsboek/templates/profile.jinja2244
-rw-r--r--fietsboek/templates/profile_calendar.jinja244
-rw-r--r--fietsboek/templates/profile_graphs.jinja216
-rw-r--r--fietsboek/templates/profile_overview.jinja2188
-rw-r--r--fietsboek/views/errors.py32
-rw-r--r--fietsboek/views/notfound.py19
-rw-r--r--fietsboek/views/profile.py70
-rw-r--r--tests/playwright/test_profiles.py2
20 files changed, 664 insertions, 403 deletions
diff --git a/doc/index.rst b/doc/index.rst
index e3c765b..617afa7 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -42,8 +42,7 @@ Fietsboek is built on top of the following open source technologies:
* `OpenStreetMap <https://www.openstreetmap.org/>`__
* `Bootstrap <https://getbootstrap.com/>`__ and `Bootstrap Icons
<https://icons.getbootstrap.com/>`__
-* `OpenSans <https://github.com/googlefonts/opensans>`__ (Copyright 2020 The
- Open Sans Project Authors)
+* `Inter <https://rsms.me/inter/>`__ by Rasmus Andersson
Indices and tables
==================
diff --git a/fietsboek/locale/de/LC_MESSAGES/messages.mo b/fietsboek/locale/de/LC_MESSAGES/messages.mo
index c953f1d..f2d0f60 100644
--- a/fietsboek/locale/de/LC_MESSAGES/messages.mo
+++ b/fietsboek/locale/de/LC_MESSAGES/messages.mo
Binary files differ
diff --git a/fietsboek/locale/de/LC_MESSAGES/messages.po b/fietsboek/locale/de/LC_MESSAGES/messages.po
index 7e00aac..3319052 100644
--- a/fietsboek/locale/de/LC_MESSAGES/messages.po
+++ b/fietsboek/locale/de/LC_MESSAGES/messages.po
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2025-03-26 19:47+0100\n"
+"POT-Creation-Date: 2025-05-06 21:46+0200\n"
"PO-Revision-Date: 2022-07-02 17:35+0200\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: de\n"
@@ -69,6 +69,30 @@ msgstr "Maximalgeschwindigkeit"
msgid "tooltip.table.avg_speed"
msgstr "Durchschnittsgeschwindigkeit"
+#: fietsboek/templates/403.jinja2:5
+msgid "403.title"
+msgstr "Zugang verboten"
+
+#: fietsboek/templates/403.jinja2:9
+msgid "403.no_access"
+msgstr "Du hast keinen Zugang zu dieser Ressource."
+
+#: fietsboek/templates/403.jinja2:12
+msgid "403.try_log_in"
+msgstr "Falls Du Zugang haben solltest, stelle sicher, dass du korrekt angemeldet bist."
+
+#: fietsboek/templates/404.jinja2:5
+msgid "404.title"
+msgstr "Sackgasse"
+
+#: fietsboek/templates/404.jinja2:9
+msgid "404.path_not_found"
+msgstr "Der gesuchte Weg wurde nicht gefunden."
+
+#: fietsboek/templates/404.jinja2:12
+msgid "404.choose_different"
+msgstr "Bitte such einen anderen Weg."
+
#: fietsboek/templates/admin.jinja2:5
msgid "page.admin.title"
msgstr "Administration"
@@ -262,52 +286,52 @@ msgid "page.browse.synthetic_tooltip"
msgstr "Dies ist eine geplante Strecke"
#: fietsboek/templates/browse.jinja2:158 fietsboek/templates/details.jinja2:103
-#: fietsboek/templates/profile.jinja2:15
+#: fietsboek/templates/profile_overview.jinja2:16
msgid "page.details.date"
msgstr "Datum"
#: fietsboek/templates/browse.jinja2:160 fietsboek/templates/details.jinja2:117
-#: fietsboek/templates/profile.jinja2:17
+#: fietsboek/templates/profile_overview.jinja2:18
msgid "page.details.length"
msgstr "Länge"
#: fietsboek/templates/browse.jinja2:165 fietsboek/templates/details.jinja2:108
-#: fietsboek/templates/profile.jinja2:21
+#: fietsboek/templates/profile_overview.jinja2:22
msgid "page.details.start_time"
msgstr "Startzeit"
#: fietsboek/templates/browse.jinja2:167 fietsboek/templates/details.jinja2:112
-#: fietsboek/templates/profile.jinja2:23
+#: fietsboek/templates/profile_overview.jinja2:24
msgid "page.details.end_time"
msgstr "Endzeit"
#: fietsboek/templates/browse.jinja2:172 fietsboek/templates/details.jinja2:121
-#: fietsboek/templates/profile.jinja2:27
+#: fietsboek/templates/profile_overview.jinja2:28
msgid "page.details.uphill"
msgstr "Bergauf"
#: fietsboek/templates/browse.jinja2:174 fietsboek/templates/details.jinja2:125
-#: fietsboek/templates/profile.jinja2:29
+#: fietsboek/templates/profile_overview.jinja2:30
msgid "page.details.downhill"
msgstr "Bergab"
#: fietsboek/templates/browse.jinja2:179 fietsboek/templates/details.jinja2:130
-#: fietsboek/templates/profile.jinja2:33
+#: fietsboek/templates/profile_overview.jinja2:34
msgid "page.details.moving_time"
msgstr "Fahrzeit"
#: fietsboek/templates/browse.jinja2:181 fietsboek/templates/details.jinja2:134
-#: fietsboek/templates/profile.jinja2:35
+#: fietsboek/templates/profile_overview.jinja2:36
msgid "page.details.stopped_time"
msgstr "Haltezeit"
#: fietsboek/templates/browse.jinja2:185 fietsboek/templates/details.jinja2:138
-#: fietsboek/templates/profile.jinja2:39
+#: fietsboek/templates/profile_overview.jinja2:40
msgid "page.details.max_speed"
msgstr "maximale Geschwindigkeit"
#: fietsboek/templates/browse.jinja2:187 fietsboek/templates/details.jinja2:142
-#: fietsboek/templates/profile.jinja2:41
+#: fietsboek/templates/profile_overview.jinja2:42
msgid "page.details.avg_speed"
msgstr "durchschnittliche Geschwindigkeit"
@@ -722,91 +746,91 @@ msgstr "Passwörter stimmen nicht überein"
msgid "page.password_reset.reset"
msgstr "Zurücksetzen"
-#: fietsboek/templates/profile.jinja2:64
+#: fietsboek/templates/profile.jinja2:10
msgid "page.profile.tabbar.overview"
msgstr "Übersicht"
-#: fietsboek/templates/profile.jinja2:69
+#: fietsboek/templates/profile.jinja2:15
msgid "page.profile.tabbar.graphs"
msgstr "Diagramme"
-#: fietsboek/templates/profile.jinja2:74
+#: fietsboek/templates/profile.jinja2:20
msgid "page.profile.tabbar.calendar"
msgstr "Kalender"
-#: fietsboek/templates/profile.jinja2:88
+#: fietsboek/templates/profile_calendar.jinja2:9
+msgid "page.profile.calendar.previous"
+msgstr "Vorheriger Monat"
+
+#: fietsboek/templates/profile_calendar.jinja2:11
+msgid "page.profile.calendar.next"
+msgstr "Nächster Monat"
+
+#: fietsboek/templates/profile_graphs.jinja2:6
+msgid "page.profile.graph.km_per_month"
+msgstr "Kilometer pro Monat"
+
+#: fietsboek/templates/profile_overview.jinja2:66
msgid "page.profile.length"
msgstr "Länge"
-#: fietsboek/templates/profile.jinja2:92
+#: fietsboek/templates/profile_overview.jinja2:70
msgid "page.profile.avg_length"
msgstr "durchschnittliche Länge"
-#: fietsboek/templates/profile.jinja2:96
+#: fietsboek/templates/profile_overview.jinja2:74
msgid "page.profile.uphill"
msgstr "Bergauf"
-#: fietsboek/templates/profile.jinja2:100
+#: fietsboek/templates/profile_overview.jinja2:78
msgid "page.profile.downhill"
msgstr "Bergab"
-#: fietsboek/templates/profile.jinja2:104
+#: fietsboek/templates/profile_overview.jinja2:82
msgid "page.profile.moving_time"
msgstr "Fahrzeit"
-#: fietsboek/templates/profile.jinja2:108
+#: fietsboek/templates/profile_overview.jinja2:86
msgid "page.profile.stopped_time"
msgstr "Haltezeit"
-#: fietsboek/templates/profile.jinja2:112
+#: fietsboek/templates/profile_overview.jinja2:90
msgid "page.profile.avg_duration"
msgstr "durchschnittliche Dauer"
-#: fietsboek/templates/profile.jinja2:116
+#: fietsboek/templates/profile_overview.jinja2:94
msgid "page.profile.max_speed"
msgstr "maximale Geschwindigkeit"
-#: fietsboek/templates/profile.jinja2:120
+#: fietsboek/templates/profile_overview.jinja2:98
msgid "page.profile.avg_speed"
msgstr "durchschnittliche Geschwindigkeit"
-#: fietsboek/templates/profile.jinja2:124
+#: fietsboek/templates/profile_overview.jinja2:102
msgid "page.profile.number_of_tracks"
msgstr "Anzahl der Strecken"
-#: fietsboek/templates/profile.jinja2:130
+#: fietsboek/templates/profile_overview.jinja2:108
msgid "page.profile.longest_distance_track"
msgstr "Weiteste Strecke"
-#: fietsboek/templates/profile.jinja2:135
+#: fietsboek/templates/profile_overview.jinja2:113
msgid "page.profile.shortest_distance_track"
msgstr "Kürzeste Strecke"
-#: fietsboek/templates/profile.jinja2:140
+#: fietsboek/templates/profile_overview.jinja2:118
msgid "page.profile.longest_duration_track"
msgstr "Am Längsten Dauernde Strecke"
-#: fietsboek/templates/profile.jinja2:145
+#: fietsboek/templates/profile_overview.jinja2:123
msgid "page.profile.shortest_duration_track"
msgstr "Am Kürzesten Dauernde Strecke"
-#: fietsboek/templates/profile.jinja2:152
-msgid "page.profile.graph.km_per_month"
-msgstr "Kilometer pro Monat"
-
-#: fietsboek/templates/profile.jinja2:161
-msgid "page.profile.calendar.previous"
-msgstr "Vorheriger Monat"
-
-#: fietsboek/templates/profile.jinja2:163
-msgid "page.profile.calendar.next"
-msgstr "Nächster Monat"
-
-#: fietsboek/templates/profile.jinja2:218
+#: fietsboek/templates/profile_overview.jinja2:144
msgid "page.profile.heatmap"
msgstr "Heatmap"
-#: fietsboek/templates/profile.jinja2:223
+#: fietsboek/templates/profile_overview.jinja2:152
msgid "page.profile.tilehunt"
msgstr "Kacheljäger"
diff --git a/fietsboek/locale/en/LC_MESSAGES/messages.mo b/fietsboek/locale/en/LC_MESSAGES/messages.mo
index 15c503b..fd67c57 100644
--- a/fietsboek/locale/en/LC_MESSAGES/messages.mo
+++ b/fietsboek/locale/en/LC_MESSAGES/messages.mo
Binary files differ
diff --git a/fietsboek/locale/en/LC_MESSAGES/messages.po b/fietsboek/locale/en/LC_MESSAGES/messages.po
index 9f8e8a5..4e9f302 100644
--- a/fietsboek/locale/en/LC_MESSAGES/messages.po
+++ b/fietsboek/locale/en/LC_MESSAGES/messages.po
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2025-03-26 19:47+0100\n"
+"POT-Creation-Date: 2025-05-06 21:46+0200\n"
"PO-Revision-Date: 2023-04-03 20:42+0200\n"
"Last-Translator: \n"
"Language: en\n"
@@ -69,6 +69,30 @@ msgstr "Max Speed"
msgid "tooltip.table.avg_speed"
msgstr "Average Speed"
+#: fietsboek/templates/403.jinja2:5
+msgid "403.title"
+msgstr "No entry"
+
+#: fietsboek/templates/403.jinja2:9
+msgid "403.no_access"
+msgstr "You are not allowed to access this resource."
+
+#: fietsboek/templates/403.jinja2:12
+msgid "403.try_log_in"
+msgstr "If you should have access, make sure you are logged in with the right credentials."
+
+#: fietsboek/templates/404.jinja2:5
+msgid "404.title"
+msgstr "Dead end"
+
+#: fietsboek/templates/404.jinja2:9
+msgid "404.path_not_found"
+msgstr "The path you have chosen was not found."
+
+#: fietsboek/templates/404.jinja2:12
+msgid "404.choose_different"
+msgstr "Please choose a different path."
+
#: fietsboek/templates/admin.jinja2:5
msgid "page.admin.title"
msgstr "Administration"
@@ -262,52 +286,52 @@ msgid "page.browse.synthetic_tooltip"
msgstr "This is a pre-planned track"
#: fietsboek/templates/browse.jinja2:158 fietsboek/templates/details.jinja2:103
-#: fietsboek/templates/profile.jinja2:15
+#: fietsboek/templates/profile_overview.jinja2:16
msgid "page.details.date"
msgstr "Date"
#: fietsboek/templates/browse.jinja2:160 fietsboek/templates/details.jinja2:117
-#: fietsboek/templates/profile.jinja2:17
+#: fietsboek/templates/profile_overview.jinja2:18
msgid "page.details.length"
msgstr "Length"
#: fietsboek/templates/browse.jinja2:165 fietsboek/templates/details.jinja2:108
-#: fietsboek/templates/profile.jinja2:21
+#: fietsboek/templates/profile_overview.jinja2:22
msgid "page.details.start_time"
msgstr "Record Start"
#: fietsboek/templates/browse.jinja2:167 fietsboek/templates/details.jinja2:112
-#: fietsboek/templates/profile.jinja2:23
+#: fietsboek/templates/profile_overview.jinja2:24
msgid "page.details.end_time"
msgstr "Record End"
#: fietsboek/templates/browse.jinja2:172 fietsboek/templates/details.jinja2:121
-#: fietsboek/templates/profile.jinja2:27
+#: fietsboek/templates/profile_overview.jinja2:28
msgid "page.details.uphill"
msgstr "Uphill"
#: fietsboek/templates/browse.jinja2:174 fietsboek/templates/details.jinja2:125
-#: fietsboek/templates/profile.jinja2:29
+#: fietsboek/templates/profile_overview.jinja2:30
msgid "page.details.downhill"
msgstr "Downhill"
#: fietsboek/templates/browse.jinja2:179 fietsboek/templates/details.jinja2:130
-#: fietsboek/templates/profile.jinja2:33
+#: fietsboek/templates/profile_overview.jinja2:34
msgid "page.details.moving_time"
msgstr "Moving Time"
#: fietsboek/templates/browse.jinja2:181 fietsboek/templates/details.jinja2:134
-#: fietsboek/templates/profile.jinja2:35
+#: fietsboek/templates/profile_overview.jinja2:36
msgid "page.details.stopped_time"
msgstr "Stopped Time"
#: fietsboek/templates/browse.jinja2:185 fietsboek/templates/details.jinja2:138
-#: fietsboek/templates/profile.jinja2:39
+#: fietsboek/templates/profile_overview.jinja2:40
msgid "page.details.max_speed"
msgstr "Max Speed"
#: fietsboek/templates/browse.jinja2:187 fietsboek/templates/details.jinja2:142
-#: fietsboek/templates/profile.jinja2:41
+#: fietsboek/templates/profile_overview.jinja2:42
msgid "page.details.avg_speed"
msgstr "Average Speed"
@@ -716,91 +740,91 @@ msgstr "Passwords must match"
msgid "page.password_reset.reset"
msgstr "Reset"
-#: fietsboek/templates/profile.jinja2:64
+#: fietsboek/templates/profile.jinja2:10
msgid "page.profile.tabbar.overview"
msgstr "Overview"
-#: fietsboek/templates/profile.jinja2:69
+#: fietsboek/templates/profile.jinja2:15
msgid "page.profile.tabbar.graphs"
msgstr "Graphs"
-#: fietsboek/templates/profile.jinja2:74
+#: fietsboek/templates/profile.jinja2:20
msgid "page.profile.tabbar.calendar"
msgstr "Calendar"
-#: fietsboek/templates/profile.jinja2:88
+#: fietsboek/templates/profile_calendar.jinja2:9
+msgid "page.profile.calendar.previous"
+msgstr "Previous month"
+
+#: fietsboek/templates/profile_calendar.jinja2:11
+msgid "page.profile.calendar.next"
+msgstr "Next month"
+
+#: fietsboek/templates/profile_graphs.jinja2:6
+msgid "page.profile.graph.km_per_month"
+msgstr "Kilometers per month"
+
+#: fietsboek/templates/profile_overview.jinja2:66
msgid "page.profile.length"
msgstr "Length"
-#: fietsboek/templates/profile.jinja2:92
+#: fietsboek/templates/profile_overview.jinja2:70
msgid "page.profile.avg_length"
msgstr "Average Length"
-#: fietsboek/templates/profile.jinja2:96
+#: fietsboek/templates/profile_overview.jinja2:74
msgid "page.profile.uphill"
msgstr "Uphill"
-#: fietsboek/templates/profile.jinja2:100
+#: fietsboek/templates/profile_overview.jinja2:78
msgid "page.profile.downhill"
msgstr "Downhill"
-#: fietsboek/templates/profile.jinja2:104
+#: fietsboek/templates/profile_overview.jinja2:82
msgid "page.profile.moving_time"
msgstr "Moving Time"
-#: fietsboek/templates/profile.jinja2:108
+#: fietsboek/templates/profile_overview.jinja2:86
msgid "page.profile.stopped_time"
msgstr "Stopped Time"
-#: fietsboek/templates/profile.jinja2:112
+#: fietsboek/templates/profile_overview.jinja2:90
msgid "page.profile.avg_duration"
msgstr "Average Duration"
-#: fietsboek/templates/profile.jinja2:116
+#: fietsboek/templates/profile_overview.jinja2:94
msgid "page.profile.max_speed"
msgstr "Max Speed"
-#: fietsboek/templates/profile.jinja2:120
+#: fietsboek/templates/profile_overview.jinja2:98
msgid "page.profile.avg_speed"
msgstr "Average Speed"
-#: fietsboek/templates/profile.jinja2:124
+#: fietsboek/templates/profile_overview.jinja2:102
msgid "page.profile.number_of_tracks"
msgstr "Number of tracks"
-#: fietsboek/templates/profile.jinja2:130
+#: fietsboek/templates/profile_overview.jinja2:108
msgid "page.profile.longest_distance_track"
msgstr "Longest Track"
-#: fietsboek/templates/profile.jinja2:135
+#: fietsboek/templates/profile_overview.jinja2:113
msgid "page.profile.shortest_distance_track"
msgstr "Shortest Track"
-#: fietsboek/templates/profile.jinja2:140
+#: fietsboek/templates/profile_overview.jinja2:118
msgid "page.profile.longest_duration_track"
msgstr "Most Time-Consuming Track"
-#: fietsboek/templates/profile.jinja2:145
+#: fietsboek/templates/profile_overview.jinja2:123
msgid "page.profile.shortest_duration_track"
msgstr "Quickest Track"
-#: fietsboek/templates/profile.jinja2:152
-msgid "page.profile.graph.km_per_month"
-msgstr "Kilometers per month"
-
-#: fietsboek/templates/profile.jinja2:161
-msgid "page.profile.calendar.previous"
-msgstr "Previous month"
-
-#: fietsboek/templates/profile.jinja2:163
-msgid "page.profile.calendar.next"
-msgstr "Next month"
-
-#: fietsboek/templates/profile.jinja2:218
+#: fietsboek/templates/profile_overview.jinja2:144
msgid "page.profile.heatmap"
msgstr "Heat Map"
-#: fietsboek/templates/profile.jinja2:223
+#: fietsboek/templates/profile_overview.jinja2:152
msgid "page.profile.tilehunt"
msgstr "Tilehunt"
diff --git a/fietsboek/locale/fietslog.pot b/fietsboek/locale/fietslog.pot
index 58a9646..1a1770b 100644
--- a/fietsboek/locale/fietslog.pot
+++ b/fietsboek/locale/fietslog.pot
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2025-03-26 19:47+0100\n"
+"POT-Creation-Date: 2025-05-06 21:46+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -65,6 +65,30 @@ msgstr ""
msgid "tooltip.table.avg_speed"
msgstr ""
+#: fietsboek/templates/403.jinja2:5
+msgid "403.title"
+msgstr ""
+
+#: fietsboek/templates/403.jinja2:9
+msgid "403.no_entry"
+msgstr ""
+
+#: fietsboek/templates/403.jinja2:12
+msgid "403.try_log_in"
+msgstr ""
+
+#: fietsboek/templates/404.jinja2:5
+msgid "404.title"
+msgstr ""
+
+#: fietsboek/templates/404.jinja2:9
+msgid "404.path_not_found"
+msgstr ""
+
+#: fietsboek/templates/404.jinja2:12
+msgid "404.choose_different"
+msgstr ""
+
#: fietsboek/templates/admin.jinja2:5
msgid "page.admin.title"
msgstr ""
@@ -258,52 +282,52 @@ msgid "page.browse.synthetic_tooltip"
msgstr ""
#: fietsboek/templates/browse.jinja2:158 fietsboek/templates/details.jinja2:103
-#: fietsboek/templates/profile.jinja2:15
+#: fietsboek/templates/profile_overview.jinja2:16
msgid "page.details.date"
msgstr ""
#: fietsboek/templates/browse.jinja2:160 fietsboek/templates/details.jinja2:117
-#: fietsboek/templates/profile.jinja2:17
+#: fietsboek/templates/profile_overview.jinja2:18
msgid "page.details.length"
msgstr ""
#: fietsboek/templates/browse.jinja2:165 fietsboek/templates/details.jinja2:108
-#: fietsboek/templates/profile.jinja2:21
+#: fietsboek/templates/profile_overview.jinja2:22
msgid "page.details.start_time"
msgstr ""
#: fietsboek/templates/browse.jinja2:167 fietsboek/templates/details.jinja2:112
-#: fietsboek/templates/profile.jinja2:23
+#: fietsboek/templates/profile_overview.jinja2:24
msgid "page.details.end_time"
msgstr ""
#: fietsboek/templates/browse.jinja2:172 fietsboek/templates/details.jinja2:121
-#: fietsboek/templates/profile.jinja2:27
+#: fietsboek/templates/profile_overview.jinja2:28
msgid "page.details.uphill"
msgstr ""
#: fietsboek/templates/browse.jinja2:174 fietsboek/templates/details.jinja2:125
-#: fietsboek/templates/profile.jinja2:29
+#: fietsboek/templates/profile_overview.jinja2:30
msgid "page.details.downhill"
msgstr ""
#: fietsboek/templates/browse.jinja2:179 fietsboek/templates/details.jinja2:130
-#: fietsboek/templates/profile.jinja2:33
+#: fietsboek/templates/profile_overview.jinja2:34
msgid "page.details.moving_time"
msgstr ""
#: fietsboek/templates/browse.jinja2:181 fietsboek/templates/details.jinja2:134
-#: fietsboek/templates/profile.jinja2:35
+#: fietsboek/templates/profile_overview.jinja2:36
msgid "page.details.stopped_time"
msgstr ""
#: fietsboek/templates/browse.jinja2:185 fietsboek/templates/details.jinja2:138
-#: fietsboek/templates/profile.jinja2:39
+#: fietsboek/templates/profile_overview.jinja2:40
msgid "page.details.max_speed"
msgstr ""
#: fietsboek/templates/browse.jinja2:187 fietsboek/templates/details.jinja2:142
-#: fietsboek/templates/profile.jinja2:41
+#: fietsboek/templates/profile_overview.jinja2:42
msgid "page.details.avg_speed"
msgstr ""
@@ -710,91 +734,91 @@ msgstr ""
msgid "page.password_reset.reset"
msgstr ""
-#: fietsboek/templates/profile.jinja2:64
+#: fietsboek/templates/profile.jinja2:10
msgid "page.profile.tabbar.overview"
msgstr ""
-#: fietsboek/templates/profile.jinja2:69
+#: fietsboek/templates/profile.jinja2:15
msgid "page.profile.tabbar.graphs"
msgstr ""
-#: fietsboek/templates/profile.jinja2:74
+#: fietsboek/templates/profile.jinja2:20
msgid "page.profile.tabbar.calendar"
msgstr ""
-#: fietsboek/templates/profile.jinja2:88
+#: fietsboek/templates/profile_calendar.jinja2:9
+msgid "page.profile.calendar.previous"
+msgstr ""
+
+#: fietsboek/templates/profile_calendar.jinja2:11
+msgid "page.profile.calendar.next"
+msgstr ""
+
+#: fietsboek/templates/profile_graphs.jinja2:6
+msgid "page.profile.graph.km_per_month"
+msgstr ""
+
+#: fietsboek/templates/profile_overview.jinja2:66
msgid "page.profile.length"
msgstr ""
-#: fietsboek/templates/profile.jinja2:92
+#: fietsboek/templates/profile_overview.jinja2:70
msgid "page.profile.avg_length"
msgstr ""
-#: fietsboek/templates/profile.jinja2:96
+#: fietsboek/templates/profile_overview.jinja2:74
msgid "page.profile.uphill"
msgstr ""
-#: fietsboek/templates/profile.jinja2:100
+#: fietsboek/templates/profile_overview.jinja2:78
msgid "page.profile.downhill"
msgstr ""
-#: fietsboek/templates/profile.jinja2:104
+#: fietsboek/templates/profile_overview.jinja2:82
msgid "page.profile.moving_time"
msgstr ""
-#: fietsboek/templates/profile.jinja2:108
+#: fietsboek/templates/profile_overview.jinja2:86
msgid "page.profile.stopped_time"
msgstr ""
-#: fietsboek/templates/profile.jinja2:112
+#: fietsboek/templates/profile_overview.jinja2:90
msgid "page.profile.avg_duration"
msgstr ""
-#: fietsboek/templates/profile.jinja2:116
+#: fietsboek/templates/profile_overview.jinja2:94
msgid "page.profile.max_speed"
msgstr ""
-#: fietsboek/templates/profile.jinja2:120
+#: fietsboek/templates/profile_overview.jinja2:98
msgid "page.profile.avg_speed"
msgstr ""
-#: fietsboek/templates/profile.jinja2:124
+#: fietsboek/templates/profile_overview.jinja2:102
msgid "page.profile.number_of_tracks"
msgstr ""
-#: fietsboek/templates/profile.jinja2:130
+#: fietsboek/templates/profile_overview.jinja2:108
msgid "page.profile.longest_distance_track"
msgstr ""
-#: fietsboek/templates/profile.jinja2:135
+#: fietsboek/templates/profile_overview.jinja2:113
msgid "page.profile.shortest_distance_track"
msgstr ""
-#: fietsboek/templates/profile.jinja2:140
+#: fietsboek/templates/profile_overview.jinja2:118
msgid "page.profile.longest_duration_track"
msgstr ""
-#: fietsboek/templates/profile.jinja2:145
+#: fietsboek/templates/profile_overview.jinja2:123
msgid "page.profile.shortest_duration_track"
msgstr ""
-#: fietsboek/templates/profile.jinja2:152
-msgid "page.profile.graph.km_per_month"
-msgstr ""
-
-#: fietsboek/templates/profile.jinja2:161
-msgid "page.profile.calendar.previous"
-msgstr ""
-
-#: fietsboek/templates/profile.jinja2:163
-msgid "page.profile.calendar.next"
-msgstr ""
-
-#: fietsboek/templates/profile.jinja2:218
+#: fietsboek/templates/profile_overview.jinja2:144
msgid "page.profile.heatmap"
msgstr ""
-#: fietsboek/templates/profile.jinja2:223
+#: fietsboek/templates/profile_overview.jinja2:152
msgid "page.profile.tilehunt"
msgstr ""
diff --git a/fietsboek/routes.py b/fietsboek/routes.py
index 325e942..a327f71 100644
--- a/fietsboek/routes.py
+++ b/fietsboek/routes.py
@@ -64,9 +64,17 @@ def includeme(config):
config.add_route("toggle-favourite", "/me/toggle-favourite")
config.add_route("force-logout", "/me/force-logout")
- config.add_route("profile", "/user/{user_id}", factory="fietsboek.models.User.factory")
config.add_route(
- "user-calendar-ym",
+ "profile-overview", "/user/{user_id}/", factory="fietsboek.models.User.factory"
+ )
+ config.add_route(
+ "profile-graphs", "/user/{user_id}/graphs", factory="fietsboek.models.User.factory"
+ )
+ config.add_route(
+ "profile-calendar", "/user/{user_id}/calendar/", factory="fietsboek.models.User.factory"
+ )
+ config.add_route(
+ "profile-calendar-ym",
"/user/{user_id}/calendar/{year}/{month}",
factory="fietsboek.models.User.factory",
)
diff --git a/fietsboek/static/DeadEnd.svg b/fietsboek/static/DeadEnd.svg
new file mode 100644
index 0000000..b65f171
--- /dev/null
+++ b/fietsboek/static/DeadEnd.svg
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ viewBox="0 0 421.0267 630.72443"
+ height="630.72443"
+ width="421.0267"
+ xml:space="preserve"
+ id="svg5733"
+ version="1.1"><metadata
+ id="metadata5739"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs5737" /><g
+ transform="matrix(1.3333333,0,0,-1.3333333,0,630.72445)"
+ id="g5741"><g
+ id="g5743"><path
+ id="path5745"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+ d="M 21.594,0.375 H 294.59 c 20.906,0 20.805,17.051 20.805,17.051 v 438.41 c 0,0 0.136,16.828 -17.977,16.828 H 18.945 c 0,0 -18.531,0.309 -18.57,-18.507 V 19.11 C 0.375,0.328 21.594,0.375 21.594,0.375 Z" /><path
+ id="path5747"
+ style="fill:#154889;fill-opacity:1;fill-rule:evenodd;stroke:none"
+ d="m 18.422,8.723 h 279.019 c 0,0 10.153,0.027 10.153,10.27 V 454.86 c 0,0 -0.008,10.058 -11.235,10.058 H 19.504 c 0,0 -11.152,0 -11.152,-10.054 V 18.352 C 8.391,8.621 18.422,8.723 18.422,8.723 Z" /><path
+ id="path5749"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none"
+ d="m 18.945,473.043 c -0.007,0 -4.711,0.078 -9.433,-2.238 C 4.781,468.485 0.02,463.688 0,454.161 V 19.11 C 0,9.582 5.441,4.731 10.832,2.356 16.223,-0.019 21.594,0 21.594,0 H 294.59 c 10.555,0 15.918,4.348 18.562,8.707 2.645,4.356 2.618,8.715 2.618,8.715 v 438.414 c 0,0.004 0.035,4.293 -2.243,8.594 -2.277,4.297 -6.937,8.613 -16.109,8.613 z m 0,-0.379 h 278.473 c 18.113,0 17.977,-16.828 17.977,-16.828 V 17.422 c 0,0 0.101,-17.047 -20.805,-17.047 H 21.594 c 0,0 -21.219,-0.047 -21.219,18.731 v 435.051 c 0.039,18.816 18.57,18.507 18.57,18.507 z" /><path
+ id="path5751"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+ d="M 53.969,418.61 V 265.614 h 96.164 v -34.368 h -66 V 181.43 H 137.16 V 6.571 h 41.59 l -0.066,174.859 h 53.027 l -0.066,49.816 h -66 v 34.368 h 96.156 V 418.61 Z" /><path
+ id="path5753"
+ style="fill:#cc0000;fill-opacity:1;fill-rule:evenodd;stroke:none"
+ d="M 88.68,226.7 H 227.098 V 186.442 H 88.68 Z" /><path
+ id="path5755"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
+ d="m 165.359,390.379 c 0,-5.152 -4.175,-9.332 -9.332,-9.332 -5.152,0 -9.328,4.18 -9.328,9.332 0,5.153 4.176,9.332 9.328,9.332 5.157,0 9.332,-4.179 9.332,-9.332 z" /><path
+ id="path5757"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
+ d="m 128.367,336.723 c 25.278,39.441 22.879,40.543 32.328,40.824 5.5,-0.164 5.664,-1.664 14.996,-8.996 11.5,-8.449 10.664,-8.551 10.5,-17.832 -0.05,-11.164 0.715,-10.328 -8.168,-14.496 v 18.832 l -7.832,6.996 v -26.496 l -18.66,0.168 -0.168,20.996 c -15.332,-23.66 -14.332,-22.328 -22.996,-19.996 z" /><path
+ id="path5759"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
+ d="m 151.863,332.891 h 18.164 c 0,-4.168 1.332,-3.832 10.996,-21.996 10.165,-18.715 10.332,-17.445 4.5,-28.16 l -25.16,45.156 c -23.832,-46.105 -20.832,-44.707 -33.996,-43.824 l 13.164,24.66 c 11.282,20.383 11.883,20.449 12.332,24.164 z" /></g></g></svg> \ No newline at end of file
diff --git a/fietsboek/static/NoEntry.svg b/fietsboek/static/NoEntry.svg
new file mode 100644
index 0000000..1cc18a4
--- /dev/null
+++ b/fietsboek/static/NoEntry.svg
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" width="600.99628" height="600.99628">
+ <defs>
+ <path id="a" d="M23.809 456.512h.051v-.047h-.051v.047z"/>
+ <path id="b" d="M23.809 456.465v.047l.05-.047h-.05zm.05 0-.05.047v-.047h.05z"/>
+ <path id="c" d="M23.859 456.465h-.05.023v.047l.027-.047z"/>
+ <path id="d" d="M23.809 456.465v.047-.047l.05.047-.05-.047z"/>
+ <path id="e" d="M23.859 456.512v-.047h-.05l.05.047z"/>
+ </defs>
+ <g transform="matrix(1.25 0 0 -1.25 0 600.99628)">
+ <path fill="#fff" d="M480.398 240.399c0-132.551-107.449-240-240-240-132.55 0-240 107.449-240 240 0 132.55 107.45 240 240 240 132.551 0 240-107.45 240-240z"/>
+ <path fill="#c1121c" d="M240.402 472.402c-127.75 0-232-104.25-232-232s104.25-232 232-232c127.746 0 232 104.25 232 232s-104.254 232-232 232zm-208-196h416v-72h-416v72z"/>
+ <path d="M240.398 480.797C107.633 480.797 0 373.164 0 240.399 0 107.633 107.633 0 240.398 0c132.766 0 240.399 107.633 240.399 240.399 0 132.765-107.633 240.398-240.399 240.398zm0-.398c132.551 0 240-107.45 240-240 0-132.551-107.449-240-240-240-132.55 0-240 107.449-240 240 0 132.55 107.45 240 240 240zM23.832 456.512v-.024h-.023l.023.024z"/>
+ <path d="M23.809 456.512h.023v-.023h-.023v.023z"/>
+ <path d="M23.809 456.488v.024l.023-.024h-.023zm.023 0-.023.024v-.024h.023z"/>
+ <path d="M23.859 456.488h-.05.023v.024l.027-.024z"/>
+ <path d="M23.809 456.488v.024-.024l.023.024-.023-.024z"/>
+ <path d="M23.809 456.512h.023v-.023h-.023v.023zm.05 0v-.047h-.05l.05.047z"/>
+ <use xlink:href="#a"/>
+ <use xlink:href="#b"/>
+ <use xlink:href="#c"/>
+ <use xlink:href="#d"/>
+ <use xlink:href="#a"/>
+ <use xlink:href="#e"/>
+ <use xlink:href="#a"/>
+ <use xlink:href="#b"/>
+ <use xlink:href="#c"/>
+ <use xlink:href="#d"/>
+ <use xlink:href="#a"/>
+ <use xlink:href="#e"/>
+ <use xlink:href="#a"/>
+ <use xlink:href="#b"/>
+ <use xlink:href="#c"/>
+ <use xlink:href="#d"/>
+ <use xlink:href="#a"/>
+ </g>
+</svg> \ No newline at end of file
diff --git a/fietsboek/templates/403.jinja2 b/fietsboek/templates/403.jinja2
new file mode 100644
index 0000000..9f4478e
--- /dev/null
+++ b/fietsboek/templates/403.jinja2
@@ -0,0 +1,16 @@
+{% extends "layout.jinja2" %}
+
+{% block content %}
+<div class="container">
+ <h1>{{ _("403.title") }}</h1>
+ <div style="text-align: center;">
+ <img src="{{ request.static_url('fietsboek:static/NoEntry.svg') }}" style="width: min(100%, 300px); margin: auto;">
+ <p>
+ {{ _("403.no_access") }}
+ </p>
+ <p>
+ {{ _("403.try_log_in") }}
+ </p>
+ </div>
+</div>
+{% endblock content %}
diff --git a/fietsboek/templates/404.jinja2 b/fietsboek/templates/404.jinja2
index aaf1241..9c7cc72 100644
--- a/fietsboek/templates/404.jinja2
+++ b/fietsboek/templates/404.jinja2
@@ -1,8 +1,16 @@
{% extends "layout.jinja2" %}
{% block content %}
-<div class="content">
- <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter project</span></h1>
- <p class="lead"><span class="font-semi-bold">404</span> Page Not Found</p>
+<div class="container">
+ <h1>{{ _("404.title") }}</h1>
+ <div style="text-align: center;">
+ <img src="{{ request.static_url('fietsboek:static/DeadEnd.svg') }}" style="width: min(100%, 300px); margin: auto;">
+ <p>
+ {{ _("404.path_not_found") }}
+ </p>
+ <p>
+ {{ _("404.choose_different") }}
+ </p>
+ </div>
</div>
{% endblock content %}
diff --git a/fietsboek/templates/layout.jinja2 b/fietsboek/templates/layout.jinja2
index b61c359..c96716d 100644
--- a/fietsboek/templates/layout.jinja2
+++ b/fietsboek/templates/layout.jinja2
@@ -77,7 +77,7 @@ const Legende = false;
<a class="dropdown-item" href="{{ request.route_url('logout') }}">{{ _("page.navbar.logout") }}</a>
</li>
<li>
- <a class="dropdown-item" href="{{ request.route_url('profile', user_id=request.identity.id) }}">{{ _("page.navbar.profile") }}</a>
+ <a class="dropdown-item" href="{{ request.route_url('profile-overview', user_id=request.identity.id) }}">{{ _("page.navbar.profile") }}</a>
</li>
<li>
<a class="dropdown-item" href="{{ request.route_url('user-data') }}">{{ _("page.navbar.user_data") }}</a>
diff --git a/fietsboek/templates/profile.jinja2 b/fietsboek/templates/profile.jinja2
index 288db70..06386ac 100644
--- a/fietsboek/templates/profile.jinja2
+++ b/fietsboek/templates/profile.jinja2
@@ -1,258 +1,30 @@
{% extends "layout.jinja2" %}
-{% macro render_track_card(track) %}
- <div class="card mb-3">
- <h5 class="card-header">
- <a href="{{ request.route_url('details', track_id=track.id) }}">{{ track.title | default(track.date, true) }}</a>
- {% if track.text_tags() %}
- {% for tag in track.tags %}<span class="badge bg-info text-dark">{{ tag.tag }}</span> {% endfor %}
- {% endif %}
- </h5>
- <div class="card-body">
- <table class="table table-hover table-sm browse-summary">
- <tbody>
- <tr>
- <th scope="row">{{ _("page.details.date") }}</th>
- <td>{{ track.date | format_datetime }}</td>
- <th scope="row">{{ _("page.details.length") }}</th>
- <td>{{ (track.length / 1000) | round(2) | format_decimal }} km</td>
- </tr>
- <tr>
- <th scope="row">{{ _("page.details.start_time") }}</th>
- <td>{{ track.start_time | format_datetime }}</td>
- <th scope="row">{{ _("page.details.end_time") }}</th>
- <td>{{ track.end_time | format_datetime }}</td>
- </tr>
- <tr>
- <th scope="row">{{ _("page.details.uphill") }}</th>
- <td>{{ track.uphill | round(2) | format_decimal }} m</td>
- <th scope="row">{{ _("page.details.downhill") }}</th>
- <td>{{ track.downhill | round(2) | format_decimal }} m</td>
- </tr>
- <tr>
- <th scope="row">{{ _("page.details.moving_time") }}</th>
- <td>{{ track.moving_time }}</td>
- <th scope="row">{{ _("page.details.stopped_time") }}</th>
- <td>{{ track.stopped_time }}</td>
- </tr>
- <tr>
- <th scope="row">{{ _("page.details.max_speed") }}</th>
- <td>{{ mps_to_kph(track.max_speed) | round(2) | format_decimal }} km/h</td>
- <th scope="row">{{ _("page.details.avg_speed") }}</th>
- <td>{{ mps_to_kph(track.avg_speed) | round(2) | format_decimal }} km/h</td>
- </tr>
- </tbody>
- </table>
-
- <ul>
- <li>{{ track.owner.name }}</li>
- {% for user in track.tagged_people %}
- <li>{{ user.name }}</li>
- {% endfor %}
- </ul>
- </div>
- </div>
-{% endmacro %}
-
{% block content %}
<div class="container">
<h1>{{ user.name }}</h1>
<ul class="nav nav-tabs" id="profileTabbar" role="tablist">
<li class="nav-item" role="presentation">
- <button class="nav-link {% if not tab_focus or tab_focus == 'overview' %}active{% endif %}" id="tabOverviewButton" data-bs-toggle="tab" data-bs-target="#tabOverviewPane" type="button" role="tab" aria-controls="tabOverviewPane" aria-selected="{% if not tab_focus or tab_focus == 'overview' %}true{% else %}false{% endif %}">
+ <a class="nav-link {% if profile_index == 0 %}active{% endif %}" aria-selected="{% if profile_index == 0 %}true{% else %}false{% endif %}" href="{{ request.route_url('profile-overview', user_id=user.id) }}">
{{ _("page.profile.tabbar.overview") }}
- </button>
+ </a>
</li>
<li class="nav-item" role="presentation">
- <button class="nav-link {% if tab_focus == 'graph' %}active{% endif %}" id="tabGraphsButton" data-bs-toggle="tab" data-bs-target="#tabGraphsPane" type="button" role="tab" aria-controls="tabGraphsPane" aria-selected="{% if tab_focus == 'graph' %}true{% else %}false{% endif %}">
+ <a class="nav-link {% if profile_index == 1 %}active{% endif %}" aria-selected="{% if profile_index == 1 %}true{% else %}false{% endif %}" href="{{ request.route_url('profile-graphs', user_id=user.id) }}">
{{ _("page.profile.tabbar.graphs") }}
- </button>
+ </a>
</li>
<li class="nav-item" role="presentation">
- <button class="nav-link {% if tab_focus == 'calendar' %}active{% endif %}" id="tabCalendarButton" data-bs-toggle="tab" data-bs-target="#tabCalendarPane" type="button" role="tab" aria-controls="tabCalendarPane" aria-selected="{% if tab_focus == 'calendar' %}true{% else %}false{% endif %}">
+ <a class="nav-link {% if profile_index == 2 %}active{% endif %}" aria-selected="{% if profile_index == 2 %}true{% else %}false{% endif %}" href="{{ request.route_url('profile-calendar', user_id=user.id) }}">
{{ _("page.profile.tabbar.calendar") }}
- </button>
+ </a>
</li>
</ul>
<div class="tab-content">
- <!-- First tab -->
- <div class="tab-pane {% if not tab_focus or tab_focus == 'overview' %}show active{% endif %}" id="tabOverviewPane" role="tabpanel" aria-labelledby="tabOverviewButton">
- {% if heatmap_url or tilehunt_url %}
- <div id="userMap" style="height: 600px; width: 100%;"></div>
- {% endif %}
-
- <table class="table table-hover table-sm">
- <tr>
- <th scope="row">{{ _("page.profile.length") }}</th>
- <td id="profileLength">{{ (total.length / 1000) | round(2) | format_decimal }} km</td>
- </tr>
- <tr>
- <th scope="row">{{ _("page.profile.avg_length") }}</th>
- <td id="profileAvgLength">{{ (total.avg_length / 1000) | round(2) | format_decimal }} km</td>
- </tr>
- <tr>
- <th scope="row">{{ _("page.profile.uphill") }}</th>
- <td id="profileUphill">{{ total.uphill | round(2) | format_decimal }} m</td>
- </tr>
- <tr>
- <th scope="row">{{ _("page.profile.downhill") }}</th>
- <td id="profileDownhill">{{ total.downhill | round(2) | format_decimal }} m</td>
- </tr>
- <tr>
- <th scope="row">{{ _("page.profile.moving_time") }}</th>
- <td id="profileMovingTime">{{ total.moving_time }}</td>
- </tr>
- <tr>
- <th scope="row">{{ _("page.profile.stopped_time") }}</th>
- <td id="profileStoppedTime">{{ total.stopped_time }}</td>
- </tr>
- <tr>
- <th scope="row">{{ _("page.profile.avg_duration") }}</th>
- <td id="profileAvgDuration">{{ total.avg_duration }}</td>
- </tr>
- <tr>
- <th scope="row">{{ _("page.profile.max_speed") }}</th>
- <td id="profileMaxSpeed">{{ mps_to_kph(total.max_speed) | round(2) | format_decimal }} km/h</td>
- </tr>
- <tr>
- <th scope="row">{{ _("page.profile.avg_speed") }}</th>
- <td id="profileAvgSpeed">{{ mps_to_kph(total.avg_speed) | round(2) | format_decimal }} km/h</td>
- </tr>
- <tr>
- <th scope="row">{{ _("page.profile.number_of_tracks") }}</th>
- <td id="profileNumberOfTracks">{{ total.count }}</td>
- </tr>
- </table>
-
- {% if total.longest_distance_track %}
- <h2>{{ _("page.profile.longest_distance_track") }}</h2>
- {{ render_track_card(total.longest_distance_track) }}
- {% endif %}
-
- {% if total.shortest_distance_track %}
- <h2>{{ _("page.profile.shortest_distance_track") }}</h2>
- {{ render_track_card(total.shortest_distance_track) }}
- {% endif %}
-
- {% if total.longest_duration_track %}
- <h2>{{ _("page.profile.longest_duration_track") }}</h2>
- {{ render_track_card(total.longest_duration_track) }}
- {% endif %}
-
- {% if total.shortest_duration_track %}
- <h2>{{ _("page.profile.shortest_duration_track") }}</h2>
- {{ render_track_card(total.shortest_duration_track) }}
- {% endif %}
- </div>
-
- <!-- Second tab -->
- <div class="tab-pane {% if tab_focus == 'graph' %}show active{% endif %}" id="tabGraphsPane" role="tabpanel" aria-labelledby="tabGraphsButton">
- <h2 class="chart-title">{{ _("page.profile.graph.km_per_month") }}</h2>
- <div style="position: relative; height: 500px; width: 75%; margin: auto;"><canvas id="graph-month-summary"></canvas></div>
- </div>
-
- <!-- Third tab -->
- <div class="tab-pane {% if tab_focus == 'calendar' %}show active{% endif %}" id="tabCalendarPane" role="tabpanel" aria-labelledby="tabCalendarButton">
- <h2 class="calendar-title">{{ calendar_month | format_date("MMMM YYYY") }}</h2>
-
- <div class="calendar-controls">
- <a href="{{ request.route_url('user-calendar-ym', user_id=user.id, year=calendar_prev.year, month=calendar_prev.month) }}">{{ _("page.profile.calendar.previous") }}</a>
- |
- <a href="{{ request.route_url('user-calendar-ym', user_id=user.id, year=calendar_next.year, month=calendar_next.month) }}">{{ _("page.profile.calendar.next") }}</a>
- </div>
-
- <table class="profile-calendar">
- <thead>
- <tr>
- {% for day in range(7) %}
- <td>{{ day_name(request, day) }}</td>
- {% endfor %}
- </tr>
- </thead>
- {% for row in calendar_rows %}
- <tr>
- {% for cell in row %}
- {% if cell %}
- {% set day, style, tracks = cell %}
- <td class="calendar-cell {{ style }}">
- <p class="calendar-date">{{ day.day }}</p>
- {% if tracks %}
- <ul>
- {% for track in tracks %}
- <li><a href="{{ request.route_url('details', track_id=track.id) }}">{{ (track.length / 1000) | round(2) | format_decimal }} km</a></li>
- {% endfor %}
- </ul>
- {% endif %}
- </td>
- {% else %}
- <td class="calendar-cell-empty"></td>
- {% endif %}
- {% endfor %}
- </tr>
- {% endfor %}
- </table>
- </div>
+ {% block profile_content %}
+ {% endblock %}
</div>
-
</div>
{% endblock %}
-
-{% block latescripts %}
-<script>
- (function() {
- loadProfileStats();
-
- const renderMap = document.getElementById("userMap") !== null;
- if (!renderMap) {
- return;
- }
-
- const map = L.map("userMap").setView([52.520008, 13.404954], 2);
-
- baseLayers = {};
- overlayLayers = {};
-
- {% if heatmap_url %}
- overlayLayers[{{ _("page.profile.heatmap") | tojson }}] = L.tileLayer({{ heatmap_url | tojson }}, {
- maxZoom: 19,
- });
- {% endif %}
- {% if tilehunt_url %}
- overlayLayers[{{ _("page.profile.tilehunt") | tojson }}] = L.tileLayer({{ tilehunt_url | tojson }}, {
- maxZoom: 19,
- });
- {% endif %}
-
- let defaultLayer = null;
-
- for (let layer of TILE_LAYERS) {
- if (layer.type === "base") {
- baseLayers[layer.name] = L.tileLayer(layer.url, {
- maxZoom: layer.zoom,
- attribution: layer.attribution,
- });
- if (defaultLayer === null) {
- defaultLayer = baseLayers[layer.name];
- }
- } else if (layer.type === "overlay") {
- overlayLayers[layer.name] = L.tileLayer(layer.url, {
- attribution: layer.attribution,
- });
- }
- }
-
-
- // Add the default layer via .addTo directly, otherwise it will not be
- // selected at the start.
- defaultLayer.addTo(map);
- L.control.layers(baseLayers, overlayLayers).addTo(map);
-
- // Fix leaflet being all weird if it's loaded on a hidden tab
- document.querySelector("#tabOverviewButton").addEventListener("shown.bs.tab", event => {
- map.invalidateSize();
- });
- })();
-</script>
-{% endblock %}
diff --git a/fietsboek/templates/profile_calendar.jinja2 b/fietsboek/templates/profile_calendar.jinja2
new file mode 100644
index 0000000..03a82db
--- /dev/null
+++ b/fietsboek/templates/profile_calendar.jinja2
@@ -0,0 +1,44 @@
+{% set profile_index = 2 %}
+{% extends "profile.jinja2" %}
+
+{% block profile_content %}
+<!-- Third tab -->
+<h2 class="calendar-title">{{ calendar_month | format_date("MMMM YYYY") }}</h2>
+
+<div class="calendar-controls">
+ <a href="{{ request.route_url('profile-calendar-ym', user_id=user.id, year=calendar_prev.year, month=calendar_prev.month) }}">{{ _("page.profile.calendar.previous") }}</a>
+ |
+ <a href="{{ request.route_url('profile-calendar-ym', user_id=user.id, year=calendar_next.year, month=calendar_next.month) }}">{{ _("page.profile.calendar.next") }}</a>
+</div>
+
+<table class="profile-calendar">
+ <thead>
+ <tr>
+ {% for day in range(7) %}
+ <td>{{ day_name(request, day) }}</td>
+ {% endfor %}
+ </tr>
+ </thead>
+ {% for row in calendar_rows %}
+ <tr>
+ {% for cell in row %}
+ {% if cell %}
+ {% set day, style, tracks = cell %}
+ <td class="calendar-cell {{ style }}">
+ <p class="calendar-date">{{ day.day }}</p>
+ {% if tracks %}
+ <ul>
+ {% for track in tracks %}
+ <li><a href="{{ request.route_url('details', track_id=track.id) }}" title="{{ track.title }}">{{ (track.length / 1000) | round(2) | format_decimal }} km</a></li>
+ {% endfor %}
+ </ul>
+ {% endif %}
+ </td>
+ {% else %}
+ <td class="calendar-cell-empty"></td>
+ {% endif %}
+ {% endfor %}
+ </tr>
+ {% endfor %}
+</table>
+{% endblock %}
diff --git a/fietsboek/templates/profile_graphs.jinja2 b/fietsboek/templates/profile_graphs.jinja2
new file mode 100644
index 0000000..4a9e474
--- /dev/null
+++ b/fietsboek/templates/profile_graphs.jinja2
@@ -0,0 +1,16 @@
+{% set profile_index = 1 %}
+{% extends "profile.jinja2" %}
+
+{% block profile_content %}
+<!-- Second tab -->
+<h2 class="chart-title">{{ _("page.profile.graph.km_per_month") }}</h2>
+<div style="position: relative; height: 500px; width: 75%; margin: auto;"><canvas id="graph-month-summary"></canvas></div>
+{% endblock %}
+
+{% block latescripts %}
+<script>
+ (function() {
+ loadProfileStats();
+ })();
+</script>
+{% endblock %}
diff --git a/fietsboek/templates/profile_overview.jinja2 b/fietsboek/templates/profile_overview.jinja2
new file mode 100644
index 0000000..aa2333c
--- /dev/null
+++ b/fietsboek/templates/profile_overview.jinja2
@@ -0,0 +1,188 @@
+{% set profile_index = 0 %}
+{% extends "profile.jinja2" %}
+
+{% macro render_track_card(track) %}
+ <div class="card mb-3">
+ <h5 class="card-header">
+ <a href="{{ request.route_url('details', track_id=track.id) }}">{{ track.title | default(track.date, true) }}</a>
+ {% if track.text_tags() %}
+ {% for tag in track.tags %}<span class="badge bg-info text-dark">{{ tag.tag }}</span> {% endfor %}
+ {% endif %}
+ </h5>
+ <div class="card-body">
+ <table class="table table-hover table-sm browse-summary">
+ <tbody>
+ <tr>
+ <th scope="row">{{ _("page.details.date") }}</th>
+ <td>{{ track.date | format_datetime }}</td>
+ <th scope="row">{{ _("page.details.length") }}</th>
+ <td>{{ (track.length / 1000) | round(2) | format_decimal }} km</td>
+ </tr>
+ <tr>
+ <th scope="row">{{ _("page.details.start_time") }}</th>
+ <td>{{ track.start_time | format_datetime }}</td>
+ <th scope="row">{{ _("page.details.end_time") }}</th>
+ <td>{{ track.end_time | format_datetime }}</td>
+ </tr>
+ <tr>
+ <th scope="row">{{ _("page.details.uphill") }}</th>
+ <td>{{ track.uphill | round(2) | format_decimal }} m</td>
+ <th scope="row">{{ _("page.details.downhill") }}</th>
+ <td>{{ track.downhill | round(2) | format_decimal }} m</td>
+ </tr>
+ <tr>
+ <th scope="row">{{ _("page.details.moving_time") }}</th>
+ <td>{{ track.moving_time }}</td>
+ <th scope="row">{{ _("page.details.stopped_time") }}</th>
+ <td>{{ track.stopped_time }}</td>
+ </tr>
+ <tr>
+ <th scope="row">{{ _("page.details.max_speed") }}</th>
+ <td>{{ mps_to_kph(track.max_speed) | round(2) | format_decimal }} km/h</td>
+ <th scope="row">{{ _("page.details.avg_speed") }}</th>
+ <td>{{ mps_to_kph(track.avg_speed) | round(2) | format_decimal }} km/h</td>
+ </tr>
+ </tbody>
+ </table>
+
+ <ul>
+ <li>{{ track.owner.name }}</li>
+ {% for user in track.tagged_people %}
+ <li>{{ user.name }}</li>
+ {% endfor %}
+ </ul>
+ </div>
+ </div>
+{% endmacro %}
+
+{% block profile_content %}
+<!-- First tab -->
+{% if heatmap_url or tilehunt_url %}
+<div id="userMap" style="height: 600px; width: 100%;"></div>
+{% endif %}
+
+<table class="table table-hover table-sm">
+ <tr>
+ <th scope="row">{{ _("page.profile.length") }}</th>
+ <td id="profileLength">{{ (total.length / 1000) | round(2) | format_decimal }} km</td>
+ </tr>
+ <tr>
+ <th scope="row">{{ _("page.profile.avg_length") }}</th>
+ <td id="profileAvgLength">{{ (total.avg_length / 1000) | round(2) | format_decimal }} km</td>
+ </tr>
+ <tr>
+ <th scope="row">{{ _("page.profile.uphill") }}</th>
+ <td id="profileUphill">{{ total.uphill | round(2) | format_decimal }} m</td>
+ </tr>
+ <tr>
+ <th scope="row">{{ _("page.profile.downhill") }}</th>
+ <td id="profileDownhill">{{ total.downhill | round(2) | format_decimal }} m</td>
+ </tr>
+ <tr>
+ <th scope="row">{{ _("page.profile.moving_time") }}</th>
+ <td id="profileMovingTime">{{ total.moving_time }}</td>
+ </tr>
+ <tr>
+ <th scope="row">{{ _("page.profile.stopped_time") }}</th>
+ <td id="profileStoppedTime">{{ total.stopped_time }}</td>
+ </tr>
+ <tr>
+ <th scope="row">{{ _("page.profile.avg_duration") }}</th>
+ <td id="profileAvgDuration">{{ total.avg_duration }}</td>
+ </tr>
+ <tr>
+ <th scope="row">{{ _("page.profile.max_speed") }}</th>
+ <td id="profileMaxSpeed">{{ mps_to_kph(total.max_speed) | round(2) | format_decimal }} km/h</td>
+ </tr>
+ <tr>
+ <th scope="row">{{ _("page.profile.avg_speed") }}</th>
+ <td id="profileAvgSpeed">{{ mps_to_kph(total.avg_speed) | round(2) | format_decimal }} km/h</td>
+ </tr>
+ <tr>
+ <th scope="row">{{ _("page.profile.number_of_tracks") }}</th>
+ <td id="profileNumberOfTracks">{{ total.count }}</td>
+ </tr>
+</table>
+
+{% if total.longest_distance_track %}
+<h2>{{ _("page.profile.longest_distance_track") }}</h2>
+{{ render_track_card(total.longest_distance_track) }}
+{% endif %}
+
+{% if total.shortest_distance_track %}
+<h2>{{ _("page.profile.shortest_distance_track") }}</h2>
+{{ render_track_card(total.shortest_distance_track) }}
+{% endif %}
+
+{% if total.longest_duration_track %}
+<h2>{{ _("page.profile.longest_duration_track") }}</h2>
+{{ render_track_card(total.longest_duration_track) }}
+{% endif %}
+
+{% if total.shortest_duration_track %}
+<h2>{{ _("page.profile.shortest_duration_track") }}</h2>
+{{ render_track_card(total.shortest_duration_track) }}
+{% endif %}
+{% endblock %}
+
+{% block latescripts %}
+<script>
+ (function() {
+ const renderMap = document.getElementById("userMap") !== null;
+ if (!renderMap) {
+ return;
+ }
+
+ const map = L.map("userMap").setView([52.520008, 13.404954], 2);
+
+ baseLayers = {};
+ overlayLayers = {};
+
+ let defaultOverlayLayer = null;
+ let layer = null;
+ {% if heatmap_url %}
+ layer = overlayLayers[{{ _("page.profile.heatmap") | tojson }}] = L.tileLayer({{ heatmap_url | tojson }}, {
+ maxZoom: 19,
+ });
+ if (defaultOverlayLayer === null) {
+ defaultOverlayLayer = layer;
+ }
+ {% endif %}
+ {% if tilehunt_url %}
+ layer = overlayLayers[{{ _("page.profile.tilehunt") | tojson }}] = L.tileLayer({{ tilehunt_url | tojson }}, {
+ maxZoom: 19,
+ });
+ if (defaultOverlayLayer === null) {
+ defaultOverlayLayer = layer;
+ }
+ {% endif %}
+
+ let defaultLayer = null;
+
+ for (let layer of TILE_LAYERS) {
+ if (layer.type === "base") {
+ baseLayers[layer.name] = L.tileLayer(layer.url, {
+ maxZoom: layer.zoom,
+ attribution: layer.attribution,
+ });
+ if (defaultLayer === null) {
+ defaultLayer = baseLayers[layer.name];
+ }
+ } else if (layer.type === "overlay") {
+ overlayLayers[layer.name] = L.tileLayer(layer.url, {
+ attribution: layer.attribution,
+ });
+ }
+ }
+
+
+ // Add the default layer via .addTo directly, otherwise it will not be
+ // selected at the start.
+ defaultLayer.addTo(map);
+ if (defaultOverlayLayer != null) {
+ defaultOverlayLayer.addTo(map);
+ }
+ L.control.layers(baseLayers, overlayLayers).addTo(map);
+ })();
+</script>
+{% endblock %}
diff --git a/fietsboek/views/errors.py b/fietsboek/views/errors.py
new file mode 100644
index 0000000..39af14e
--- /dev/null
+++ b/fietsboek/views/errors.py
@@ -0,0 +1,32 @@
+"""Error views."""
+
+from pyramid.view import forbidden_view_config, notfound_view_config
+
+
+@notfound_view_config(renderer="fietsboek:templates/404.jinja2")
+def notfound_view(request):
+ """Renders the 404 response.
+
+ :param request: The Pyramid request.
+ :type request: pyramid.request.Request
+ :return: The HTTP response.
+ :rtype: pyramid.response.Response
+ """
+ request.response.status = 404
+ return {}
+
+
+@forbidden_view_config(renderer="fietsboek:templates/403.jinja2")
+def forbidden_view(request):
+ """Renders the 403 response.
+
+ :param request: The Pyramid request.
+ :type request: pyramid.request.Request
+ :return: The HTTP response.
+ :rtype: pyramid.response.Response
+ """
+ request.response.status = 403
+ return {}
+
+
+__all__ = ["notfound_view", "forbidden_view"]
diff --git a/fietsboek/views/notfound.py b/fietsboek/views/notfound.py
deleted file mode 100644
index 2ec6c6c..0000000
--- a/fietsboek/views/notfound.py
+++ /dev/null
@@ -1,19 +0,0 @@
-"""Error views."""
-
-from pyramid.view import notfound_view_config
-
-
-@notfound_view_config(renderer="fietsboek:templates/404.jinja2")
-def notfound_view(request):
- """Renders the 404 response.
-
- :param request: The Pyramid request.
- :type request: pyramid.request.Request
- :return: The HTTP response.
- :rtype: pyramid.response.Response
- """
- request.response.status = 404
- return {}
-
-
-__all__ = ["notfound_view"]
diff --git a/fietsboek/views/profile.py b/fietsboek/views/profile.py
index bf604b5..15bc46c 100644
--- a/fietsboek/views/profile.py
+++ b/fietsboek/views/profile.py
@@ -83,46 +83,82 @@ def profile_data(request: Request) -> dict:
@view_config(
- route_name="profile",
- renderer="fietsboek:templates/profile.jinja2",
+ route_name="profile-overview",
+ renderer="fietsboek:templates/profile_overview.jinja2",
request_method="GET",
permission="profile.view",
)
-def profile(request: Request) -> dict:
+def profile_overview(request: Request) -> dict:
"""Shows the profile page.
:param request: The pyramid request.
:return: The template parameters.
"""
data = profile_data(request)
- today = datetime.date.today()
+ return data
+
+
+@view_config(
+ route_name="profile-graphs",
+ renderer="fietsboek:templates/profile_graphs.jinja2",
+ request_method="GET",
+ permission="profile.view",
+)
+def profile_graphs(request: Request) -> dict:
+ """Shows the user's graphs.
+
+ :param request: The pyramid request.
+ :return: The template parameters.
+ """
+ return {
+ "user": request.context,
+ }
+
+
+@view_config(
+ route_name="profile-calendar",
+ renderer="fietsboek:templates/profile_calendar.jinja2",
+ request_method="GET",
+ permission="profile.view",
+)
+def profile_calendar(request: Request) -> dict:
+ """Shows the user's calendar for the current month.
+
+ :param request: The pyramid request.
+ :return: The template parameters.
+ """
+ date = datetime.date.today()
+ data = {}
+ data["user"] = request.context
data["calendar_rows"] = calendar_rows(
request.dbsession,
request.data_manager,
request.context,
- today.year,
- today.month,
+ date.year,
+ date.month,
)
- data["calendar_month"] = today
- data["calendar_prev"], data["calendar_next"] = prev_next_month(today)
+ data["calendar_month"] = date
+ data["calendar_prev"], data["calendar_next"] = prev_next_month(date)
+ data["tab_focus"] = "calendar"
data["day_name"] = util.day_name
return data
@view_config(
- route_name="user-calendar-ym",
- renderer="fietsboek:templates/profile.jinja2",
+ route_name="profile-calendar-ym",
+ renderer="fietsboek:templates/profile_calendar.jinja2",
request_method="GET",
permission="profile.view",
)
-def user_calendar_ym(request: Request) -> dict:
+def profile_calendar_ym(request: Request) -> dict:
"""Shows the user's calendar.
:param request: The pyramid request.
:return: The template parameters.
"""
- data = profile_data(request)
date = datetime.date(int(request.matchdict["year"]), int(request.matchdict["month"]), 1)
+ data = {}
+ data["user"] = request.context
data["calendar_rows"] = calendar_rows(
request.dbsession,
request.data_manager,
@@ -309,4 +345,12 @@ def json_summary(request: Request) -> Response:
return {y.year: {m.month: m.total_length for m in y} for y in summary}
-__all__ = ["EMPTY_TILE", "profile", "user_tile", "user_calendar_ym", "json_summary"]
+__all__ = [
+ "EMPTY_TILE",
+ "profile_overview",
+ "profile_graphs",
+ "profile_calendar",
+ "profile_calendar_ym",
+ "user_tile",
+ "json_summary",
+]
diff --git a/tests/playwright/test_profiles.py b/tests/playwright/test_profiles.py
index 7e5fb3c..ffbaab0 100644
--- a/tests/playwright/test_profiles.py
+++ b/tests/playwright/test_profiles.py
@@ -5,7 +5,7 @@ def test_forbidden(page: Page, playwright_helper):
john = playwright_helper.john_doe()
with page.expect_response(lambda resp: resp.status == 403):
- page.goto(f"/user/{john.id}")
+ page.goto(f"/user/{john.id}/")
def test_profile(page: Page, playwright_helper):