aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Schadt <kingdread@gmx.de>2023-09-11 21:32:10 +0200
committerDaniel Schadt <kingdread@gmx.de>2023-09-11 21:32:10 +0200
commit9550be82608f54407f2306d13045d5cf90e8d56e (patch)
treed7a27ce4e3a932ed2d0dc21d011ed85c2fc322fa
parent68e49e1e879a32c0f0d764f927923ca42706e593 (diff)
parent6b320b11ac2f86e9f63f6f9998db9dbbac56849d (diff)
downloadfietsboek-9550be82608f54407f2306d13045d5cf90e8d56e.tar.gz
fietsboek-9550be82608f54407f2306d13045d5cf90e8d56e.tar.bz2
fietsboek-9550be82608f54407f2306d13045d5cf90e8d56e.zip
Merge branch 'favourites'
-rw-r--r--asset-sources/fietsboek.ts51
-rw-r--r--asset-sources/theme.scss8
-rw-r--r--fietsboek/alembic/versions/20230830_8f4e4eae5eb2.py31
-rw-r--r--fietsboek/locale/de/LC_MESSAGES/messages.mobin14524 -> 14714 bytes
-rw-r--r--fietsboek/locale/de/LC_MESSAGES/messages.po156
-rw-r--r--fietsboek/locale/en/LC_MESSAGES/messages.mobin13551 -> 13738 bytes
-rw-r--r--fietsboek/locale/en/LC_MESSAGES/messages.po156
-rw-r--r--fietsboek/locale/fietslog.pot156
-rw-r--r--fietsboek/models/track.py18
-rw-r--r--fietsboek/models/user.py15
-rw-r--r--fietsboek/routes.py1
-rw-r--r--fietsboek/static/fietsboek.js49
-rw-r--r--fietsboek/static/fietsboek.js.map2
-rw-r--r--fietsboek/static/theme.css8
-rw-r--r--fietsboek/static/theme.css.map2
-rw-r--r--fietsboek/templates/browse.jinja216
-rw-r--r--fietsboek/templates/details.jinja213
-rw-r--r--fietsboek/templates/home.jinja23
-rw-r--r--fietsboek/views/browse.py38
-rw-r--r--fietsboek/views/user_data.py19
-rw-r--r--pylint.toml12
-rw-r--r--tests/integration/test_upload.py4
-rw-r--r--tests/playwright/test_basic.py20
-rw-r--r--tox.ini2
24 files changed, 550 insertions, 230 deletions
diff --git a/asset-sources/fietsboek.ts b/asset-sources/fietsboek.ts
index b885271..ae9e724 100644
--- a/asset-sources/fietsboek.ts
+++ b/asset-sources/fietsboek.ts
@@ -22,6 +22,19 @@ interface Window {
*/
type Selector = string;
+
+/**
+ * Gets the value of a single cookie.
+ *
+ * @param name - Name of the cookie.
+ * @return The cookie value, or null.
+ */
+function getCookie(name: string): string | undefined {
+ return document.cookie.split("; ")
+ .find((row) => row.startsWith(`${name}=`))
+ ?.split("=")[1];
+}
+
/**
* Installs a listener to the given DOM objects.
*
@@ -400,9 +413,7 @@ addHandler(".button-clear-input", "click", clearInputButtonClicked);
* @param event - The triggering event.
*/
function changeHomeSorting(_event: MouseEvent) {
- const currentSorting = document.cookie.split("; ")
- .find((row) => row.startsWith("home_sorting="))
- ?.split("=")[1] ?? "asc";
+ const currentSorting = getCookie("home_sorting") ?? "asc";
const newSorting = currentSorting == "asc" ? "desc" : "asc";
document.cookie = `home_sorting=${newSorting}; SameSite=Lax`;
window.location.reload();
@@ -411,6 +422,40 @@ function changeHomeSorting(_event: MouseEvent) {
addHandler("#changeHomeSorting", "click", changeHomeSorting);
+/**
+ * Handler to toggle the favourite status of a track.
+ *
+ * This is applied to .favourite-star elements and expects the track ID in
+ * data-track-id.
+ *
+ * @param event - The triggering event.
+ */
+function toggleTrackFavourite(event: MouseEvent) {
+ const target = event.target as HTMLElement;
+ const trackId = target.getAttribute("data-track-id");
+ if (trackId === null) {
+ return;
+ }
+ const url = new URL("/me/toggle-favourite", window.location.href);
+ const formData = new URLSearchParams();
+ formData.append("track-id", trackId);
+ formData.append("csrf_token", getCookie("csrf_token") ?? "");
+ fetch(url, {
+ "method": "POST",
+ "body": formData,
+ }).then(response => response.json().then(data => {
+ const isNowFavourite = data["favourite"];
+ if (isNowFavourite) {
+ target.classList.replace("bi-star", "bi-star-fill");
+ } else {
+ target.classList.replace("bi-star-fill", "bi-star");
+ }
+ }));
+}
+
+addHandler(".favourite-star", "click", toggleTrackFavourite);
+
+
document.addEventListener('DOMContentLoaded', function() {
window.fietsboekImageIndex = 0;
diff --git a/asset-sources/theme.scss b/asset-sources/theme.scss
index faa4f44..8e0f001 100644
--- a/asset-sources/theme.scss
+++ b/asset-sources/theme.scss
@@ -118,6 +118,14 @@ strong {
}
}
+.favourite-star, .favourite-star-ni {
+ color: orange;
+}
+
+.favourite-star {
+ cursor: pointer;
+}
+
/* Ensure a consistent width of the cells in the browse view. */
.browse-summary th, .browse-summary td {
width: 25%;
diff --git a/fietsboek/alembic/versions/20230830_8f4e4eae5eb2.py b/fietsboek/alembic/versions/20230830_8f4e4eae5eb2.py
new file mode 100644
index 0000000..e8ac627
--- /dev/null
+++ b/fietsboek/alembic/versions/20230830_8f4e4eae5eb2.py
@@ -0,0 +1,31 @@
+"""add favourite association table
+
+Revision ID: 8f4e4eae5eb2
+Revises: 3149aa2d0114
+Create Date: 2023-08-30 19:19:54.545517
+
+"""
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = '8f4e4eae5eb2'
+down_revision = '3149aa2d0114'
+branch_labels = None
+depends_on = None
+
+def upgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.create_table('track_favourite_assoc',
+ sa.Column('track_id', sa.Integer(), nullable=False),
+ sa.Column('user_id', sa.Integer(), nullable=False),
+ sa.ForeignKeyConstraint(['track_id'], ['tracks.id'], name=op.f('fk_track_favourite_assoc_track_id_tracks')),
+ sa.ForeignKeyConstraint(['user_id'], ['users.id'], name=op.f('fk_track_favourite_assoc_user_id_users')),
+ sa.PrimaryKeyConstraint('track_id', 'user_id', name=op.f('pk_track_favourite_assoc'))
+ )
+ # ### end Alembic commands ###
+
+def downgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.drop_table('track_favourite_assoc')
+ # ### end Alembic commands ###
diff --git a/fietsboek/locale/de/LC_MESSAGES/messages.mo b/fietsboek/locale/de/LC_MESSAGES/messages.mo
index 1a44e6b..e6dd1c6 100644
--- a/fietsboek/locale/de/LC_MESSAGES/messages.mo
+++ b/fietsboek/locale/de/LC_MESSAGES/messages.mo
Binary files differ
diff --git a/fietsboek/locale/de/LC_MESSAGES/messages.po b/fietsboek/locale/de/LC_MESSAGES/messages.po
index be0aca1..b34e849 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: 2023-07-22 14:31+0200\n"
+"POT-Creation-Date: 2023-08-17 22:51+0200\n"
"PO-Revision-Date: 2022-07-02 17:35+0200\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: de\n"
@@ -37,35 +37,35 @@ msgstr "Passwörter stimmen nicht überein"
msgid "password_constraint.length"
msgstr "Passwort zu kurz"
-#: fietsboek/models/track.py:589
+#: fietsboek/models/track.py:602
msgid "tooltip.table.length"
msgstr "Länge"
-#: fietsboek/models/track.py:590
+#: fietsboek/models/track.py:603
msgid "tooltip.table.people"
msgstr "# Personen"
-#: fietsboek/models/track.py:591
+#: fietsboek/models/track.py:604
msgid "tooltip.table.uphill"
msgstr "Bergauf"
-#: fietsboek/models/track.py:592
+#: fietsboek/models/track.py:605
msgid "tooltip.table.downhill"
msgstr "Bergab"
-#: fietsboek/models/track.py:593
+#: fietsboek/models/track.py:606
msgid "tooltip.table.moving_time"
msgstr "Fahrzeit"
-#: fietsboek/models/track.py:594
+#: fietsboek/models/track.py:607
msgid "tooltip.table.stopped_time"
msgstr "Haltezeit"
-#: fietsboek/models/track.py:596
+#: fietsboek/models/track.py:609
msgid "tooltip.table.max_speed"
msgstr "Maximalgeschwindigkeit"
-#: fietsboek/models/track.py:600
+#: fietsboek/models/track.py:613
msgid "tooltip.table.avg_speed"
msgstr "Durchschnittsgeschwindigkeit"
@@ -137,109 +137,121 @@ msgstr "Aufnahme (natürlich)"
msgid "page.browse.filter.type.synthetic"
msgstr "Vorlage (synthetisch)"
-#: fietsboek/templates/browse.jinja2:86
+#: fietsboek/templates/browse.jinja2:82
+msgid "page.browse.filter.favourite.all"
+msgstr "Alle Strecken"
+
+#: fietsboek/templates/browse.jinja2:83
+msgid "page.browse.filter.favourite.yes"
+msgstr "Nur Favoriten"
+
+#: fietsboek/templates/browse.jinja2:84
+msgid "page.browse.filter.favourite.no"
+msgstr "Keine Favoriten"
+
+#: fietsboek/templates/browse.jinja2:97
msgid "page.browse.filter.my_tracks.only"
msgstr "Zeige nur eigene Strecken"
-#: fietsboek/templates/browse.jinja2:87
+#: fietsboek/templates/browse.jinja2:98
msgid "page.browse.filter.friends_tracks_only"
msgstr "Zeige nur Strecken von Freunden"
-#: fietsboek/templates/browse.jinja2:88
+#: fietsboek/templates/browse.jinja2:99
msgid "page.browse.filter.me_tagged_only"
msgstr "Zeige nur Strecken, in denen ich markiert bin"
-#: fietsboek/templates/browse.jinja2:98
+#: fietsboek/templates/browse.jinja2:109
msgid "page.browse.filters.apply"
msgstr "Filter anwenden"
-#: fietsboek/templates/browse.jinja2:102
+#: fietsboek/templates/browse.jinja2:113
msgid "page.browse.filters.clear_all"
msgstr "Filter zurücksetzen"
-#: fietsboek/templates/browse.jinja2:105
+#: fietsboek/templates/browse.jinja2:116
msgid "page.browse.filters.expand_advanced"
msgstr "Erweitert"
-#: fietsboek/templates/browse.jinja2:110 fietsboek/templates/browse.jinja2:111
+#: fietsboek/templates/browse.jinja2:121 fietsboek/templates/browse.jinja2:122
msgid "page.browse.sort.date"
msgstr "Nach Datum sortieren"
-#: fietsboek/templates/browse.jinja2:112 fietsboek/templates/browse.jinja2:113
+#: fietsboek/templates/browse.jinja2:123 fietsboek/templates/browse.jinja2:124
msgid "page.browse.sort.length"
msgstr "Nach Länge sortieren"
-#: fietsboek/templates/browse.jinja2:114 fietsboek/templates/browse.jinja2:115
+#: fietsboek/templates/browse.jinja2:125 fietsboek/templates/browse.jinja2:126
msgid "page.browse.sort.duration"
msgstr "Nach Dauer sortieren"
-#: fietsboek/templates/browse.jinja2:132
+#: fietsboek/templates/browse.jinja2:148
msgid "page.browse.organic_tooltip"
msgstr "Dies ist eine Aufnahme einer Strecke"
-#: fietsboek/templates/browse.jinja2:134
+#: fietsboek/templates/browse.jinja2:150
msgid "page.browse.synthetic_tooltip"
msgstr "Dies ist eine geplante Strecke"
-#: fietsboek/templates/browse.jinja2:142 fietsboek/templates/details.jinja2:90
+#: fietsboek/templates/browse.jinja2:158 fietsboek/templates/details.jinja2:103
#: fietsboek/templates/profile.jinja2:15
msgid "page.details.date"
msgstr "Datum"
-#: fietsboek/templates/browse.jinja2:144 fietsboek/templates/details.jinja2:104
+#: fietsboek/templates/browse.jinja2:160 fietsboek/templates/details.jinja2:117
#: fietsboek/templates/profile.jinja2:17
msgid "page.details.length"
msgstr "Länge"
-#: fietsboek/templates/browse.jinja2:149 fietsboek/templates/details.jinja2:95
+#: fietsboek/templates/browse.jinja2:165 fietsboek/templates/details.jinja2:108
#: fietsboek/templates/profile.jinja2:21
msgid "page.details.start_time"
msgstr "Startzeit"
-#: fietsboek/templates/browse.jinja2:151 fietsboek/templates/details.jinja2:99
+#: fietsboek/templates/browse.jinja2:167 fietsboek/templates/details.jinja2:112
#: fietsboek/templates/profile.jinja2:23
msgid "page.details.end_time"
msgstr "Endzeit"
-#: fietsboek/templates/browse.jinja2:156 fietsboek/templates/details.jinja2:108
+#: fietsboek/templates/browse.jinja2:172 fietsboek/templates/details.jinja2:121
#: fietsboek/templates/profile.jinja2:27
msgid "page.details.uphill"
msgstr "Bergauf"
-#: fietsboek/templates/browse.jinja2:158 fietsboek/templates/details.jinja2:112
+#: fietsboek/templates/browse.jinja2:174 fietsboek/templates/details.jinja2:125
#: fietsboek/templates/profile.jinja2:29
msgid "page.details.downhill"
msgstr "Bergab"
-#: fietsboek/templates/browse.jinja2:163 fietsboek/templates/details.jinja2:117
+#: fietsboek/templates/browse.jinja2:179 fietsboek/templates/details.jinja2:130
#: fietsboek/templates/profile.jinja2:33
msgid "page.details.moving_time"
msgstr "Fahrzeit"
-#: fietsboek/templates/browse.jinja2:165 fietsboek/templates/details.jinja2:121
+#: fietsboek/templates/browse.jinja2:181 fietsboek/templates/details.jinja2:134
#: fietsboek/templates/profile.jinja2:35
msgid "page.details.stopped_time"
msgstr "Haltezeit"
-#: fietsboek/templates/browse.jinja2:169 fietsboek/templates/details.jinja2:125
+#: fietsboek/templates/browse.jinja2:185 fietsboek/templates/details.jinja2:138
#: fietsboek/templates/profile.jinja2:39
msgid "page.details.max_speed"
msgstr "maximale Geschwindigkeit"
-#: fietsboek/templates/browse.jinja2:171 fietsboek/templates/details.jinja2:129
+#: fietsboek/templates/browse.jinja2:187 fietsboek/templates/details.jinja2:142
#: fietsboek/templates/profile.jinja2:41
msgid "page.details.avg_speed"
msgstr "durchschnittliche Geschwindigkeit"
-#: fietsboek/templates/browse.jinja2:189
+#: fietsboek/templates/browse.jinja2:205
msgid "page.browse.download_multiple"
msgstr "ausgewählte Herunterladen"
-#: fietsboek/templates/browse.jinja2:191
+#: fietsboek/templates/browse.jinja2:207
msgid "page.browse.no_results"
msgstr "Es wurden keine Strecken gefunden, die den Filtern entsprechen."
-#: fietsboek/templates/browse.jinja2:193
+#: fietsboek/templates/browse.jinja2:209
msgid "page.browse.no_tracks"
msgstr ""
"Es wurden keine Strecken gefunden, auf die Du Zugriff hast. Versuche, "
@@ -285,90 +297,90 @@ msgstr "Passwort wiederholen"
msgid "page.create_account.create"
msgstr "Erstellen"
-#: fietsboek/templates/details.jinja2:6
+#: fietsboek/templates/details.jinja2:7
msgid "page.details.title"
msgstr "Details"
-#: fietsboek/templates/details.jinja2:9
+#: fietsboek/templates/details.jinja2:20
msgid "page.details.edit"
msgstr "Bearbeiten"
-#: fietsboek/templates/details.jinja2:10
+#: fietsboek/templates/details.jinja2:21
msgid "page.details.share"
msgstr "Teilen"
-#: fietsboek/templates/details.jinja2:11
+#: fietsboek/templates/details.jinja2:22
msgid "page.details.delete"
msgstr "Löschen"
-#: fietsboek/templates/details.jinja2:17
+#: fietsboek/templates/details.jinja2:28
msgid "page.details.sharelink.title"
msgstr "Link zum Teilen"
-#: fietsboek/templates/details.jinja2:21
+#: fietsboek/templates/details.jinja2:32
msgid "page.details.sharelink.info"
msgstr "Jeder mit Zugang zu diesem Link kann die Strecke ansehen!"
-#: fietsboek/templates/details.jinja2:28
+#: fietsboek/templates/details.jinja2:39
msgid "page.details.sharelink.invalidate"
msgstr "Link invalidieren"
-#: fietsboek/templates/details.jinja2:30
+#: fietsboek/templates/details.jinja2:41
msgid "page.details.sharelink.close"
msgstr "Schließen"
-#: fietsboek/templates/details.jinja2:40
+#: fietsboek/templates/details.jinja2:51
msgid "page.details.delete.title"
msgstr "Strecke Löschen"
-#: fietsboek/templates/details.jinja2:44
+#: fietsboek/templates/details.jinja2:55
msgid "page.details.delete.info"
msgstr "Das Löschen der Strecke wird alle damit verbundenen Informationen löschen!"
-#: fietsboek/templates/details.jinja2:49
+#: fietsboek/templates/details.jinja2:60
msgid "page.details.delete.delete"
msgstr "Löschen"
-#: fietsboek/templates/details.jinja2:51
+#: fietsboek/templates/details.jinja2:62
msgid "page.details.delete.close"
msgstr "Abbrechen"
-#: fietsboek/templates/details.jinja2:70
+#: fietsboek/templates/details.jinja2:81
msgid "page.details.tags"
msgstr "Schlagwörter"
-#: fietsboek/templates/details.jinja2:80 fietsboek/templates/edit.jinja2:10
+#: fietsboek/templates/details.jinja2:91 fietsboek/templates/edit.jinja2:10
#: fietsboek/templates/finish_upload.jinja2:10
msgid "page.noscript"
msgstr ""
"JavaScript ist deaktiviert, zum Nutzen aller Funktionen bitte JavaScript "
"aktivieren"
-#: fietsboek/templates/details.jinja2:85
+#: fietsboek/templates/details.jinja2:97
msgid "page.details.download"
msgstr "Herunterladen"
-#: fietsboek/templates/details.jinja2:174
+#: fietsboek/templates/details.jinja2:187
msgid "page.details.comments"
msgstr "Kommentare"
-#: fietsboek/templates/details.jinja2:178
+#: fietsboek/templates/details.jinja2:191
msgid "page.details.comments.author"
msgstr "Kommentar von {}"
-#: fietsboek/templates/details.jinja2:195
+#: fietsboek/templates/details.jinja2:208
msgid "page.details.comments.new.title"
msgstr "Kommentar erstellen"
-#: fietsboek/templates/details.jinja2:198
+#: fietsboek/templates/details.jinja2:211
msgid "page.details.comments.new.input_title"
msgstr "Titel"
-#: fietsboek/templates/details.jinja2:199
+#: fietsboek/templates/details.jinja2:212
msgid "page.details.comments.new.input_comment"
msgstr "Kommentar"
-#: fietsboek/templates/details.jinja2:202
+#: fietsboek/templates/details.jinja2:215
msgid "page.details.comments.new.submit"
msgstr "Absenden"
@@ -504,13 +516,13 @@ msgstr ""
"Links, um sie fortzusetzen:"
#: fietsboek/templates/home.jinja2:31 fietsboek/templates/home.jinja2:38
-#: fietsboek/templates/home.jinja2:59
+#: fietsboek/templates/home.jinja2:66
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:59
+#: fietsboek/templates/home.jinja2:66
msgid "page.home.total"
msgstr "Gesamt"
@@ -824,23 +836,23 @@ msgstr "Konto noch nicht bestätigt"
msgid "flash.logged_in"
msgstr "Du bist nun angemeldet"
-#: fietsboek/views/default.py:142
+#: fietsboek/views/default.py:143
msgid "flash.logged_out"
msgstr "Du bist nun abgemeldet"
-#: fietsboek/views/default.py:172
+#: fietsboek/views/default.py:173
msgid "flash.reset_invalid_email"
msgstr "Ungültige E-Mail-Adresse angegeben"
-#: fietsboek/views/default.py:177
+#: fietsboek/views/default.py:178
msgid "flash.password_token_generated"
msgstr "Ein Link zum Zurücksetzen des Passworts wurde versandt"
-#: fietsboek/views/default.py:182
+#: fietsboek/views/default.py:183
msgid "page.password_reset.email.subject"
msgstr "Fietsboek Passwortzurücksetzung"
-#: fietsboek/views/default.py:185
+#: fietsboek/views/default.py:186
msgid "page.password_reset.email.body"
msgstr ""
"Du kannst Dein Fietsboek-Passwort hier zurücksetzen: {}\n"
@@ -848,27 +860,27 @@ msgstr ""
"Falls Du keine Passwortzurücksetzung beantragt hast, dann ignoriere diese"
" E-Mail."
-#: fietsboek/views/default.py:224
+#: fietsboek/views/default.py:225
msgid "flash.resend_verification_email_fail"
msgstr "Ungültige E-Mail-Adresse angegeben"
-#: fietsboek/views/default.py:229
+#: fietsboek/views/default.py:230
msgid "flash.verification_token_generated"
msgstr "Ein Link zur Bestätigung Deines Kontos wurde versandt"
-#: fietsboek/views/default.py:249
+#: fietsboek/views/default.py:250
msgid "flash.token_expired"
msgstr "Der Link ist nicht mehr gültig"
-#: fietsboek/views/default.py:255
+#: fietsboek/views/default.py:256
msgid "flash.email_verified"
msgstr "E-Mail-Adresse bestätigt"
-#: fietsboek/views/default.py:269
+#: fietsboek/views/default.py:270
msgid "flash.password_updated"
msgstr "Passwort aktualisiert"
-#: fietsboek/views/detail.py:155
+#: fietsboek/views/detail.py:161
msgid "flash.track_deleted"
msgstr "Strecke gelöscht"
@@ -888,23 +900,23 @@ msgstr "Hochladen erfolgreich"
msgid "flash.upload_cancelled"
msgstr "Hochladen abgebrochen"
-#: fietsboek/views/user_data.py:60
+#: fietsboek/views/user_data.py:61
msgid "flash.personal_data_updated"
msgstr "Persönliche Daten wurden gespeichert"
-#: fietsboek/views/user_data.py:78
+#: fietsboek/views/user_data.py:79
msgid "flash.friend_not_found"
msgstr "Das angegebene Konto wurde nicht gefunden"
-#: fietsboek/views/user_data.py:84
+#: fietsboek/views/user_data.py:85
msgid "flash.friend_already_exists"
msgstr "Dieser Freund existiert bereits"
-#: fietsboek/views/user_data.py:92
+#: fietsboek/views/user_data.py:93
msgid "flash.friend_added"
msgstr "Freund hinzugefügt"
-#: fietsboek/views/user_data.py:102
+#: fietsboek/views/user_data.py:103
msgid "flash.friend_request_sent"
msgstr "Freundschaftsanfrage gesendet"
diff --git a/fietsboek/locale/en/LC_MESSAGES/messages.mo b/fietsboek/locale/en/LC_MESSAGES/messages.mo
index 2a6205f..56137c1 100644
--- a/fietsboek/locale/en/LC_MESSAGES/messages.mo
+++ b/fietsboek/locale/en/LC_MESSAGES/messages.mo
Binary files differ
diff --git a/fietsboek/locale/en/LC_MESSAGES/messages.po b/fietsboek/locale/en/LC_MESSAGES/messages.po
index 6c13d78..c1ff29e 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: 2023-07-22 14:31+0200\n"
+"POT-Creation-Date: 2023-08-17 22:51+0200\n"
"PO-Revision-Date: 2023-04-03 20:42+0200\n"
"Last-Translator: \n"
"Language: en\n"
@@ -37,35 +37,35 @@ msgstr "Passwords don't match"
msgid "password_constraint.length"
msgstr "Password not long enough"
-#: fietsboek/models/track.py:589
+#: fietsboek/models/track.py:602
msgid "tooltip.table.length"
msgstr "Length"
-#: fietsboek/models/track.py:590
+#: fietsboek/models/track.py:603
msgid "tooltip.table.people"
msgstr "# People"
-#: fietsboek/models/track.py:591
+#: fietsboek/models/track.py:604
msgid "tooltip.table.uphill"
msgstr "Uphill"
-#: fietsboek/models/track.py:592
+#: fietsboek/models/track.py:605
msgid "tooltip.table.downhill"
msgstr "Downhill"
-#: fietsboek/models/track.py:593
+#: fietsboek/models/track.py:606
msgid "tooltip.table.moving_time"
msgstr "Moving Time"
-#: fietsboek/models/track.py:594
+#: fietsboek/models/track.py:607
msgid "tooltip.table.stopped_time"
msgstr "Stopped Time"
-#: fietsboek/models/track.py:596
+#: fietsboek/models/track.py:609
msgid "tooltip.table.max_speed"
msgstr "Max Speed"
-#: fietsboek/models/track.py:600
+#: fietsboek/models/track.py:613
msgid "tooltip.table.avg_speed"
msgstr "Average Speed"
@@ -137,109 +137,121 @@ msgstr "Recording (organic)"
msgid "page.browse.filter.type.synthetic"
msgstr "Template (synthetic)"
-#: fietsboek/templates/browse.jinja2:86
+#: fietsboek/templates/browse.jinja2:82
+msgid "page.browse.filter.favourite.all"
+msgstr "All Tracks"
+
+#: fietsboek/templates/browse.jinja2:83
+msgid "page.browse.filter.favourite.yes"
+msgstr "Only Favourites"
+
+#: fietsboek/templates/browse.jinja2:84
+msgid "page.browse.filter.favourite.no"
+msgstr "No Favourites"
+
+#: fietsboek/templates/browse.jinja2:97
msgid "page.browse.filter.my_tracks.only"
msgstr "Show only my own tracks"
-#: fietsboek/templates/browse.jinja2:87
+#: fietsboek/templates/browse.jinja2:98
msgid "page.browse.filter.friends_tracks_only"
msgstr "Show only tracks of my friends"
-#: fietsboek/templates/browse.jinja2:88
+#: fietsboek/templates/browse.jinja2:99
msgid "page.browse.filter.me_tagged_only"
msgstr "Show only tracks in which I'm tagged"
-#: fietsboek/templates/browse.jinja2:98
+#: fietsboek/templates/browse.jinja2:109
msgid "page.browse.filters.apply"
msgstr "Apply filters"
-#: fietsboek/templates/browse.jinja2:102
+#: fietsboek/templates/browse.jinja2:113
msgid "page.browse.filters.clear_all"
msgstr "Remove filters"
-#: fietsboek/templates/browse.jinja2:105
+#: fietsboek/templates/browse.jinja2:116
msgid "page.browse.filters.expand_advanced"
msgstr "Advanced"
-#: fietsboek/templates/browse.jinja2:110 fietsboek/templates/browse.jinja2:111
+#: fietsboek/templates/browse.jinja2:121 fietsboek/templates/browse.jinja2:122
msgid "page.browse.sort.date"
msgstr "Sort by date"
-#: fietsboek/templates/browse.jinja2:112 fietsboek/templates/browse.jinja2:113
+#: fietsboek/templates/browse.jinja2:123 fietsboek/templates/browse.jinja2:124
msgid "page.browse.sort.length"
msgstr "Sort by length"
-#: fietsboek/templates/browse.jinja2:114 fietsboek/templates/browse.jinja2:115
+#: fietsboek/templates/browse.jinja2:125 fietsboek/templates/browse.jinja2:126
msgid "page.browse.sort.duration"
msgstr "Sort by duration"
-#: fietsboek/templates/browse.jinja2:132
+#: fietsboek/templates/browse.jinja2:148
msgid "page.browse.organic_tooltip"
msgstr "This is a recording of a track"
-#: fietsboek/templates/browse.jinja2:134
+#: fietsboek/templates/browse.jinja2:150
msgid "page.browse.synthetic_tooltip"
msgstr "This is a pre-planned track"
-#: fietsboek/templates/browse.jinja2:142 fietsboek/templates/details.jinja2:90
+#: fietsboek/templates/browse.jinja2:158 fietsboek/templates/details.jinja2:103
#: fietsboek/templates/profile.jinja2:15
msgid "page.details.date"
msgstr "Date"
-#: fietsboek/templates/browse.jinja2:144 fietsboek/templates/details.jinja2:104
+#: fietsboek/templates/browse.jinja2:160 fietsboek/templates/details.jinja2:117
#: fietsboek/templates/profile.jinja2:17
msgid "page.details.length"
msgstr "Length"
-#: fietsboek/templates/browse.jinja2:149 fietsboek/templates/details.jinja2:95
+#: fietsboek/templates/browse.jinja2:165 fietsboek/templates/details.jinja2:108
#: fietsboek/templates/profile.jinja2:21
msgid "page.details.start_time"
msgstr "Record Start"
-#: fietsboek/templates/browse.jinja2:151 fietsboek/templates/details.jinja2:99
+#: fietsboek/templates/browse.jinja2:167 fietsboek/templates/details.jinja2:112
#: fietsboek/templates/profile.jinja2:23
msgid "page.details.end_time"
msgstr "Record End"
-#: fietsboek/templates/browse.jinja2:156 fietsboek/templates/details.jinja2:108
+#: fietsboek/templates/browse.jinja2:172 fietsboek/templates/details.jinja2:121
#: fietsboek/templates/profile.jinja2:27
msgid "page.details.uphill"
msgstr "Uphill"
-#: fietsboek/templates/browse.jinja2:158 fietsboek/templates/details.jinja2:112
+#: fietsboek/templates/browse.jinja2:174 fietsboek/templates/details.jinja2:125
#: fietsboek/templates/profile.jinja2:29
msgid "page.details.downhill"
msgstr "Downhill"
-#: fietsboek/templates/browse.jinja2:163 fietsboek/templates/details.jinja2:117
+#: fietsboek/templates/browse.jinja2:179 fietsboek/templates/details.jinja2:130
#: fietsboek/templates/profile.jinja2:33
msgid "page.details.moving_time"
msgstr "Moving Time"
-#: fietsboek/templates/browse.jinja2:165 fietsboek/templates/details.jinja2:121
+#: fietsboek/templates/browse.jinja2:181 fietsboek/templates/details.jinja2:134
#: fietsboek/templates/profile.jinja2:35
msgid "page.details.stopped_time"
msgstr "Stopped Time"
-#: fietsboek/templates/browse.jinja2:169 fietsboek/templates/details.jinja2:125
+#: fietsboek/templates/browse.jinja2:185 fietsboek/templates/details.jinja2:138
#: fietsboek/templates/profile.jinja2:39
msgid "page.details.max_speed"
msgstr "Max Speed"
-#: fietsboek/templates/browse.jinja2:171 fietsboek/templates/details.jinja2:129
+#: fietsboek/templates/browse.jinja2:187 fietsboek/templates/details.jinja2:142
#: fietsboek/templates/profile.jinja2:41
msgid "page.details.avg_speed"
msgstr "Average Speed"
-#: fietsboek/templates/browse.jinja2:189
+#: fietsboek/templates/browse.jinja2:205
msgid "page.browse.download_multiple"
msgstr "Download selected"
-#: fietsboek/templates/browse.jinja2:191
+#: fietsboek/templates/browse.jinja2:207
msgid "page.browse.no_results"
msgstr "No results matching the filters were found."
-#: fietsboek/templates/browse.jinja2:193
+#: fietsboek/templates/browse.jinja2:209
msgid "page.browse.no_tracks"
msgstr "You currently do not have access to any tracks. Try logging in."
@@ -283,88 +295,88 @@ msgstr "Repeat password"
msgid "page.create_account.create"
msgstr "Create"
-#: fietsboek/templates/details.jinja2:6
+#: fietsboek/templates/details.jinja2:7
msgid "page.details.title"
msgstr "Track Details"
-#: fietsboek/templates/details.jinja2:9
+#: fietsboek/templates/details.jinja2:20
msgid "page.details.edit"
msgstr "Edit"
-#: fietsboek/templates/details.jinja2:10
+#: fietsboek/templates/details.jinja2:21
msgid "page.details.share"
msgstr "Share"
-#: fietsboek/templates/details.jinja2:11
+#: fietsboek/templates/details.jinja2:22
msgid "page.details.delete"
msgstr "Delete"
-#: fietsboek/templates/details.jinja2:17
+#: fietsboek/templates/details.jinja2:28
msgid "page.details.sharelink.title"
msgstr "Share Link"
-#: fietsboek/templates/details.jinja2:21
+#: fietsboek/templates/details.jinja2:32
msgid "page.details.sharelink.info"
msgstr "Everyone with access to this link can view the track!"
-#: fietsboek/templates/details.jinja2:28
+#: fietsboek/templates/details.jinja2:39
msgid "page.details.sharelink.invalidate"
msgstr "Invalidate link"
-#: fietsboek/templates/details.jinja2:30
+#: fietsboek/templates/details.jinja2:41
msgid "page.details.sharelink.close"
msgstr "Close"
-#: fietsboek/templates/details.jinja2:40
+#: fietsboek/templates/details.jinja2:51
msgid "page.details.delete.title"
msgstr "Delete Track"
-#: fietsboek/templates/details.jinja2:44
+#: fietsboek/templates/details.jinja2:55
msgid "page.details.delete.info"
msgstr "Deleting this track will remove all associated information with it!"
-#: fietsboek/templates/details.jinja2:49
+#: fietsboek/templates/details.jinja2:60
msgid "page.details.delete.delete"
msgstr "Delete"
-#: fietsboek/templates/details.jinja2:51
+#: fietsboek/templates/details.jinja2:62
msgid "page.details.delete.close"
msgstr "Abort"
-#: fietsboek/templates/details.jinja2:70
+#: fietsboek/templates/details.jinja2:81
msgid "page.details.tags"
msgstr "Tagged as"
-#: fietsboek/templates/details.jinja2:80 fietsboek/templates/edit.jinja2:10
+#: fietsboek/templates/details.jinja2:91 fietsboek/templates/edit.jinja2:10
#: fietsboek/templates/finish_upload.jinja2:10
msgid "page.noscript"
msgstr "JavaScript is disabled, please enable JavaScript"
-#: fietsboek/templates/details.jinja2:85
+#: fietsboek/templates/details.jinja2:97
msgid "page.details.download"
msgstr "Download Tour"
-#: fietsboek/templates/details.jinja2:174
+#: fietsboek/templates/details.jinja2:187
msgid "page.details.comments"
msgstr "Comments"
-#: fietsboek/templates/details.jinja2:178
+#: fietsboek/templates/details.jinja2:191
msgid "page.details.comments.author"
msgstr "Comment by {}"
-#: fietsboek/templates/details.jinja2:195
+#: fietsboek/templates/details.jinja2:208
msgid "page.details.comments.new.title"
msgstr "Create a new comment"
-#: fietsboek/templates/details.jinja2:198
+#: fietsboek/templates/details.jinja2:211
msgid "page.details.comments.new.input_title"
msgstr "Title"
-#: fietsboek/templates/details.jinja2:199
+#: fietsboek/templates/details.jinja2:212
msgid "page.details.comments.new.input_comment"
msgstr "Comment"
-#: fietsboek/templates/details.jinja2:202
+#: fietsboek/templates/details.jinja2:215
msgid "page.details.comments.new.submit"
msgstr "Submit"
@@ -498,13 +510,13 @@ msgid "page.home.unfinished_uploads"
msgstr "You have unfinished uploads. Click on the links below to resume them:"
#: fietsboek/templates/home.jinja2:31 fietsboek/templates/home.jinja2:38
-#: fietsboek/templates/home.jinja2:59
+#: fietsboek/templates/home.jinja2:66
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:59
+#: fietsboek/templates/home.jinja2:66
msgid "page.home.total"
msgstr "Total"
@@ -814,50 +826,50 @@ msgstr "Your account is not verified yet"
msgid "flash.logged_in"
msgstr "You are now logged in"
-#: fietsboek/views/default.py:142
+#: fietsboek/views/default.py:143
msgid "flash.logged_out"
msgstr "You have been logged out"
-#: fietsboek/views/default.py:172
+#: fietsboek/views/default.py:173
msgid "flash.reset_invalid_email"
msgstr "Invalid email address provided"
-#: fietsboek/views/default.py:177
+#: fietsboek/views/default.py:178
msgid "flash.password_token_generated"
msgstr "A password reset email has been sent"
-#: fietsboek/views/default.py:182
+#: fietsboek/views/default.py:183
msgid "page.password_reset.email.subject"
msgstr "Fietsboek Password Reset"
-#: fietsboek/views/default.py:185
+#: fietsboek/views/default.py:186
msgid "page.password_reset.email.body"
msgstr ""
"You can reset your Fietsboek password here: {}\n"
"\n"
"If you did not request a password reset, ignore this email."
-#: fietsboek/views/default.py:224
+#: fietsboek/views/default.py:225
msgid "flash.resend_verification_email_fail"
msgstr "Invalid email address provided"
-#: fietsboek/views/default.py:229
+#: fietsboek/views/default.py:230
msgid "flash.verification_token_generated"
msgstr "A verification email has been sent"
-#: fietsboek/views/default.py:249
+#: fietsboek/views/default.py:250
msgid "flash.token_expired"
msgstr "The link has expired"
-#: fietsboek/views/default.py:255
+#: fietsboek/views/default.py:256
msgid "flash.email_verified"
msgstr "Your email address has been verified"
-#: fietsboek/views/default.py:269
+#: fietsboek/views/default.py:270
msgid "flash.password_updated"
msgstr "Password has been updated"
-#: fietsboek/views/detail.py:155
+#: fietsboek/views/detail.py:161
msgid "flash.track_deleted"
msgstr "Track has been deleted"
@@ -877,23 +889,23 @@ msgstr "Upload successful"
msgid "flash.upload_cancelled"
msgstr "Upload cancelled"
-#: fietsboek/views/user_data.py:60
+#: fietsboek/views/user_data.py:61
msgid "flash.personal_data_updated"
msgstr "Personal data has been updated"
-#: fietsboek/views/user_data.py:78
+#: fietsboek/views/user_data.py:79
msgid "flash.friend_not_found"
msgstr "The friend was not found"
-#: fietsboek/views/user_data.py:84
+#: fietsboek/views/user_data.py:85
msgid "flash.friend_already_exists"
msgstr "Friend already exists"
-#: fietsboek/views/user_data.py:92
+#: fietsboek/views/user_data.py:93
msgid "flash.friend_added"
msgstr "Friend has been added"
-#: fietsboek/views/user_data.py:102
+#: fietsboek/views/user_data.py:103
msgid "flash.friend_request_sent"
msgstr "Friend request sent"
diff --git a/fietsboek/locale/fietslog.pot b/fietsboek/locale/fietslog.pot
index f6577af..ed93194 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: 2023-07-22 14:31+0200\n"
+"POT-Creation-Date: 2023-08-17 22:51+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"
@@ -33,35 +33,35 @@ msgstr ""
msgid "password_constraint.length"
msgstr ""
-#: fietsboek/models/track.py:589
+#: fietsboek/models/track.py:602
msgid "tooltip.table.length"
msgstr ""
-#: fietsboek/models/track.py:590
+#: fietsboek/models/track.py:603
msgid "tooltip.table.people"
msgstr ""
-#: fietsboek/models/track.py:591
+#: fietsboek/models/track.py:604
msgid "tooltip.table.uphill"
msgstr ""
-#: fietsboek/models/track.py:592
+#: fietsboek/models/track.py:605
msgid "tooltip.table.downhill"
msgstr ""
-#: fietsboek/models/track.py:593
+#: fietsboek/models/track.py:606
msgid "tooltip.table.moving_time"
msgstr ""
-#: fietsboek/models/track.py:594
+#: fietsboek/models/track.py:607
msgid "tooltip.table.stopped_time"
msgstr ""
-#: fietsboek/models/track.py:596
+#: fietsboek/models/track.py:609
msgid "tooltip.table.max_speed"
msgstr ""
-#: fietsboek/models/track.py:600
+#: fietsboek/models/track.py:613
msgid "tooltip.table.avg_speed"
msgstr ""
@@ -133,109 +133,121 @@ msgstr ""
msgid "page.browse.filter.type.synthetic"
msgstr ""
-#: fietsboek/templates/browse.jinja2:86
+#: fietsboek/templates/browse.jinja2:82
+msgid "page.browse.filter.favourite.all"
+msgstr ""
+
+#: fietsboek/templates/browse.jinja2:83
+msgid "page.browse.filter.favourite.yes"
+msgstr ""
+
+#: fietsboek/templates/browse.jinja2:84
+msgid "page.browse.filter.favourite.no"
+msgstr ""
+
+#: fietsboek/templates/browse.jinja2:97
msgid "page.browse.filter.my_tracks.only"
msgstr ""
-#: fietsboek/templates/browse.jinja2:87
+#: fietsboek/templates/browse.jinja2:98
msgid "page.browse.filter.friends_tracks_only"
msgstr ""
-#: fietsboek/templates/browse.jinja2:88
+#: fietsboek/templates/browse.jinja2:99
msgid "page.browse.filter.me_tagged_only"
msgstr ""
-#: fietsboek/templates/browse.jinja2:98
+#: fietsboek/templates/browse.jinja2:109
msgid "page.browse.filters.apply"
msgstr ""
-#: fietsboek/templates/browse.jinja2:102
+#: fietsboek/templates/browse.jinja2:113
msgid "page.browse.filters.clear_all"
msgstr ""
-#: fietsboek/templates/browse.jinja2:105
+#: fietsboek/templates/browse.jinja2:116
msgid "page.browse.filters.expand_advanced"
msgstr ""
-#: fietsboek/templates/browse.jinja2:110 fietsboek/templates/browse.jinja2:111
+#: fietsboek/templates/browse.jinja2:121 fietsboek/templates/browse.jinja2:122
msgid "page.browse.sort.date"
msgstr ""
-#: fietsboek/templates/browse.jinja2:112 fietsboek/templates/browse.jinja2:113
+#: fietsboek/templates/browse.jinja2:123 fietsboek/templates/browse.jinja2:124
msgid "page.browse.sort.length"
msgstr ""
-#: fietsboek/templates/browse.jinja2:114 fietsboek/templates/browse.jinja2:115
+#: fietsboek/templates/browse.jinja2:125 fietsboek/templates/browse.jinja2:126
msgid "page.browse.sort.duration"
msgstr ""
-#: fietsboek/templates/browse.jinja2:132
+#: fietsboek/templates/browse.jinja2:148
msgid "page.browse.organic_tooltip"
msgstr ""
-#: fietsboek/templates/browse.jinja2:134
+#: fietsboek/templates/browse.jinja2:150
msgid "page.browse.synthetic_tooltip"
msgstr ""
-#: fietsboek/templates/browse.jinja2:142 fietsboek/templates/details.jinja2:90
+#: fietsboek/templates/browse.jinja2:158 fietsboek/templates/details.jinja2:103
#: fietsboek/templates/profile.jinja2:15
msgid "page.details.date"
msgstr ""
-#: fietsboek/templates/browse.jinja2:144 fietsboek/templates/details.jinja2:104
+#: fietsboek/templates/browse.jinja2:160 fietsboek/templates/details.jinja2:117
#: fietsboek/templates/profile.jinja2:17
msgid "page.details.length"
msgstr ""
-#: fietsboek/templates/browse.jinja2:149 fietsboek/templates/details.jinja2:95
+#: fietsboek/templates/browse.jinja2:165 fietsboek/templates/details.jinja2:108
#: fietsboek/templates/profile.jinja2:21
msgid "page.details.start_time"
msgstr ""
-#: fietsboek/templates/browse.jinja2:151 fietsboek/templates/details.jinja2:99
+#: fietsboek/templates/browse.jinja2:167 fietsboek/templates/details.jinja2:112
#: fietsboek/templates/profile.jinja2:23
msgid "page.details.end_time"
msgstr ""
-#: fietsboek/templates/browse.jinja2:156 fietsboek/templates/details.jinja2:108
+#: fietsboek/templates/browse.jinja2:172 fietsboek/templates/details.jinja2:121
#: fietsboek/templates/profile.jinja2:27
msgid "page.details.uphill"
msgstr ""
-#: fietsboek/templates/browse.jinja2:158 fietsboek/templates/details.jinja2:112
+#: fietsboek/templates/browse.jinja2:174 fietsboek/templates/details.jinja2:125
#: fietsboek/templates/profile.jinja2:29
msgid "page.details.downhill"
msgstr ""
-#: fietsboek/templates/browse.jinja2:163 fietsboek/templates/details.jinja2:117
+#: fietsboek/templates/browse.jinja2:179 fietsboek/templates/details.jinja2:130
#: fietsboek/templates/profile.jinja2:33
msgid "page.details.moving_time"
msgstr ""
-#: fietsboek/templates/browse.jinja2:165 fietsboek/templates/details.jinja2:121
+#: fietsboek/templates/browse.jinja2:181 fietsboek/templates/details.jinja2:134
#: fietsboek/templates/profile.jinja2:35
msgid "page.details.stopped_time"
msgstr ""
-#: fietsboek/templates/browse.jinja2:169 fietsboek/templates/details.jinja2:125
+#: fietsboek/templates/browse.jinja2:185 fietsboek/templates/details.jinja2:138
#: fietsboek/templates/profile.jinja2:39
msgid "page.details.max_speed"
msgstr ""
-#: fietsboek/templates/browse.jinja2:171 fietsboek/templates/details.jinja2:129
+#: fietsboek/templates/browse.jinja2:187 fietsboek/templates/details.jinja2:142
#: fietsboek/templates/profile.jinja2:41
msgid "page.details.avg_speed"
msgstr ""
-#: fietsboek/templates/browse.jinja2:189
+#: fietsboek/templates/browse.jinja2:205
msgid "page.browse.download_multiple"
msgstr ""
-#: fietsboek/templates/browse.jinja2:191
+#: fietsboek/templates/browse.jinja2:207
msgid "page.browse.no_results"
msgstr ""
-#: fietsboek/templates/browse.jinja2:193
+#: fietsboek/templates/browse.jinja2:209
msgid "page.browse.no_tracks"
msgstr ""
@@ -279,88 +291,88 @@ msgstr ""
msgid "page.create_account.create"
msgstr ""
-#: fietsboek/templates/details.jinja2:6
+#: fietsboek/templates/details.jinja2:7
msgid "page.details.title"
msgstr ""
-#: fietsboek/templates/details.jinja2:9
+#: fietsboek/templates/details.jinja2:20
msgid "page.details.edit"
msgstr ""
-#: fietsboek/templates/details.jinja2:10
+#: fietsboek/templates/details.jinja2:21
msgid "page.details.share"
msgstr ""
-#: fietsboek/templates/details.jinja2:11
+#: fietsboek/templates/details.jinja2:22
msgid "page.details.delete"
msgstr ""
-#: fietsboek/templates/details.jinja2:17
+#: fietsboek/templates/details.jinja2:28
msgid "page.details.sharelink.title"
msgstr ""
-#: fietsboek/templates/details.jinja2:21
+#: fietsboek/templates/details.jinja2:32
msgid "page.details.sharelink.info"
msgstr ""
-#: fietsboek/templates/details.jinja2:28
+#: fietsboek/templates/details.jinja2:39
msgid "page.details.sharelink.invalidate"
msgstr ""
-#: fietsboek/templates/details.jinja2:30
+#: fietsboek/templates/details.jinja2:41
msgid "page.details.sharelink.close"
msgstr ""
-#: fietsboek/templates/details.jinja2:40
+#: fietsboek/templates/details.jinja2:51
msgid "page.details.delete.title"
msgstr ""
-#: fietsboek/templates/details.jinja2:44
+#: fietsboek/templates/details.jinja2:55
msgid "page.details.delete.info"
msgstr ""
-#: fietsboek/templates/details.jinja2:49
+#: fietsboek/templates/details.jinja2:60
msgid "page.details.delete.delete"
msgstr ""
-#: fietsboek/templates/details.jinja2:51
+#: fietsboek/templates/details.jinja2:62
msgid "page.details.delete.close"
msgstr ""
-#: fietsboek/templates/details.jinja2:70
+#: fietsboek/templates/details.jinja2:81
msgid "page.details.tags"
msgstr ""
-#: fietsboek/templates/details.jinja2:80 fietsboek/templates/edit.jinja2:10
+#: fietsboek/templates/details.jinja2:91 fietsboek/templates/edit.jinja2:10
#: fietsboek/templates/finish_upload.jinja2:10
msgid "page.noscript"
msgstr ""
-#: fietsboek/templates/details.jinja2:85
+#: fietsboek/templates/details.jinja2:97
msgid "page.details.download"
msgstr ""
-#: fietsboek/templates/details.jinja2:174
+#: fietsboek/templates/details.jinja2:187
msgid "page.details.comments"
msgstr ""
-#: fietsboek/templates/details.jinja2:178
+#: fietsboek/templates/details.jinja2:191
msgid "page.details.comments.author"
msgstr ""
-#: fietsboek/templates/details.jinja2:195
+#: fietsboek/templates/details.jinja2:208
msgid "page.details.comments.new.title"
msgstr ""
-#: fietsboek/templates/details.jinja2:198
+#: fietsboek/templates/details.jinja2:211
msgid "page.details.comments.new.input_title"
msgstr ""
-#: fietsboek/templates/details.jinja2:199
+#: fietsboek/templates/details.jinja2:212
msgid "page.details.comments.new.input_comment"
msgstr ""
-#: fietsboek/templates/details.jinja2:202
+#: fietsboek/templates/details.jinja2:215
msgid "page.details.comments.new.submit"
msgstr ""
@@ -492,13 +504,13 @@ msgid "page.home.unfinished_uploads"
msgstr ""
#: fietsboek/templates/home.jinja2:31 fietsboek/templates/home.jinja2:38
-#: fietsboek/templates/home.jinja2:59
+#: fietsboek/templates/home.jinja2:66
msgid "page.home.summary.track"
msgid_plural "page.home.summary.tracks"
msgstr[0] ""
msgstr[1] ""
-#: fietsboek/templates/home.jinja2:59
+#: fietsboek/templates/home.jinja2:66
msgid "page.home.total"
msgstr ""
@@ -806,47 +818,47 @@ msgstr ""
msgid "flash.logged_in"
msgstr ""
-#: fietsboek/views/default.py:142
+#: fietsboek/views/default.py:143
msgid "flash.logged_out"
msgstr ""
-#: fietsboek/views/default.py:172
+#: fietsboek/views/default.py:173
msgid "flash.reset_invalid_email"
msgstr ""
-#: fietsboek/views/default.py:177
+#: fietsboek/views/default.py:178
msgid "flash.password_token_generated"
msgstr ""
-#: fietsboek/views/default.py:182
+#: fietsboek/views/default.py:183
msgid "page.password_reset.email.subject"
msgstr ""
-#: fietsboek/views/default.py:185
+#: fietsboek/views/default.py:186
msgid "page.password_reset.email.body"
msgstr ""
-#: fietsboek/views/default.py:224
+#: fietsboek/views/default.py:225
msgid "flash.resend_verification_email_fail"
msgstr ""
-#: fietsboek/views/default.py:229
+#: fietsboek/views/default.py:230
msgid "flash.verification_token_generated"
msgstr ""
-#: fietsboek/views/default.py:249
+#: fietsboek/views/default.py:250
msgid "flash.token_expired"
msgstr ""
-#: fietsboek/views/default.py:255
+#: fietsboek/views/default.py:256
msgid "flash.email_verified"
msgstr ""
-#: fietsboek/views/default.py:269
+#: fietsboek/views/default.py:270
msgid "flash.password_updated"
msgstr ""
-#: fietsboek/views/detail.py:155
+#: fietsboek/views/detail.py:161
msgid "flash.track_deleted"
msgstr ""
@@ -866,23 +878,23 @@ msgstr ""
msgid "flash.upload_cancelled"
msgstr ""
-#: fietsboek/views/user_data.py:60
+#: fietsboek/views/user_data.py:61
msgid "flash.personal_data_updated"
msgstr ""
-#: fietsboek/views/user_data.py:78
+#: fietsboek/views/user_data.py:79
msgid "flash.friend_not_found"
msgstr ""
-#: fietsboek/views/user_data.py:84
+#: fietsboek/views/user_data.py:85
msgid "flash.friend_already_exists"
msgstr ""
-#: fietsboek/views/user_data.py:92
+#: fietsboek/views/user_data.py:93
msgid "flash.friend_added"
msgstr ""
-#: fietsboek/views/user_data.py:102
+#: fietsboek/views/user_data.py:103
msgid "flash.friend_request_sent"
msgstr ""
diff --git a/fietsboek/models/track.py b/fietsboek/models/track.py
index a78e56a..0f5fd39 100644
--- a/fietsboek/models/track.py
+++ b/fietsboek/models/track.py
@@ -123,6 +123,14 @@ track_badge_assoc = Table(
Column("badge_id", ForeignKey("badges.id"), primary_key=True),
)
+
+track_favourite_assoc = Table(
+ "track_favourite_assoc",
+ Base.metadata,
+ Column("track_id", ForeignKey("tracks.id"), primary_key=True),
+ Column("user_id", ForeignKey("users.id"), primary_key=True),
+)
+
# Some words about timezone handling in saved tracks:
# https://www.youtube.com/watch?v=-5wpm-gesOY
#
@@ -190,6 +198,8 @@ class Track(Base):
:vartype comments: list[fietsboek.models.comment.Comment]
:ivar images: Metadata of the images saved for this track.
:vartype images: list[fietsboek.models.image.ImageMetadata]
+ :ivar favourees: List of users that have this track as a favourite.
+ :vartype favourees: list[fietsboek.models.user.User]
"""
__tablename__ = "tracks"
@@ -223,6 +233,9 @@ class Track(Base):
images: Mapped[list["models.ImageMetadata"]] = relationship(
"ImageMetadata", back_populates="track", cascade="all, delete-orphan"
)
+ favourees: Mapped[list["models.User"]] = relationship(
+ "User", secondary=track_favourite_assoc, back_populates="favourite_tracks"
+ )
@classmethod
def factory(cls, request):
@@ -683,6 +696,11 @@ class TrackWithMetadata:
"""Images of the underlying track."""
return self.track.images[:]
+ @property
+ def favourees(self) -> List["models.User"]:
+ """People who have favoured this track."""
+ return self.track.favourees[:]
+
def text_tags(self) -> Set[str]:
"""Returns a set of textual tags.
diff --git a/fietsboek/models/user.py b/fietsboek/models/user.py
index 9dd2298..432c61d 100644
--- a/fietsboek/models/user.py
+++ b/fietsboek/models/user.py
@@ -97,7 +97,7 @@ class User(Base):
:ivar tracks: Tracks owned by this user.
:vartype tracks: list[fietsboek.models.track.Track]
:ivar tagged_tracks: Tracks in which this user is tagged.
- :vartype tracks: list[fietsboek.models.track.Track]
+ :vartype tagged_tracks: list[fietsboek.models.track.Track]
:ivar uploads: Currently ongoing uploads by this user.
:vartype uploads: list[fietsboek.models.track.Upload]
:ivar tokens: List of tokens that this user can use.
@@ -121,6 +121,9 @@ class User(Base):
tagged_tracks: Mapped[list["Track"]] = relationship(
"Track", secondary="track_people_assoc", back_populates="tagged_people"
)
+ favourite_tracks: Mapped[list["Track"]] = relationship(
+ "Track", secondary="track_favourite_assoc", back_populates="favourees"
+ )
uploads: Mapped[list["Upload"]] = relationship(
"Upload", back_populates="owner", cascade="all, delete-orphan"
)
@@ -424,6 +427,16 @@ class User(Base):
stmt = friends_assoc.insert().values(user_1_id=self.id, user_2_id=friend.id)
session.execute(stmt)
+ def toggle_favourite(self, track: "Track"):
+ """Toggles the favourite status for the given track.
+
+ :param track: The track to (un)favour.
+ """
+ if track in self.favourite_tracks:
+ self.favourite_tracks.remove(track)
+ else:
+ self.favourite_tracks.append(track)
+
def autocomplete_tags(self):
"""Returns all tags the user has ever used, suitable for autocompletion
lists.
diff --git a/fietsboek/routes.py b/fietsboek/routes.py
index cfcc7bf..36233e6 100644
--- a/fietsboek/routes.py
+++ b/fietsboek/routes.py
@@ -59,6 +59,7 @@ def includeme(config):
config.add_route("delete-friend", "/me/delete-friend")
config.add_route("accept-friend", "/me/accept-friend")
config.add_route("json-friends", "/me/friends.json")
+ config.add_route("toggle-favourite", "/me/toggle-favourite")
config.add_route("profile", "/user/{user_id}", factory="fietsboek.models.User.factory")
config.add_route(
diff --git a/fietsboek/static/fietsboek.js b/fietsboek/static/fietsboek.js
index 0179005..c9dfe2a 100644
--- a/fietsboek/static/fietsboek.js
+++ b/fietsboek/static/fietsboek.js
@@ -2,6 +2,17 @@
// Make eslint happy about the Window redefinition
(_) => null;
/**
+ * Gets the value of a single cookie.
+ *
+ * @param name - Name of the cookie.
+ * @return The cookie value, or null.
+ */
+function getCookie(name) {
+ var _a;
+ return (_a = document.cookie.split("; ")
+ .find((row) => row.startsWith(`${name}=`))) === null || _a === void 0 ? void 0 : _a.split("=")[1];
+}
+/**
* Installs a listener to the given DOM objects.
*
* @param selector - The query selector to find the DOM objects.
@@ -329,14 +340,46 @@ addHandler(".button-clear-input", "click", clearInputButtonClicked);
* @param event - The triggering event.
*/
function changeHomeSorting(_event) {
- var _a, _b;
- const currentSorting = (_b = (_a = document.cookie.split("; ")
- .find((row) => row.startsWith("home_sorting="))) === null || _a === void 0 ? void 0 : _a.split("=")[1]) !== null && _b !== void 0 ? _b : "asc";
+ var _a;
+ const currentSorting = (_a = getCookie("home_sorting")) !== null && _a !== void 0 ? _a : "asc";
const newSorting = currentSorting == "asc" ? "desc" : "asc";
document.cookie = `home_sorting=${newSorting}; SameSite=Lax`;
window.location.reload();
}
addHandler("#changeHomeSorting", "click", changeHomeSorting);
+/**
+ * Handler to toggle the favourite status of a track.
+ *
+ * This is applied to .favourite-star elements and expects the track ID in
+ * data-track-id.
+ *
+ * @param event - The triggering event.
+ */
+function toggleTrackFavourite(event) {
+ var _a;
+ const target = event.target;
+ const trackId = target.getAttribute("data-track-id");
+ if (trackId === null) {
+ return;
+ }
+ const url = new URL("/me/toggle-favourite", window.location.href);
+ const formData = new URLSearchParams();
+ formData.append("track-id", trackId);
+ formData.append("csrf_token", (_a = getCookie("csrf_token")) !== null && _a !== void 0 ? _a : "");
+ fetch(url, {
+ "method": "POST",
+ "body": formData,
+ }).then(response => response.json().then(data => {
+ const isNowFavourite = data["favourite"];
+ if (isNowFavourite) {
+ target.classList.replace("bi-star", "bi-star-fill");
+ }
+ else {
+ target.classList.replace("bi-star-fill", "bi-star");
+ }
+ }));
+}
+addHandler(".favourite-star", "click", toggleTrackFavourite);
document.addEventListener('DOMContentLoaded', function () {
window.fietsboekImageIndex = 0;
/* Enable tooltips */
diff --git a/fietsboek/static/fietsboek.js.map b/fietsboek/static/fietsboek.js.map
index 7304128..557b442 100644
--- a/fietsboek/static/fietsboek.js.map
+++ b/fietsboek/static/fietsboek.js.map
@@ -1 +1 @@
-{"version":3,"file":"fietsboek.js","sourceRoot":"","sources":["../../asset-sources/fietsboek.ts"],"names":[],"mappings":"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"} \ No newline at end of file
+{"version":3,"file":"fietsboek.js","sourceRoot":"","sources":["../../asset-sources/fietsboek.ts"],"names":[],"mappings":"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"} \ No newline at end of file
diff --git a/fietsboek/static/theme.css b/fietsboek/static/theme.css
index d97192b..f319c4c 100644
--- a/fietsboek/static/theme.css
+++ b/fietsboek/static/theme.css
@@ -112,6 +112,14 @@ strong {
padding-left: 1em;
}
+.favourite-star, .favourite-star-ni {
+ color: orange;
+}
+
+.favourite-star {
+ cursor: pointer;
+}
+
/* Ensure a consistent width of the cells in the browse view. */
.browse-summary th, .browse-summary td {
width: 25%;
diff --git a/fietsboek/static/theme.css.map b/fietsboek/static/theme.css.map
index 632d85f..36a6762 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;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;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;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;;;AAIJ;AACA;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;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;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;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;;;AAIJ;EACE;;;AAGF;EACE;;;AAGF;AACA;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/browse.jinja2 b/fietsboek/templates/browse.jinja2
index 9860201..1eae88d 100644
--- a/fietsboek/templates/browse.jinja2
+++ b/fietsboek/templates/browse.jinja2
@@ -74,6 +74,17 @@
<option value="SYNTHETIC"{% if "SYNTHETIC" in request.params.getall("type[]") %} selected{% endif %}>{{ _("page.browse.filter.type.synthetic") }}</option>
</select>
</div>
+
+ {% if request.identity %}
+ <div class="input-group mt-2">
+ <button type="button" class="btn btn-outline-secondary button-clear-input"><i class="bi bi-eraser-fill"></i></button>
+ <select class="form-select" name="favourite">
+ <option value="">{{ _("page.browse.filter.favourite.all") }}</option>
+ <option value="true"{% if request.params.get("favourite") == "true" %} selected{% endif %}>{{ _("page.browse.filter.favourite.yes") }}</option>
+ <option value="false"{% if request.params.get("favourite") == "false" %} selected{% endif %}>{{ _("page.browse.filter.favourite.no") }}</option>
+ </select>
+ </div>
+ {% endif %}
</div>
<div class="col-md-6">
{% if request.identity %}
@@ -123,6 +134,11 @@
<div class="card mb-3">
<h5 class="card-header">
<input type="checkbox" class="form-check-input archive-checkbox" name="track_id[]" value="{{ track.id }}">
+ {% if request.identity and request.identity in track.favourees %}
+ <i class="bi bi-star-fill favourite-star" data-track-id="{{ track.id }}"></i>
+ {% else %}
+ <i class="bi bi-star favourite-star" data-track-id="{{ track.id }}"></i>
+ {% endif %}
<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 %}
diff --git a/fietsboek/templates/details.jinja2 b/fietsboek/templates/details.jinja2
index b7ebefe..7307270 100644
--- a/fietsboek/templates/details.jinja2
+++ b/fietsboek/templates/details.jinja2
@@ -3,7 +3,18 @@
{% block content %}
<div class="container">
- <h1>{{ track.title | default(_("page.details.title"), true) }}</h1>
+ <h1>
+ {{ track.title | default(_("page.details.title"), true) }}
+ {% if request.identity %}
+ <span style="display: inline-flex; vertical-align: middle;">
+ {% if request.identity in track.favourees %}
+ <i class="bi bi-star-fill favourite-star" data-track-id="{{ track.id }}"></i>
+ {% else %}
+ <i class="bi bi-star favourite-star" data-track-id="{{ track.id }}"></i>
+ {% endif %}
+ </span>
+ {% endif %}
+ </h1>
{% if show_edit_link %}
<div class="btn-group" role="group">
<a class="btn btn-success" href="{{ request.route_path('edit', track_id=track.id) }}"><i class="bi-pencil-square"></i> {{ _("page.details.edit") }}</a>
diff --git a/fietsboek/templates/home.jinja2 b/fietsboek/templates/home.jinja2
index 383e90a..d3725ca 100644
--- a/fietsboek/templates/home.jinja2
+++ b/fietsboek/templates/home.jinja2
@@ -44,6 +44,9 @@
<a href="{{ request.route_url('details', track_id=track.id) }}" data-bs-toggle="tooltip" data-bs-container="body" data-bs-html="true" title="{{ track.html_tooltip(request.localizer) }}">
{{ track.title | default(track.date, true) }}
</a>
+ {% if request.identity in track.favourees %}
+ <i class="bi bi-star-fill favourite-star-ni"></i>
+ {% endif %}
<span class="float-end">
{% if track.tagged_people %}
<span data-bs-toggle="tooltip" data-bs-container="body" data-bs-html="true" title="{{ track.html_tooltip_tagged_people() }}">
diff --git a/fietsboek/views/browse.py b/fietsboek/views/browse.py
index d4f48b7..f23d3a1 100644
--- a/fietsboek/views/browse.py
+++ b/fietsboek/views/browse.py
@@ -10,7 +10,7 @@ from pyramid.httpexceptions import HTTPBadRequest, HTTPForbidden, HTTPNotFound
from pyramid.request import Request
from pyramid.response import Response
from pyramid.view import view_config
-from sqlalchemy import func, or_, select
+from sqlalchemy import func, not_, or_, select
from sqlalchemy.orm import aliased
from sqlalchemy.sql import Select
@@ -226,6 +226,38 @@ class UserTaggedFilter(Filter):
return track.owner == self.user or self.user in track.tagged_people
+class FavouriteFilter(Filter):
+ """A :class:`Filter` that accepts only favoured or non-favoured tracks."""
+
+ def __init__(self, user: models.User, favourite: bool):
+ """Sets up the filter.
+
+ :param user: The user for which the favourite status should be checked.
+ :param favourite: ``True`` if only favourites should be returned,
+ ``False`` otherwise.
+ """
+ self.user = user
+ self.favourite = favourite
+
+ def compile(self, query: Select, track: AliasedTrack) -> Select:
+ tfa = models.track.track_favourite_assoc
+ if self.favourite:
+ invert = lambda x: x
+ else:
+ invert = not_
+ return query.where(
+ invert(
+ select(tfa)
+ .where(tfa.c.track_id == track.id)
+ .where(tfa.c.user_id == self.user.id)
+ .exists()
+ )
+ )
+
+ def apply(self, track: TrackWithMetadata) -> bool:
+ return (self.user in track.favourees) == self.favourite
+
+
class FilterCollection(Filter):
"""A class that applies multiple :class:`Filter`."""
@@ -345,6 +377,10 @@ class FilterCollection(Filter):
)
)
+ if request.identity and request.params.get("favourite"):
+ favourite = request.params.get("favourite") == "true"
+ filters.append(FavouriteFilter(request.identity, favourite))
+
return cls(filters)
diff --git a/fietsboek/views/user_data.py b/fietsboek/views/user_data.py
index a6ad11d..a7680e4 100644
--- a/fietsboek/views/user_data.py
+++ b/fietsboek/views/user_data.py
@@ -3,6 +3,7 @@ import datetime
from pyramid.httpexceptions import HTTPForbidden, HTTPFound, HTTPNotFound
from pyramid.i18n import TranslationString as _
+from pyramid.request import Request
from pyramid.view import view_config
from sqlalchemy import select
@@ -157,3 +158,21 @@ def json_friends(request):
"""
friends = [{"name": friend.name, "id": friend.id} for friend in request.identity.get_friends()]
return friends
+
+
+@view_config(
+ route_name="toggle-favourite", renderer="json", permission="user", request_method="POST"
+)
+def do_toggle_favourite(request: Request) -> dict:
+ """Toggles the favourite status for the given track.
+
+ :param request: The Pyramid request.
+ :return: The data to return to the client.
+ """
+ track = request.dbsession.execute(
+ select(models.Track).filter_by(id=request.params["track-id"])
+ ).scalar_one_or_none()
+ if track is None:
+ return HTTPNotFound()
+ request.identity.toggle_favourite(track)
+ return {"favourite": request.identity in track.favourees}
diff --git a/pylint.toml b/pylint.toml
index 3f011cb..b6dbaef 100644
--- a/pylint.toml
+++ b/pylint.toml
@@ -345,7 +345,17 @@ confidence = ["HIGH", "CONTROL_FLOW", "INFERENCE", "INFERENCE_FAILURE", "UNDEFIN
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use "--disable=all --enable=classes
# --disable=W".
-disable = ["raw-checker-failed", "bad-inline-option", "locally-disabled", "file-ignored", "suppressed-message", "useless-suppression", "deprecated-pragma", "use-symbolic-message-instead"]
+disable = [
+ "raw-checker-failed",
+ "bad-inline-option",
+ "locally-disabled",
+ "file-ignored",
+ "suppressed-message",
+ "useless-suppression",
+ "deprecated-pragma",
+ "use-symbolic-message-instead",
+ "unnecessary-lambda-assignment",
+]
# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
diff --git a/tests/integration/test_upload.py b/tests/integration/test_upload.py
index 454785f..f09b468 100644
--- a/tests/integration/test_upload.py
+++ b/tests/integration/test_upload.py
@@ -1,3 +1,5 @@
+import re
+
from sqlalchemy import select, func
from webtest import Upload
@@ -30,7 +32,7 @@ def test_upload(testapp, dbsession, route_path, logged_in):
]
result = finish_form.submit().maybe_follow()
- assert "<h1>FoOoOo Bar&#39;s Testtrack</h1>" in result.text
+ assert re.search("<h1>\\s*FoOoOo Bar&#39;s Testtrack", result.text)
# Step 4: Ensure the track is stored right
track = dbsession.execute(select(models.Track)).scalar_one()
diff --git a/tests/playwright/test_basic.py b/tests/playwright/test_basic.py
index a98e52d..66ec326 100644
--- a/tests/playwright/test_basic.py
+++ b/tests/playwright/test_basic.py
@@ -146,3 +146,23 @@ def test_delete(page: Page, playwright_helper, dbaccess, data_manager):
assert track is None
with pytest.raises(FileNotFoundError):
data_manager.open(track_id)
+
+
+def test_favour(page: Page, playwright_helper, dbaccess, data_manager):
+ john = playwright_helper.john_doe()
+ playwright_helper.login()
+ track_id = playwright_helper.add_track().id
+
+ page.goto(f"/track/{track_id}")
+
+ with page.expect_request_finished(predicate=lambda req: "/toggle-favourite" in req.url):
+ page.locator(".favourite-star").click()
+
+ track = dbaccess.execute(select(models.Track).filter_by(id=track_id)).scalar_one_or_none()
+ assert john.id in [user.id for user in track.favourees]
+
+ with page.expect_request_finished(predicate=lambda req: "/toggle-favourite" in req.url):
+ page.locator(".favourite-star").click()
+
+ dbaccess.refresh(track, ["favourees"])
+ assert john.id not in [user.id for user in track.favourees]
diff --git a/tox.ini b/tox.ini
index d922d4b..4c0e38e 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,7 +1,7 @@
[flake8]
max-line-length = 100
exclude = fietsboek/alembic
-extend-ignore = E203
+extend-ignore = E203 E731
per-file-ignores =
fietsboek/models/__init__.py:F401