diff options
24 files changed, 930 insertions, 246 deletions
diff --git a/asset-sources/theme.scss b/asset-sources/theme.scss index cfe8adb..ba05782 100644 --- a/asset-sources/theme.scss +++ b/asset-sources/theme.scss @@ -317,6 +317,26 @@ strong { text-align: center; } +/* Admin view layout: We have an extra sidebar for the navigation */ +#adminContainer { + display: grid; + grid-template-areas: "sidebar main"; + grid-template-columns: 1fr 5fr; + gap: 1rem; +} + +#adminNavigation { + grid-area: sidebar; +} + +#adminContent { + grid-area: main; +} + +.admin-stat { + font-size: 120%; +} + .list-group.list-group-root { padding: 0; overflow: hidden; diff --git a/fietsboek/data.py b/fietsboek/data.py index d1268bf..9d5a133 100644 --- a/fietsboek/data.py +++ b/fietsboek/data.py @@ -9,7 +9,6 @@ the database itself. This module makes access to such data objects easier. # pylint: disable=deprecated-argument import datetime import logging -import os import random import shutil import string @@ -130,6 +129,27 @@ class DataManager: raise FileNotFoundError(f"The path {path} is not a directory") from None return UserDataDir(user_id, path) + def size(self) -> int: + """Returns the size of all data. + + :return: The size of all data in bytes. + """ + return util.recursive_size(self.data_dir) + + def list_tracks(self) -> list[int]: + """Returns a list of all tracks. + + :return: A list of all track IDs. + """ + return [int(track.name) for track in self._track_data_dir(".").iterdir()] + + def list_users(self) -> list[int]: + """Returns a list of all users. + + :return: A list of all user IDs. + """ + return [int(user.name) for user in self._user_data_dir(".").iterdir()] + class TrackDataDir: """Manager for a single track's data. @@ -261,10 +281,7 @@ class TrackDataDir: :return: The size of bytes that this track consumes. """ - size = 0 - for root, _, files in os.walk(self.path): - size += sum(os.path.getsize(os.path.join(root, fname)) for fname in files) - return size + return util.recursive_size(self.path) def gpx_path(self) -> Path: """Returns the path of the GPX file. diff --git a/fietsboek/locale/de/LC_MESSAGES/messages.mo b/fietsboek/locale/de/LC_MESSAGES/messages.mo Binary files differindex 9de152c..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 ccb46a3..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-01-30 21:50+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" @@ -29,11 +29,11 @@ msgstr "" "\n" "Falls Du kein Konto angelegt hast, ignoriere diese E-Mail." -#: fietsboek/util.py:333 +#: fietsboek/util.py:334 msgid "password_constraint.mismatch" msgstr "Passwörter stimmen nicht überein" -#: fietsboek/util.py:335 +#: fietsboek/util.py:336 msgid "password_constraint.length" msgstr "Passwort zu kurz" @@ -69,34 +69,126 @@ 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" -#: fietsboek/templates/admin.jinja2:7 +#: fietsboek/templates/admin.jinja2:10 +msgid "page.admin.nav.overview" +msgstr "Übersicht" + +#: fietsboek/templates/admin.jinja2:11 +msgid "page.admin.nav.badges" +msgstr "Wappen" + +#: fietsboek/templates/admin_badges.jinja2:5 msgid "page.admin.badges" msgstr "Wappen" -#: fietsboek/templates/admin.jinja2:23 +#: fietsboek/templates/admin_badges.jinja2:21 msgid "page.admin.badge.edit" msgstr "Bearbeiten" -#: fietsboek/templates/admin.jinja2:29 +#: fietsboek/templates/admin_badges.jinja2:27 msgid "page.admin.badge.delete_badge" msgstr "Löschen" -#: fietsboek/templates/admin.jinja2:37 +#: fietsboek/templates/admin_badges.jinja2:35 msgid "page.admin.badges.badge_title" msgstr "Titel" -#: fietsboek/templates/admin.jinja2:41 +#: fietsboek/templates/admin_badges.jinja2:39 msgid "page.admin.badges.badge_image" msgstr "Bild" -#: fietsboek/templates/admin.jinja2:45 +#: fietsboek/templates/admin_badges.jinja2:43 msgid "page.admin.badges.add_badge" msgstr "Hinzufügen" +#: fietsboek/templates/admin_overview.jinja2:5 +msgid "admin.overview.instance_has" +msgstr "Diese Instanz hat" + +#: fietsboek/templates/admin_overview.jinja2:9 +msgid "admin.overview.stat.user" +msgid_plural "admin.overview.stat.users" +msgstr[0] "%(num)d Nutzer:in" +msgstr[1] "%(num)d Nutzer:innen" + +#: fietsboek/templates/admin_overview.jinja2:13 +msgid "admin.overview.stat.track" +msgid_plural "admin.overview.stat.tracks" +msgstr[0] "%(num)d Strecke" +msgstr[1] "%(num)d Strecken" + +#: fietsboek/templates/admin_overview.jinja2:17 +msgid "admin.overview.stats.mib" +msgstr "MiB an Daten" + +#: fietsboek/templates/admin_overview.jinja2:24 +msgid "admin.overview.system_overview" +msgstr "Systemübersicht" + +#: fietsboek/templates/admin_overview.jinja2:28 +msgid "admin.overview.fietsboek_version" +msgstr "Fietsboek-Version" + +#: fietsboek/templates/admin_overview.jinja2:32 +msgid "admin.overview.python_version" +msgstr "Python-Version" + +#: fietsboek/templates/admin_overview.jinja2:36 +msgid "admin.overview.kernel_version" +msgstr "Kernel-Version" + +#: fietsboek/templates/admin_overview.jinja2:40 +msgid "admin.overview.distro_version" +msgstr "Distribution" + +#: fietsboek/templates/admin_overview.jinja2:44 +msgid "admin.overview.last_cronjob" +msgstr "Letzter Cronjob" + +#: fietsboek/templates/admin_overview.jinja2:55 +msgid "admin.overview.storage_graph.label.gpx" +msgstr "GPX" + +#: fietsboek/templates/admin_overview.jinja2:56 +msgid "admin.overview.storage_graph.label.images" +msgstr "Bilder" + +#: fietsboek/templates/admin_overview.jinja2:57 +msgid "admin.overview.storage_graph.label.user_maps" +msgstr "Nutzerkarten" + +#: fietsboek/templates/admin_overview.jinja2:82 +msgid "admin.overview.storage_graph.title" +msgstr "Speicherübersicht" + #: fietsboek/templates/browse.jinja2:4 msgid "page.browse.title" msgstr "Stöbern" @@ -194,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" @@ -544,13 +636,13 @@ msgstr "" "Links, um sie fortzusetzen:" #: fietsboek/templates/home.jinja2:44 fietsboek/templates/home.jinja2:53 -#: fietsboek/templates/home.jinja2:97 +#: fietsboek/templates/home.jinja2:99 msgid "page.home.summary.track" msgid_plural "page.home.summary.tracks" msgstr[0] "%(num)d Strecke" msgstr[1] "%(num)d Strecken" -#: fietsboek/templates/home.jinja2:97 +#: fietsboek/templates/home.jinja2:99 msgid "page.home.total" msgstr "Gesamt" @@ -654,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" @@ -888,15 +980,15 @@ msgstr "Ungültige E-Mail-Adresse" msgid "flash.a_confirmation_link_has_been_sent" msgstr "Ein Bestätigungslink wurde versandt" -#: fietsboek/views/admin.py:49 +#: fietsboek/views/admin.py:157 msgid "flash.badge_added" msgstr "Wappen hinzugefügt" -#: fietsboek/views/admin.py:73 +#: fietsboek/views/admin.py:181 msgid "flash.badge_modified" msgstr "Wappen bearbeitet" -#: fietsboek/views/admin.py:93 +#: fietsboek/views/admin.py:201 msgid "flash.badge_deleted" msgstr "Wappen gelöscht" diff --git a/fietsboek/locale/en/LC_MESSAGES/messages.mo b/fietsboek/locale/en/LC_MESSAGES/messages.mo Binary files differindex 5f8edc6..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 981d134..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-01-30 21:50+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" @@ -29,11 +29,11 @@ msgstr "" "\n" "If you did not create an account, ignore this email." -#: fietsboek/util.py:333 +#: fietsboek/util.py:334 msgid "password_constraint.mismatch" msgstr "Passwords don't match" -#: fietsboek/util.py:335 +#: fietsboek/util.py:336 msgid "password_constraint.length" msgstr "Password not long enough" @@ -69,34 +69,126 @@ 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" -#: fietsboek/templates/admin.jinja2:7 +#: fietsboek/templates/admin.jinja2:10 +msgid "page.admin.nav.overview" +msgstr "Overview" + +#: fietsboek/templates/admin.jinja2:11 +msgid "page.admin.nav.badges" +msgstr "Badges" + +#: fietsboek/templates/admin_badges.jinja2:5 msgid "page.admin.badges" msgstr "Badges" -#: fietsboek/templates/admin.jinja2:23 +#: fietsboek/templates/admin_badges.jinja2:21 msgid "page.admin.badge.edit" msgstr "Edit" -#: fietsboek/templates/admin.jinja2:29 +#: fietsboek/templates/admin_badges.jinja2:27 msgid "page.admin.badge.delete_badge" msgstr "Delete badge" -#: fietsboek/templates/admin.jinja2:37 +#: fietsboek/templates/admin_badges.jinja2:35 msgid "page.admin.badges.badge_title" msgstr "Badge Title" -#: fietsboek/templates/admin.jinja2:41 +#: fietsboek/templates/admin_badges.jinja2:39 msgid "page.admin.badges.badge_image" msgstr "Badge Image" -#: fietsboek/templates/admin.jinja2:45 +#: fietsboek/templates/admin_badges.jinja2:43 msgid "page.admin.badges.add_badge" msgstr "Add Badge" +#: fietsboek/templates/admin_overview.jinja2:5 +msgid "admin.overview.instance_has" +msgstr "This instance has" + +#: fietsboek/templates/admin_overview.jinja2:9 +msgid "admin.overview.stat.user" +msgid_plural "admin.overview.stat.users" +msgstr[0] "%(num)d user" +msgstr[1] "%(num)d users" + +#: fietsboek/templates/admin_overview.jinja2:13 +msgid "admin.overview.stat.track" +msgid_plural "admin.overview.stat.tracks" +msgstr[0] "%(num)d track" +msgstr[1] "%(num)d tracks" + +#: fietsboek/templates/admin_overview.jinja2:17 +msgid "admin.overview.stats.mib" +msgstr "MiB of data" + +#: fietsboek/templates/admin_overview.jinja2:24 +msgid "admin.overview.system_overview" +msgstr "System information" + +#: fietsboek/templates/admin_overview.jinja2:28 +msgid "admin.overview.fietsboek_version" +msgstr "Fietsboek version" + +#: fietsboek/templates/admin_overview.jinja2:32 +msgid "admin.overview.python_version" +msgstr "Python version" + +#: fietsboek/templates/admin_overview.jinja2:36 +msgid "admin.overview.kernel_version" +msgstr "Linux version" + +#: fietsboek/templates/admin_overview.jinja2:40 +msgid "admin.overview.distro_version" +msgstr "Distribution" + +#: fietsboek/templates/admin_overview.jinja2:44 +msgid "admin.overview.last_cronjob" +msgstr "Last cronjob" + +#: fietsboek/templates/admin_overview.jinja2:55 +msgid "admin.overview.storage_graph.label.gpx" +msgstr "GPX" + +#: fietsboek/templates/admin_overview.jinja2:56 +msgid "admin.overview.storage_graph.label.images" +msgstr "Images" + +#: fietsboek/templates/admin_overview.jinja2:57 +msgid "admin.overview.storage_graph.label.user_maps" +msgstr "User maps" + +#: fietsboek/templates/admin_overview.jinja2:82 +msgid "admin.overview.storage_graph.title" +msgstr "Storage breakdown" + #: fietsboek/templates/browse.jinja2:4 msgid "page.browse.title" msgstr "Browse" @@ -194,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" @@ -538,13 +630,13 @@ msgid "page.home.unfinished_uploads" msgstr "You have unfinished uploads. Click on the links below to resume them:" #: fietsboek/templates/home.jinja2:44 fietsboek/templates/home.jinja2:53 -#: fietsboek/templates/home.jinja2:97 +#: fietsboek/templates/home.jinja2:99 msgid "page.home.summary.track" msgid_plural "page.home.summary.tracks" msgstr[0] "%(num)d track" msgstr[1] "%(num)d tracks" -#: fietsboek/templates/home.jinja2:97 +#: fietsboek/templates/home.jinja2:99 msgid "page.home.total" msgstr "Total" @@ -648,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" @@ -878,15 +970,15 @@ msgstr "Invalid email" msgid "flash.a_confirmation_link_has_been_sent" msgstr "A confirmation link has been sent" -#: fietsboek/views/admin.py:49 +#: fietsboek/views/admin.py:157 msgid "flash.badge_added" msgstr "Badge has been added" -#: fietsboek/views/admin.py:73 +#: fietsboek/views/admin.py:181 msgid "flash.badge_modified" msgstr "Badge has been modified" -#: fietsboek/views/admin.py:93 +#: fietsboek/views/admin.py:201 msgid "flash.badge_deleted" msgstr "Badge has been deleted" diff --git a/fietsboek/locale/fietslog.pot b/fietsboek/locale/fietslog.pot index 60c77a5..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-01-30 21:50+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" @@ -25,11 +25,11 @@ msgstr "" msgid "email.verify.text" msgstr "" -#: fietsboek/util.py:333 +#: fietsboek/util.py:334 msgid "password_constraint.mismatch" msgstr "" -#: fietsboek/util.py:335 +#: fietsboek/util.py:336 msgid "password_constraint.length" msgstr "" @@ -65,34 +65,126 @@ 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 "" -#: fietsboek/templates/admin.jinja2:7 +#: fietsboek/templates/admin.jinja2:10 +msgid "page.admin.nav.overview" +msgstr "" + +#: fietsboek/templates/admin.jinja2:11 +msgid "page.admin.nav.badges" +msgstr "" + +#: fietsboek/templates/admin_badges.jinja2:5 msgid "page.admin.badges" msgstr "" -#: fietsboek/templates/admin.jinja2:23 +#: fietsboek/templates/admin_badges.jinja2:21 msgid "page.admin.badge.edit" msgstr "" -#: fietsboek/templates/admin.jinja2:29 +#: fietsboek/templates/admin_badges.jinja2:27 msgid "page.admin.badge.delete_badge" msgstr "" -#: fietsboek/templates/admin.jinja2:37 +#: fietsboek/templates/admin_badges.jinja2:35 msgid "page.admin.badges.badge_title" msgstr "" -#: fietsboek/templates/admin.jinja2:41 +#: fietsboek/templates/admin_badges.jinja2:39 msgid "page.admin.badges.badge_image" msgstr "" -#: fietsboek/templates/admin.jinja2:45 +#: fietsboek/templates/admin_badges.jinja2:43 msgid "page.admin.badges.add_badge" msgstr "" +#: fietsboek/templates/admin_overview.jinja2:5 +msgid "admin.overview.instance_has" +msgstr "" + +#: fietsboek/templates/admin_overview.jinja2:9 +msgid "admin.overview.stat.user" +msgid_plural "admin.overview.stat.users" +msgstr[0] "" +msgstr[1] "" + +#: fietsboek/templates/admin_overview.jinja2:13 +msgid "admin.overview.stat.track" +msgid_plural "admin.overview.stat.tracks" +msgstr[0] "" +msgstr[1] "" + +#: fietsboek/templates/admin_overview.jinja2:17 +msgid "admin.overview.stats.mib" +msgstr "" + +#: fietsboek/templates/admin_overview.jinja2:24 +msgid "admin.overview.system_overview" +msgstr "" + +#: fietsboek/templates/admin_overview.jinja2:28 +msgid "admin.overview.fietsboek_version" +msgstr "" + +#: fietsboek/templates/admin_overview.jinja2:32 +msgid "admin.overview.python_version" +msgstr "" + +#: fietsboek/templates/admin_overview.jinja2:36 +msgid "admin.overview.kernel_version" +msgstr "" + +#: fietsboek/templates/admin_overview.jinja2:40 +msgid "admin.overview.distro_version" +msgstr "" + +#: fietsboek/templates/admin_overview.jinja2:44 +msgid "admin.overview.last_cronjob" +msgstr "" + +#: fietsboek/templates/admin_overview.jinja2:55 +msgid "admin.overview.storage_graph.label.gpx" +msgstr "" + +#: fietsboek/templates/admin_overview.jinja2:56 +msgid "admin.overview.storage_graph.label.images" +msgstr "" + +#: fietsboek/templates/admin_overview.jinja2:57 +msgid "admin.overview.storage_graph.label.user_maps" +msgstr "" + +#: fietsboek/templates/admin_overview.jinja2:82 +msgid "admin.overview.storage_graph.title" +msgstr "" + #: fietsboek/templates/browse.jinja2:4 msgid "page.browse.title" msgstr "" @@ -190,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 "" @@ -532,13 +624,13 @@ msgid "page.home.unfinished_uploads" msgstr "" #: fietsboek/templates/home.jinja2:44 fietsboek/templates/home.jinja2:53 -#: fietsboek/templates/home.jinja2:97 +#: fietsboek/templates/home.jinja2:99 msgid "page.home.summary.track" msgid_plural "page.home.summary.tracks" msgstr[0] "" msgstr[1] "" -#: fietsboek/templates/home.jinja2:97 +#: fietsboek/templates/home.jinja2:99 msgid "page.home.total" msgstr "" @@ -642,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 "" @@ -866,15 +958,15 @@ msgstr "" msgid "flash.a_confirmation_link_has_been_sent" msgstr "" -#: fietsboek/views/admin.py:49 +#: fietsboek/views/admin.py:157 msgid "flash.badge_added" msgstr "" -#: fietsboek/views/admin.py:73 +#: fietsboek/views/admin.py:181 msgid "flash.badge_modified" msgstr "" -#: fietsboek/views/admin.py:93 +#: fietsboek/views/admin.py:201 msgid "flash.badge_deleted" msgstr "" diff --git a/fietsboek/routes.py b/fietsboek/routes.py index f1d4ee2..4286e92 100644 --- a/fietsboek/routes.py +++ b/fietsboek/routes.py @@ -54,10 +54,11 @@ def includeme(config): config.add_route("badge", "/badge/{badge_id}", factory="fietsboek.models.Badge.factory") - config.add_route("admin", "/admin") - config.add_route("admin-badge-add", "/admin/add-badge") - config.add_route("admin-badge-edit", "/admin/edit-badge") - config.add_route("admin-badge-delete", "/admin/delete-badge") + config.add_route("admin", "/admin/") + config.add_route("admin-badge", "/admin/badges/") + config.add_route("admin-badge-add", "/admin/badges/add") + config.add_route("admin-badge-edit", "/admin/badges/edit") + config.add_route("admin-badge-delete", "/admin/badges/delete") config.add_route("user-data", "/me") config.add_route("add-friend", "/me/send-friend-request") diff --git a/fietsboek/scripts/fietscron.py b/fietsboek/scripts/fietscron.py index 68b731f..a3f3f54 100644 --- a/fietsboek/scripts/fietscron.py +++ b/fietsboek/scripts/fietscron.py @@ -58,9 +58,12 @@ def cli(config): rebuild_cache(engine, data_manager) build_previews(engine, data_manager, redis, config) + redis = mod_redis.from_url(config.redis_url) if config.hittekaart_autogenerate: run_hittekaart(engine, data_manager, redis, config) + redis.set("last-cronjob", datetime.datetime.now(datetime.UTC).timestamp()) + def remove_old_uploads(engine: Engine): """Removes old uploads from the database.""" 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/static/theme.css b/fietsboek/static/theme.css index 0e932dd..65e6881 100644 --- a/fietsboek/static/theme.css +++ b/fietsboek/static/theme.css @@ -301,6 +301,26 @@ strong { text-align: center; } +/* Admin view layout: We have an extra sidebar for the navigation */ +#adminContainer { + display: grid; + grid-template-areas: "sidebar main"; + grid-template-columns: 1fr 5fr; + gap: 1rem; +} + +#adminNavigation { + grid-area: sidebar; +} + +#adminContent { + grid-area: main; +} + +.admin-stat { + font-size: 120%; +} + .list-group.list-group-root { padding: 0; overflow: hidden; diff --git a/fietsboek/static/theme.css.map b/fietsboek/static/theme.css.map index 6773b79..3ddb9bc 100644 --- a/fietsboek/static/theme.css.map +++ b/fietsboek/static/theme.css.map @@ -1 +1 @@ -{"version":3,"sourceRoot":"","sources":["../../asset-sources/theme.scss"],"names":[],"mappings":"AAAA;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;;AAEA;EACE;EACA;EACA;;;AAIJ;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EAqCE;EACA;EACA;EACA;EACA;EACA;;AAzCA;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAWJ;EACI;;;AAGJ;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;;;AAIJ;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;;;AAEF;EACE;EACA;;;AAEF;EACE;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;AAIJ;EACE;;AAEF;EACE;;AACA;EACE;;AAGJ;EACE;;AACA;EACE;;AAGJ;EACE;;AACA;EACE;;AAGJ;EACE;;AACA;EACE;;AAGJ;EACE;EACA;;AACA;EACE;;AAIJ;EACE;EACA;;;AAIJ;EACE;;;AAGF;AACA;EACE;;;AAGF;EACE;EACA;;AAEA;EAJF;IAKI,eACE;;;AAIJ;EACE;;AAGF;EACE;;AAEA;EAHF;IAII;;;AAGF;EACE;EACA;;AAIJ;EACE;EACA;;;AAIJ;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE","file":"theme.css"}
\ No newline at end of file +{"version":3,"sourceRoot":"","sources":["../../asset-sources/theme.scss"],"names":[],"mappings":"AAAA;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;;AAEA;EACE;EACA;EACA;;;AAIJ;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EAqCE;EACA;EACA;EACA;EACA;EACA;;AAzCA;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAWJ;EACI;;;AAGJ;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;;;AAIJ;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;;;AAEF;EACE;EACA;;;AAEF;EACE;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;AAIJ;EACE;;AAEF;EACE;;AACA;EACE;;AAGJ;EACE;;AACA;EACE;;AAGJ;EACE;;AACA;EACE;;AAGJ;EACE;;AACA;EACE;;AAGJ;EACE;EACA;;AACA;EACE;;AAIJ;EACE;EACA;;;AAIJ;EACE;;;AAGF;AACA;EACE;;;AAGF;EACE;EACA;;AAEA;EAJF;IAKI,eACE;;;AAIJ;EACE;;AAGF;EACE;;AAEA;EAHF;IAII;;;AAGF;EACE;EACA;;AAIJ;EACE;EACA;;;AAIJ;EACE;;;AAGF;AACA;EACE;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE","file":"theme.css"}
\ 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/admin.jinja2 b/fietsboek/templates/admin.jinja2 index 3201d8d..e05e9f0 100644 --- a/fietsboek/templates/admin.jinja2 +++ b/fietsboek/templates/admin.jinja2 @@ -4,45 +4,18 @@ <div class="container"> <h1>{{ _("page.admin.title") }}</h1> - <h2>{{ _("page.admin.badges") }}</h2> + <div id="adminContainer"> + <aside id="adminNavigation"> + <nav class="nav nav-pills nav-fill flex-column"> + <a class="nav-link{% if admin_index == 0 %} active text-bg-dark{% endif %}" href="{{ request.route_url('admin') }}">{{ _("page.admin.nav.overview") }}</a> + <a class="nav-link{% if admin_index == 1 %} active text-bg-dark{% endif %}" href="{{ request.route_url('admin-badge') }}">{{ _("page.admin.nav.badges") }}</a> + </nav> + </aside> - <div class="list-group"> - {% for badge in badges %} - <span href="#" class="list-group-item list-group-item-action d-flex admin-badge-list"> - {{ util.render_badge(badge) }} - <form method="POST" enctype="multipart/form-data" action="{{ request.route_path('admin-badge-edit') }}"> - <input type="hidden" name="badge-edit-id" value="{{ badge.id }}"> - <div class="mb-3"> - <input type="text" class="form-control" name="badge-title" value="{{ badge.title }}"> - </div> - <div class="mb-3"> - <input class="form-control" type="file" name="badge-image"> - </div> - {{ util.hidden_csrf_input() }} - <div class="mb-3"> - <button class="btn btn-primary">{{ _("page.admin.badge.edit") }}</button> - </div> - </form> - <form method="POST" action="{{ request.route_path('admin-badge-delete') }}"> - <input type="hidden" name="badge-delete-id" value="{{ badge.id }}"> - {{ util.hidden_csrf_input() }} - <button class="btn btn-danger"><i class="bi bi-trash"></i> {{ _("page.admin.badge.delete_badge") }}</button> - </form> - </span> - {% endfor %} - </div> - - <form method="POST" enctype="multipart/form-data" action="{{ request.route_path('admin-badge-add') }}"> - <div class="mb-3"> - <label for="badge-title" class="form-label">{{ _("page.admin.badges.badge_title") }}</label> - <input type="text" class="form-control" id="badge-title" name="badge-title"> + <div id="adminContent"> + {% block admin_content %} + {% endblock %} </div> - <div class="mb-3"> - <label for="badge-image" class="form-label">{{ _("page.admin.badges.badge_image") }}</label> - <input class="form-control" type="file" name="badge-image"> - </div> - {{ util.hidden_csrf_input() }} - <button type="submit" class="btn btn-primary">{{ _("page.admin.badges.add_badge") }}</button> - </form> + </div> </div> {% endblock %} diff --git a/fietsboek/templates/admin_badges.jinja2 b/fietsboek/templates/admin_badges.jinja2 new file mode 100644 index 0000000..efe8d2c --- /dev/null +++ b/fietsboek/templates/admin_badges.jinja2 @@ -0,0 +1,45 @@ +{% set admin_index = 1 %} +{% extends "admin.jinja2" %} +{% import "util.jinja2" as util with context %} +{% block admin_content %} +<h2>{{ _("page.admin.badges") }}</h2> + +<div class="list-group"> + {% for badge in badges %} + <span href="#" class="list-group-item list-group-item-action d-flex admin-badge-list"> + {{ util.render_badge(badge) }} + <form method="POST" enctype="multipart/form-data" action="{{ request.route_path('admin-badge-edit') }}"> + <input type="hidden" name="badge-edit-id" value="{{ badge.id }}"> + <div class="mb-3"> + <input type="text" class="form-control" name="badge-title" value="{{ badge.title }}"> + </div> + <div class="mb-3"> + <input class="form-control" type="file" name="badge-image"> + </div> + {{ util.hidden_csrf_input() }} + <div class="mb-3"> + <button class="btn btn-success"><i class="bi bi-pencil"></i> {{ _("page.admin.badge.edit") }}</button> + <button class="btn btn-danger" form="deleteBadge{{ badge.id }}"><i class="bi bi-trash"></i> {{ _("page.admin.badge.delete_badge") }}</button> + </div> + </form> + <form method="POST" id="deleteBadge{{ badge.id }}" action="{{ request.route_path('admin-badge-delete') }}"> + <input type="hidden" name="badge-delete-id" value="{{ badge.id }}"> + {{ util.hidden_csrf_input() }} + </form> + </span> + {% endfor %} +</div> + +<form method="POST" enctype="multipart/form-data" action="{{ request.route_path('admin-badge-add') }}"> + <div class="mb-3"> + <label for="badge-title" class="form-label">{{ _("page.admin.badges.badge_title") }}</label> + <input type="text" class="form-control" id="badge-title" name="badge-title"> + </div> + <div class="mb-3"> + <label for="badge-image" class="form-label">{{ _("page.admin.badges.badge_image") }}</label> + <input class="form-control" type="file" name="badge-image"> + </div> + {{ util.hidden_csrf_input() }} + <button type="submit" class="btn btn-primary">{{ _("page.admin.badges.add_badge") }}</button> +</form> +{% endblock %} diff --git a/fietsboek/templates/admin_overview.jinja2 b/fietsboek/templates/admin_overview.jinja2 new file mode 100644 index 0000000..5337a69 --- /dev/null +++ b/fietsboek/templates/admin_overview.jinja2 @@ -0,0 +1,91 @@ +{% set admin_index = 0 %} +{% extends "admin.jinja2" %} +{% block admin_content %} +<p class="admin-stat"> + {{ _("admin.overview.instance_has") }}… +</p> + +<p class="admin-stat"> + … {{ ngettext("admin.overview.stat.user", "admin.overview.stat.users", user_count) }} +</p> + +<p class="admin-stat"> + … {{ ngettext("admin.overview.stat.track", "admin.overview.stat.tracks", track_count) }} +</p> + +<p class="admin-stat"> + … {{ (total_size / 1024 / 1024) | round(2) }} {{ _("admin.overview.stats.mib") }} +</p> + +<div style="position: relative; height: 500px; margin: auto; width: 75%;"> + <canvas id="graph-size-breakdown"></canvas> +</div> + +<h2>{{ _("admin.overview.system_overview") }}</h2> + +<table class="table"> + <tr> + <td>{{ _("admin.overview.fietsboek_version") }}</td> + <td>{{ versions["fietsboek"] }}</td> + </tr> + <tr> + <td>{{ _("admin.overview.python_version") }}</td> + <td>{{ versions["python"] }}</td> + </tr> + <tr> + <td>{{ _("admin.overview.kernel_version") }}</td> + <td>{{ versions["linux"] }}</td> + </tr> + <tr> + <td>{{ _("admin.overview.distro_version") }}</td> + <td>{{ versions["distro"] }}</td> + </tr> + <tr class="{% if cron_good %}table-success{% else %}table-warning{% endif %}"> + <td>{{ _("admin.overview.last_cronjob") }} {% if not cron_good %}<i class="bi bi-exclamation-triangle-fill"></i>{% endif %}</td> + <td>{{ last_cronjob }}</td> + </tr> +</table> +{% endblock %} + +{% block latescripts %} +<script> + (function() { + const data = { + labels: [ + {{ _("admin.overview.storage_graph.label.gpx") | tojson }}, + {{ _("admin.overview.storage_graph.label.images") | tojson }}, + {{ _("admin.overview.storage_graph.label.user_maps") | tojson }} + ], + datasets: [ + { + label: "MiB", + data: [ + {{ (size_breakdown.gpx_files / 1024 / 1024) | tojson }}, + {{ (size_breakdown.image_files / 1024 / 1024) | tojson }}, + {{ (size_breakdown.user_maps / 1024 / 1024) | tojson }} + ] + } + ] + }; + + const config = { + type: 'pie', + data: data, + options: { + responsive: true, + plugins: { + legend: { + position: 'top', + }, + title: { + display: true, + text: {{ _("admin.overview.storage_graph.title") | tojson }} + } + } + } + }; + + new Chart("graph-size-breakdown", config); + })(); +</script> +{% endblock %} diff --git a/fietsboek/util.py b/fietsboek/util.py index 9284ce2..5611c51 100644 --- a/fietsboek/util.py +++ b/fietsboek/util.py @@ -7,6 +7,7 @@ import os import re import secrets import unicodedata +from pathlib import Path from typing import Optional, TypeVar, Union import babel @@ -504,6 +505,18 @@ def secure_filename(filename: str) -> str: return filename +def recursive_size(path: Path) -> int: + """Recursively determines the size of the given directory. + + :param path: The directory. + :return: The combined size, in bytes. + """ + size = 0 + for root, _folders, files in os.walk(path): + size += sum(os.path.getsize(os.path.join(root, fname)) for fname in files) + return size + + __all__ = [ "ALLOWED_TAGS", "ALLOWED_ATTRIBUTES", @@ -529,4 +542,5 @@ __all__ = [ "tile_url", "encode_gpx", "secure_filename", + "recursive_size", ] diff --git a/fietsboek/views/admin.py b/fietsboek/views/admin.py index d078794..0589cd0 100644 --- a/fietsboek/views/admin.py +++ b/fietsboek/views/admin.py @@ -1,21 +1,126 @@ """Admin views.""" +import datetime +import platform +import stat +from dataclasses import dataclass +from pathlib import Path + from pyramid.httpexceptions import HTTPFound from pyramid.i18n import TranslationString as _ +from pyramid.request import Request from pyramid.view import view_config -from sqlalchemy import select +from sqlalchemy import func, select + +from .. import models, util + +GOOD_CRON_THRESHOLD = datetime.timedelta(hours=1) + + +def _safe_size(path: Path) -> int: + try: + res = path.stat() + if stat.S_ISDIR(res.st_mode): + return util.recursive_size(path) + if stat.S_ISREG(res.st_mode): + return res.st_size + return 0 + except FileNotFoundError: + return 0 + + +@dataclass +class SizeBreakdown: + """A breakdown of what objects take how much storage.""" + + gpx_files: int = 0 + image_files: int = 0 + user_maps: int = 0 -from .. import models + +def _get_size_breakdown(data_manager): + breakdown = SizeBreakdown() + + for track_id in data_manager.list_tracks(): + track = data_manager.open(track_id) + breakdown.gpx_files += _safe_size(track.gpx_path()) + for image_id in track.images(): + breakdown.image_files += _safe_size(track.image_path(image_id)) + + for user_id in data_manager.list_users(): + user = data_manager.open_user(user_id) + breakdown.user_maps += _safe_size(user.heatmap_path()) + breakdown.user_maps += _safe_size(user.tilehunt_path()) + + return breakdown + + +def _get_fietsboek_version(): + # pylint: disable=import-outside-toplevel + from fietsboek import __VERSION__ + + return __VERSION__ @view_config( route_name="admin", - renderer="fietsboek:templates/admin.jinja2", + renderer="fietsboek:templates/admin_overview.jinja2", + request_method="GET", + permission="admin", +) +def admin(request: Request): + """Renders the admin overview. + + :param request: The Pyramid request. + :return: The HTTP response. + """ + # False-positive with func.count() + # pylint: disable=not-callable + user_count = request.dbsession.execute(select(func.count()).select_from(models.User)).scalar() + track_count = request.dbsession.execute(select(func.count()).select_from(models.Track)).scalar() + size_total = request.data_manager.size() + size_breakdown = _get_size_breakdown(request.data_manager) + + try: + distro = platform.freedesktop_os_release()["PRETTY_NAME"] + except OSError: + distro = None + + try: + last_cronjob_timestamp = float(request.redis.get("last-cronjob")) + except (TypeError, ValueError): + last_cronjob = None + cron_good = False + else: + last_cronjob = datetime.datetime.fromtimestamp(last_cronjob_timestamp, datetime.UTC) + cron_good = (datetime.datetime.now(datetime.UTC) - last_cronjob) < GOOD_CRON_THRESHOLD + + versions = { + "fietsboek": _get_fietsboek_version(), + "python": platform.python_version(), + "linux": platform.platform(), + "distro": distro, + } + + return { + "user_count": user_count, + "track_count": track_count, + "total_size": size_total, + "size_breakdown": size_breakdown, + "versions": versions, + "last_cronjob": last_cronjob, + "cron_good": cron_good, + } + + +@view_config( + route_name="admin-badge", + renderer="fietsboek:templates/admin_badges.jinja2", request_method="GET", permission="admin", ) -def admin(request): - """Renders the main admin overview. +def admin_badges(request): + """Renders the badges editor. :param request: The Pyramid request. :type request: pyramid.request.Request @@ -47,7 +152,7 @@ def do_badge_add(request): request.dbsession.add(badge) request.session.flash(request.localizer.translate(_("flash.badge_added"))) - return HTTPFound(request.route_url("admin")) + return HTTPFound(request.route_url("admin-badge")) @view_config(route_name="admin-badge-edit", permission="admin", request_method="POST") @@ -71,7 +176,7 @@ def do_badge_edit(request): badge.title = request.params["badge-title"] request.session.flash(request.localizer.translate(_("flash.badge_modified"))) - return HTTPFound(request.route_url("admin")) + return HTTPFound(request.route_url("admin-badge")) @view_config(route_name="admin-badge-delete", permission="admin", request_method="POST") @@ -91,7 +196,7 @@ def do_badge_delete(request): request.dbsession.delete(badge) request.session.flash(request.localizer.translate(_("flash.badge_deleted"))) - return HTTPFound(request.route_url("admin")) + return HTTPFound(request.route_url("admin-badge")) -__all__ = ["admin", "do_badge_add", "do_badge_edit", "do_badge_delete"] +__all__ = ["admin", "admin_badges", "do_badge_add", "do_badge_edit", "do_badge_delete"] 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/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): diff --git a/tests/playwright/test_share.py b/tests/playwright/test_share.py index de288a0..dcba899 100644 --- a/tests/playwright/test_share.py +++ b/tests/playwright/test_share.py @@ -29,7 +29,8 @@ def test_view_wrong_link(page: Page, playwright_helper, dbaccess): with page.expect_response(lambda resp: resp.status == 403): page.goto(f"/track/{track.id}?secret=foobar") - assert "Forbidden" in page.content() + assert "No entry" in page.content() + assert "not allowed to access" in page.content() def test_change_link(page: Page, playwright_helper, dbaccess): |