diff options
-rw-r--r-- | doc/index.rst | 3 | ||||
-rw-r--r-- | fietsboek/locale/de/LC_MESSAGES/messages.mo | bin | 17256 -> 17659 bytes | |||
-rw-r--r-- | fietsboek/locale/de/LC_MESSAGES/messages.po | 108 | ||||
-rw-r--r-- | fietsboek/locale/en/LC_MESSAGES/messages.mo | bin | 16183 -> 16586 bytes | |||
-rw-r--r-- | fietsboek/locale/en/LC_MESSAGES/messages.po | 108 | ||||
-rw-r--r-- | fietsboek/locale/fietslog.pot | 108 | ||||
-rw-r--r-- | fietsboek/routes.py | 12 | ||||
-rw-r--r-- | fietsboek/static/DeadEnd.svg | 44 | ||||
-rw-r--r-- | fietsboek/static/NoEntry.svg | 37 | ||||
-rw-r--r-- | fietsboek/templates/403.jinja2 | 16 | ||||
-rw-r--r-- | fietsboek/templates/404.jinja2 | 14 | ||||
-rw-r--r-- | fietsboek/templates/layout.jinja2 | 2 | ||||
-rw-r--r-- | fietsboek/templates/profile.jinja2 | 244 | ||||
-rw-r--r-- | fietsboek/templates/profile_calendar.jinja2 | 44 | ||||
-rw-r--r-- | fietsboek/templates/profile_graphs.jinja2 | 16 | ||||
-rw-r--r-- | fietsboek/templates/profile_overview.jinja2 | 188 | ||||
-rw-r--r-- | fietsboek/views/errors.py | 32 | ||||
-rw-r--r-- | fietsboek/views/notfound.py | 19 | ||||
-rw-r--r-- | fietsboek/views/profile.py | 70 | ||||
-rw-r--r-- | tests/playwright/test_profiles.py | 2 |
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 Binary files differindex c953f1d..f2d0f60 100644 --- a/fietsboek/locale/de/LC_MESSAGES/messages.mo +++ b/fietsboek/locale/de/LC_MESSAGES/messages.mo 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 Binary files differindex 15c503b..fd67c57 100644 --- a/fietsboek/locale/en/LC_MESSAGES/messages.mo +++ b/fietsboek/locale/en/LC_MESSAGES/messages.mo 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): |