Files
voyage/backend/server/adventures/views/collection_view.py
Sean Morley 240c617010 Bug Fixes + Speed Improvements (#871)
* Show timezone information on visits list (#865)

* Initial plan

* Initial investigation: Show timezone on visits list - planning implementation

Co-authored-by: seanmorley15 <98704938+seanmorley15@users.noreply.github.com>

* Show timezone on visits list - add timezone badge display

Co-authored-by: seanmorley15 <98704938+seanmorley15@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: seanmorley15 <98704938+seanmorley15@users.noreply.github.com>

* fix: add additional timezones (#852)

* [BUG] Transportation node does not accept complex links in link parameter
Fixes #856

* Squashed commit of the following:

commit 59d5128cc642d133b0c166fbaf2d41a88c237d92
Merge: 0f9d31f4 7b8961e0
Author: Hosted Weblate <hosted@weblate.org>
Date:   Fri Sep 19 04:22:36 2025 +0200

    Merge branch 'origin/development' into Weblate.

commit 7b8961e02430b9b6fab7b22a7a8c1f7b06ff950b
Author: Orhun <orhunavcu@gmail.com>
Date:   Fri Sep 19 00:30:25 2025 +0200

    Translated using Weblate (Turkish)

    Currently translated at 16.7% (160 of 958 strings)

    Translation: AdventureLog/Web App
    Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/tr/

commit a8134bdbda318d00175c984785d150e38a1e24bf
Author: Orhun <orhunavcu@gmail.com>
Date:   Thu Sep 18 22:52:56 2025 +0200

    Added translation using Weblate (Turkish)

commit ac8a8ee8c9fc55da2d4ded1c4beac04a1ea66bb8
Merge: 2527e345 3fca3872
Author: Hosted Weblate <hosted@weblate.org>
Date:   Thu Sep 18 14:11:58 2025 +0200

    Merge branch 'origin/development' into Weblate.

commit 3fca387272d52dfcb634751a74e4a4b4fcf7ac6b
Merge: 4907ba87 85d8b45c
Author: Hosted Weblate <hosted@weblate.org>
Date:   Thu Sep 18 05:13:16 2025 +0200

    Merge branch 'origin/development' into Weblate.

commit 85d8b45c4eff77bcc9d5708f7fd8a4fcea91890b
Merge: 9f5cc9cc ae07c440
Author: Sean Morley <mail@seanmorley.com>
Date:   Wed Sep 17 19:25:35 2025 -0400

    Merge branch 'development' of github.com:seanmorley15/AdventureLog into development

commit 9f5cc9ccb8809cd5ae0c01e979dae271e9197987
Author: Sean Morley <mail@seanmorley.com>
Date:   Wed Sep 17 19:23:47 2025 -0400

    Remove duplicate comment for syncing development branch with main in .env.example

commit 77c1f516266b2db5e1c97c5cbc8a3fb9e08d40cd
Author: Sean Morley <mail@seanmorley.com>
Date:   Wed Sep 17 19:22:36 2025 -0400

    Squashed commit of the following:

    commit 9d4f1b8f534a3cdfb22812f2a25ababd7a236a87
    Author: Jacob <jacob.aulin@proton.me>
    Date:   Sat Sep 13 15:17:22 2025 +0200

        Translated using Weblate (Swedish)

        Currently translated at 99.8% (957 of 958 strings)

        Translation: AdventureLog/Web App
        Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/sv/

    commit 8fac40cfde425c989521c891b3ba9c75ab32e57e
    Author: Christian S <schuld.christian@gmail.com>
    Date:   Sat Sep 13 12:54:52 2025 +0200

        Translated using Weblate (German)

        Currently translated at 100.0% (958 of 958 strings)

        Translation: AdventureLog/Web App
        Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/de/

    commit 8e8c42396ec77b763983155e8b1e89cabf38ce17
    Author: Patricio Carrau <duckycb@proton.me>
    Date:   Tue Sep 9 21:59:48 2025 +0200

        Translated using Weblate (Spanish)

        Currently translated at 100.0% (958 of 958 strings)

        Translation: AdventureLog/Web App
        Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/es/

    commit be818ab408d00c5c26dfb3b25632604a415d3570
    Author: pplulee <hi@pplulee.me>
    Date:   Mon Sep 8 04:06:54 2025 +0100

        fix(i18n): update Chinese translations for location-related terms (#829)

        Co-authored-by: Sean Morley <98704938+seanmorley15@users.noreply.github.com>

    commit 9e40dcf6a1dc194d4694a114b3c7e88135121016
    Merge: af2f2809 733eefce
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sun Sep 7 23:03:57 2025 -0400

        Merge remote-tracking branch 'weblate/development' into development

    commit af2f28090b9242fb7ab263fa5bbb95a5bcc1b27f
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sun Sep 7 23:00:33 2025 -0400

        [BUG] Location Visit End Date not affected by Location Timezone
        Fixes #843

    commit 733eefceddbdad01726364e5d4523605f095fde2
    Author: Alex <div@alexe.at>
    Date:   Sun Sep 7 23:28:20 2025 +0200

        Translated using Weblate (German)

        Currently translated at 100.0% (958 of 958 strings)

        Translation: AdventureLog/Web App
        Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/de/

    commit 6c750d1c8f95b42418893e15ad46c3d4ed86d053
    Author: fantastron27 <fantastron27@gmail.com>
    Date:   Sun Sep 7 09:17:16 2025 +0200

        Translated using Weblate (Slovak)

        Currently translated at 100.0% (958 of 958 strings)

        Translation: AdventureLog/Web App
        Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/sk/

    commit f733b3b96bbddc71d426f2e60320a5ad2f6755af
    Merge: 769ea6ad af4e541c
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 21:36:35 2025 -0400

        Merge branch 'development' of github.com:seanmorley15/AdventureLog into development

    commit 769ea6ad710890e931aabace2c00dc37436f869f
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 21:36:33 2025 -0400

        Implement code changes to enhance functionality and improve performance

    commit af4e541c1c9e7309857102287199279aec339387
    Author: fantastron27 <fantastron27@gmail.com>
    Date:   Sun Sep 7 03:36:23 2025 +0200

        Added Slovak translations (#815)

        * Created sk.json

        * Update Navbar.svelte

        * Update +layout.svelte

        ---------

        Co-authored-by: Sean Morley <98704938+seanmorley15@users.noreply.github.com>

    commit 904474d757577229b47441d1378a6fd6788fbe40
    Merge: d4709434 f87a5fe3
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 21:31:58 2025 -0400

        Merge remote-tracking branch 'weblate/development' into development

    commit d47094346c0b63ea753294a0786414e5e070ae7f
    Merge: 4a5f59bf 6366a3eb
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 21:29:39 2025 -0400

        Merge remote-tracking branch 'weblate/development' into development

    commit f87a5fe3bcc2fe28cfc206fb5cba517bbffa8df6
    Author: Sergio <garcia.sergio@me.com>
    Date:   Sun Sep 7 01:12:50 2025 +0200

        Translated using Weblate (Spanish)

        Currently translated at 100.0% (956 of 956 strings)

        Translation: AdventureLog/Web App
        Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/es/

    commit 4a5f59bfd24e32fdf3558b009a8f636636cb3663
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 21:06:17 2025 -0400

        Fixes #654

    commit c1302bb54ab272c2a98c53ce0d508b7d39e9674b
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 20:52:05 2025 -0400

        [BUG] Single day Collections will think location visits are out of date range
        Fixes #827

    commit 773f2d65bbfb2a9591b31fabfd6844612b840f1a
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 19:52:28 2025 -0400

        [BUG] Server Error (500) when trying to access the API docs
        Fixes #712

    commit 4228db249ed5e3261931a1cdb3895d0ddd3ac4ac
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 19:44:00 2025 -0400

        [BUG]Ordered Itinerary includes visits that are outside itinerary date range
        Fixes #746

    commit 26f36cabb0a860f10d7ba62b5279ddd1e282c78e
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 18:36:50 2025 +0200

        Translated using Weblate (Spanish)

        Currently translated at 100.0% (956 of 956 strings)

        Translation: AdventureLog/Web App
        Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/es/

    commit 3bfd2dd5615afdbd04e3451c2ef728f1d7caf466
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 12:33:23 2025 -0400

        Remove empty English (United States) locale file

    commit 6366a3eba6ab72090e52be474212a663799dfe19
    Author: Nikolai Eidsheim <nikolai.eidsheim@gmail.com>
    Date:   Sat Sep 6 18:10:15 2025 +0200

        Translated using Weblate (Norwegian Bokmål)

        Currently translated at 100.0% (956 of 956 strings)

        Translation: AdventureLog/Web App
        Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/nb_NO/

    commit 671cd3701fc5a601f2f1bad9aef93106f91eec0b
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 16:58:04 2025 +0200

        Added translation using Weblate (English (United States))

    commit bdbbe5f4978f041f620f0503da69fa870cb1997c
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 16:54:43 2025 +0200

        Translated using Weblate (Spanish)

        Currently translated at 100.0% (956 of 956 strings)

        Translation: AdventureLog/Web App
        Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/es/

commit f310771702291be93e1c6b3bdb5a76e02fb8cae8
Author: Sean Morley <98704938+seanmorley15@users.noreply.github.com>
Date:   Mon Sep 8 22:38:08 2025 -0400

    Update issue templates for bug reports, deployment issues, and feature requests (#849)

commit 02ed89fa46fa22f8c5d96b0e7f0948204dec9306
Author: Sean Morley <98704938+seanmorley15@users.noreply.github.com>
Date:   Sun Sep 7 23:16:12 2025 -0400

    Timezone fixes, Translations, and Misc Fixes (#844)

    * Translated using Weblate (Spanish)

    Currently translated at 100.0% (956 of 956 strings)

    Translation: AdventureLog/Web App
    Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/es/

    * Added translation using Weblate (English (United States))

    * Translated using Weblate (Norwegian Bokmål)

    Currently translated at 100.0% (956 of 956 strings)

    Translation: AdventureLog/Web App
    Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/nb_NO/

    * Remove empty English (United States) locale file

    * Translated using Weblate (Spanish)

    Currently translated at 100.0% (956 of 956 strings)

    Translation: AdventureLog/Web App
    Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/es/

    * [BUG]Ordered Itinerary includes visits that are outside itinerary date range
    Fixes #746

    * [BUG] Server Error (500) when trying to access the API docs
    Fixes #712

    * [BUG] Single day Collections will think location visits are out of date range
    Fixes #827

    * Fixes #654

    * Translated using Weblate (Spanish)

    Currently translated at 100.0% (956 of 956 strings)

    Translation: AdventureLog/Web App
    Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/es/

    * Added Slovak translations (#815)

    * Created sk.json

    * Update Navbar.svelte

    * Update +layout.svelte

    ---------

    Co-authored-by: Sean Morley <98704938+seanmorley15@users.noreply.github.com>

    * Implement code changes to enhance functionality and improve performance

    * Translated using Weblate (Slovak)

    Currently translated at 100.0% (958 of 958 strings)

    Translation: AdventureLog/Web App
    Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/sk/

    * Translated using Weblate (German)

    Currently translated at 100.0% (958 of 958 strings)

    Translation: AdventureLog/Web App
    Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/de/

    * [BUG] Location Visit End Date not affected by Location Timezone
    Fixes #843

    * fix(i18n): update Chinese translations for location-related terms (#829)

    Co-authored-by: Sean Morley <98704938+seanmorley15@users.noreply.github.com>

    ---------

    Co-authored-by: Nikolai Eidsheim <nikolai.eidsheim@gmail.com>
    Co-authored-by: Sergio <garcia.sergio@me.com>
    Co-authored-by: fantastron27 <fantastron27@gmail.com>
    Co-authored-by: Alex <div@alexe.at>
    Co-authored-by: pplulee <hi@pplulee.me>

commit ae07c44030ff597be80988b63460595e8bea132f
Author: Sean Morley <mail@seanmorley.com>
Date:   Wed Sep 17 19:23:47 2025 -0400

    Remove duplicate comment for syncing development branch with main in .env.example

commit 94964f1fb16583f1ff63ddf4aee2469a532b0c6c
Author: Sean Morley <mail@seanmorley.com>
Date:   Wed Sep 17 19:22:36 2025 -0400

    Squashed commit of the following:

    commit 9d4f1b8f534a3cdfb22812f2a25ababd7a236a87
    Author: Jacob <jacob.aulin@proton.me>
    Date:   Sat Sep 13 15:17:22 2025 +0200

        Translated using Weblate (Swedish)

        Currently translated at 99.8% (957 of 958 strings)

        Translation: AdventureLog/Web App
        Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/sv/

    commit 8fac40cfde425c989521c891b3ba9c75ab32e57e
    Author: Christian S <schuld.christian@gmail.com>
    Date:   Sat Sep 13 12:54:52 2025 +0200

        Translated using Weblate (German)

        Currently translated at 100.0% (958 of 958 strings)

        Translation: AdventureLog/Web App
        Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/de/

    commit 8e8c42396ec77b763983155e8b1e89cabf38ce17
    Author: Patricio Carrau <duckycb@proton.me>
    Date:   Tue Sep 9 21:59:48 2025 +0200

        Translated using Weblate (Spanish)

        Currently translated at 100.0% (958 of 958 strings)

        Translation: AdventureLog/Web App
        Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/es/

    commit be818ab408d00c5c26dfb3b25632604a415d3570
    Author: pplulee <hi@pplulee.me>
    Date:   Mon Sep 8 04:06:54 2025 +0100

        fix(i18n): update Chinese translations for location-related terms (#829)

        Co-authored-by: Sean Morley <98704938+seanmorley15@users.noreply.github.com>

    commit 9e40dcf6a1dc194d4694a114b3c7e88135121016
    Merge: af2f2809 733eefce
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sun Sep 7 23:03:57 2025 -0400

        Merge remote-tracking branch 'weblate/development' into development

    commit af2f28090b9242fb7ab263fa5bbb95a5bcc1b27f
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sun Sep 7 23:00:33 2025 -0400

        [BUG] Location Visit End Date not affected by Location Timezone
        Fixes #843

    commit 733eefceddbdad01726364e5d4523605f095fde2
    Author: Alex <div@alexe.at>
    Date:   Sun Sep 7 23:28:20 2025 +0200

        Translated using Weblate (German)

        Currently translated at 100.0% (958 of 958 strings)

        Translation: AdventureLog/Web App
        Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/de/

    commit 6c750d1c8f95b42418893e15ad46c3d4ed86d053
    Author: fantastron27 <fantastron27@gmail.com>
    Date:   Sun Sep 7 09:17:16 2025 +0200

        Translated using Weblate (Slovak)

        Currently translated at 100.0% (958 of 958 strings)

        Translation: AdventureLog/Web App
        Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/sk/

    commit f733b3b96bbddc71d426f2e60320a5ad2f6755af
    Merge: 769ea6ad af4e541c
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 21:36:35 2025 -0400

        Merge branch 'development' of github.com:seanmorley15/AdventureLog into development

    commit 769ea6ad710890e931aabace2c00dc37436f869f
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 21:36:33 2025 -0400

        Implement code changes to enhance functionality and improve performance

    commit af4e541c1c9e7309857102287199279aec339387
    Author: fantastron27 <fantastron27@gmail.com>
    Date:   Sun Sep 7 03:36:23 2025 +0200

        Added Slovak translations (#815)

        * Created sk.json

        * Update Navbar.svelte

        * Update +layout.svelte

        ---------

        Co-authored-by: Sean Morley <98704938+seanmorley15@users.noreply.github.com>

    commit 904474d757577229b47441d1378a6fd6788fbe40
    Merge: d4709434 f87a5fe3
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 21:31:58 2025 -0400

        Merge remote-tracking branch 'weblate/development' into development

    commit d47094346c0b63ea753294a0786414e5e070ae7f
    Merge: 4a5f59bf 6366a3eb
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 21:29:39 2025 -0400

        Merge remote-tracking branch 'weblate/development' into development

    commit f87a5fe3bcc2fe28cfc206fb5cba517bbffa8df6
    Author: Sergio <garcia.sergio@me.com>
    Date:   Sun Sep 7 01:12:50 2025 +0200

        Translated using Weblate (Spanish)

        Currently translated at 100.0% (956 of 956 strings)

        Translation: AdventureLog/Web App
        Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/es/

    commit 4a5f59bfd24e32fdf3558b009a8f636636cb3663
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 21:06:17 2025 -0400

        Fixes #654

    commit c1302bb54ab272c2a98c53ce0d508b7d39e9674b
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 20:52:05 2025 -0400

        [BUG] Single day Collections will think location visits are out of date range
        Fixes #827

    commit 773f2d65bbfb2a9591b31fabfd6844612b840f1a
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 19:52:28 2025 -0400

        [BUG] Server Error (500) when trying to access the API docs
        Fixes #712

    commit 4228db249ed5e3261931a1cdb3895d0ddd3ac4ac
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 19:44:00 2025 -0400

        [BUG]Ordered Itinerary includes visits that are outside itinerary date range
        Fixes #746

    commit 26f36cabb0a860f10d7ba62b5279ddd1e282c78e
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 18:36:50 2025 +0200

        Translated using Weblate (Spanish)

        Currently translated at 100.0% (956 of 956 strings)

        Translation: AdventureLog/Web App
        Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/es/

    commit 3bfd2dd5615afdbd04e3451c2ef728f1d7caf466
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 12:33:23 2025 -0400

        Remove empty English (United States) locale file

    commit 6366a3eba6ab72090e52be474212a663799dfe19
    Author: Nikolai Eidsheim <nikolai.eidsheim@gmail.com>
    Date:   Sat Sep 6 18:10:15 2025 +0200

        Translated using Weblate (Norwegian Bokmål)

        Currently translated at 100.0% (956 of 956 strings)

        Translation: AdventureLog/Web App
        Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/nb_NO/

    commit 671cd3701fc5a601f2f1bad9aef93106f91eec0b
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 16:58:04 2025 +0200

        Added translation using Weblate (English (United States))

    commit bdbbe5f4978f041f620f0503da69fa870cb1997c
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 16:54:43 2025 +0200

        Translated using Weblate (Spanish)

        Currently translated at 100.0% (956 of 956 strings)

        Translation: AdventureLog/Web App
        Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/es/

commit 9d4f1b8f534a3cdfb22812f2a25ababd7a236a87
Author: Jacob <jacob.aulin@proton.me>
Date:   Sat Sep 13 15:17:22 2025 +0200

    Translated using Weblate (Swedish)

    Currently translated at 99.8% (957 of 958 strings)

    Translation: AdventureLog/Web App
    Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/sv/

commit 8fac40cfde425c989521c891b3ba9c75ab32e57e
Author: Christian S <schuld.christian@gmail.com>
Date:   Sat Sep 13 12:54:52 2025 +0200

    Translated using Weblate (German)

    Currently translated at 100.0% (958 of 958 strings)

    Translation: AdventureLog/Web App
    Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/de/

commit 8e8c42396ec77b763983155e8b1e89cabf38ce17
Author: Patricio Carrau <duckycb@proton.me>
Date:   Tue Sep 9 21:59:48 2025 +0200

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (958 of 958 strings)

    Translation: AdventureLog/Web App
    Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/es/

commit be818ab408d00c5c26dfb3b25632604a415d3570
Author: pplulee <hi@pplulee.me>
Date:   Mon Sep 8 04:06:54 2025 +0100

    fix(i18n): update Chinese translations for location-related terms (#829)

    Co-authored-by: Sean Morley <98704938+seanmorley15@users.noreply.github.com>

commit 9e40dcf6a1dc194d4694a114b3c7e88135121016
Merge: af2f2809 733eefce
Author: Sean Morley <mail@seanmorley.com>
Date:   Sun Sep 7 23:03:57 2025 -0400

    Merge remote-tracking branch 'weblate/development' into development

commit af2f28090b9242fb7ab263fa5bbb95a5bcc1b27f
Author: Sean Morley <mail@seanmorley.com>
Date:   Sun Sep 7 23:00:33 2025 -0400

    [BUG] Location Visit End Date not affected by Location Timezone
    Fixes #843

commit 733eefceddbdad01726364e5d4523605f095fde2
Author: Alex <div@alexe.at>
Date:   Sun Sep 7 23:28:20 2025 +0200

    Translated using Weblate (German)

    Currently translated at 100.0% (958 of 958 strings)

    Translation: AdventureLog/Web App
    Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/de/

commit 6c750d1c8f95b42418893e15ad46c3d4ed86d053
Author: fantastron27 <fantastron27@gmail.com>
Date:   Sun Sep 7 09:17:16 2025 +0200

    Translated using Weblate (Slovak)

    Currently translated at 100.0% (958 of 958 strings)

    Translation: AdventureLog/Web App
    Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/sk/

commit f733b3b96bbddc71d426f2e60320a5ad2f6755af
Merge: 769ea6ad af4e541c
Author: Sean Morley <mail@seanmorley.com>
Date:   Sat Sep 6 21:36:35 2025 -0400

    Merge branch 'development' of github.com:seanmorley15/AdventureLog into development

commit 769ea6ad710890e931aabace2c00dc37436f869f
Author: Sean Morley <mail@seanmorley.com>
Date:   Sat Sep 6 21:36:33 2025 -0400

    Implement code changes to enhance functionality and improve performance

commit af4e541c1c9e7309857102287199279aec339387
Author: fantastron27 <fantastron27@gmail.com>
Date:   Sun Sep 7 03:36:23 2025 +0200

    Added Slovak translations (#815)

    * Created sk.json

    * Update Navbar.svelte

    * Update +layout.svelte

    ---------

    Co-authored-by: Sean Morley <98704938+seanmorley15@users.noreply.github.com>

commit 904474d757577229b47441d1378a6fd6788fbe40
Merge: d4709434 f87a5fe3
Author: Sean Morley <mail@seanmorley.com>
Date:   Sat Sep 6 21:31:58 2025 -0400

    Merge remote-tracking branch 'weblate/development' into development

commit d47094346c0b63ea753294a0786414e5e070ae7f
Merge: 4a5f59bf 6366a3eb
Author: Sean Morley <mail@seanmorley.com>
Date:   Sat Sep 6 21:29:39 2025 -0400

    Merge remote-tracking branch 'weblate/development' into development

commit f87a5fe3bcc2fe28cfc206fb5cba517bbffa8df6
Author: Sergio <garcia.sergio@me.com>
Date:   Sun Sep 7 01:12:50 2025 +0200

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (956 of 956 strings)

    Translation: AdventureLog/Web App
    Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/es/

commit 4a5f59bfd24e32fdf3558b009a8f636636cb3663
Author: Sean Morley <mail@seanmorley.com>
Date:   Sat Sep 6 21:06:17 2025 -0400

    Fixes #654

commit c1302bb54ab272c2a98c53ce0d508b7d39e9674b
Author: Sean Morley <mail@seanmorley.com>
Date:   Sat Sep 6 20:52:05 2025 -0400

    [BUG] Single day Collections will think location visits are out of date range
    Fixes #827

commit 773f2d65bbfb2a9591b31fabfd6844612b840f1a
Author: Sean Morley <mail@seanmorley.com>
Date:   Sat Sep 6 19:52:28 2025 -0400

    [BUG] Server Error (500) when trying to access the API docs
    Fixes #712

commit 4228db249ed5e3261931a1cdb3895d0ddd3ac4ac
Author: Sean Morley <mail@seanmorley.com>
Date:   Sat Sep 6 19:44:00 2025 -0400

    [BUG]Ordered Itinerary includes visits that are outside itinerary date range
    Fixes #746

commit 26f36cabb0a860f10d7ba62b5279ddd1e282c78e
Author: Sean Morley <mail@seanmorley.com>
Date:   Sat Sep 6 18:36:50 2025 +0200

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (956 of 956 strings)

    Translation: AdventureLog/Web App
    Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/es/

commit 3bfd2dd5615afdbd04e3451c2ef728f1d7caf466
Author: Sean Morley <mail@seanmorley.com>
Date:   Sat Sep 6 12:33:23 2025 -0400

    Remove empty English (United States) locale file

commit 6366a3eba6ab72090e52be474212a663799dfe19
Author: Nikolai Eidsheim <nikolai.eidsheim@gmail.com>
Date:   Sat Sep 6 18:10:15 2025 +0200

    Translated using Weblate (Norwegian Bokmål)

    Currently translated at 100.0% (956 of 956 strings)

    Translation: AdventureLog/Web App
    Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/nb_NO/

commit 671cd3701fc5a601f2f1bad9aef93106f91eec0b
Author: Sean Morley <mail@seanmorley.com>
Date:   Sat Sep 6 16:58:04 2025 +0200

    Added translation using Weblate (English (United States))

commit bdbbe5f4978f041f620f0503da69fa870cb1997c
Author: Sean Morley <mail@seanmorley.com>
Date:   Sat Sep 6 16:54:43 2025 +0200

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (956 of 956 strings)

    Translation: AdventureLog/Web App
    Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/es/

* Translated using Weblate (French)

Currently translated at 100.0% (958 of 958 strings)

Translation: AdventureLog/Web App
Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/fr/

* Translated using Weblate (Turkish)

Currently translated at 100.0% (958 of 958 strings)

Translation: AdventureLog/Web App
Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/tr/

* feat: enhance serializers and views for nested representations and lightweight data fetching

* feat: implement caching for visits by country and region endpoints

* feat: update LocationSerializer to include debug logging and adjust nested context handling in CollectionSerializer

* feat: enhance CollectionSerializer and CollectionViewSet to support nested context handling

* feat(worldtravel): add globe spin feature with loading and result display

- Introduced a toggle button for the globe spin feature.
- Implemented fetch logic for globe spin data from the API.
- Added loading state with a spinning globe animation.
- Displayed country information, including flag, subregion, and visit statistics.
- Enhanced UI with animations for loading and displaying results.
- Included error handling for API fetch failures.

* Add Turkish language support to the application

* feat(map): enhance pin styling with gradient backgrounds and improved hover effects

* Squashed commit of the following:

commit 990b0645059421c4a293fbf64830a6d864ceb40e
Author: Henrique Fonseca Veloso <henriquefv@tutamail.com>
Date:   Sun Sep 21 00:27:59 2025 +0200

    Translated using Weblate (Portuguese (Brazil))

    Currently translated at 100.0% (958 of 958 strings)

    Translation: AdventureLog/Web App
    Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/pt_BR/

commit 03a4b9235faa849fb817348a2774e834a6851dc3
Author: Orhun <orhunavcu@gmail.com>
Date:   Fri Sep 19 19:38:12 2025 +0200

    Translated using Weblate (Turkish)

    Currently translated at 100.0% (958 of 958 strings)

    Translation: AdventureLog/Web App
    Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/tr/

commit 1ccdc678627eb5915b56a8bfb3465928c80a524f
Author: Henrique Fonseca Veloso <henriquefv@tutamail.com>
Date:   Sat Sep 20 20:01:36 2025 +0200

    Translated using Weblate (Portuguese (Brazil))

    Currently translated at 97.0% (930 of 958 strings)

    Translation: AdventureLog/Web App
    Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/pt_BR/

* fix(config): update appVersion to v0.11.0-main-09212025

* fix duration calculation on ordered collection view (#867)

Co-authored-by: Sean Morley <98704938+seanmorley15@users.noreply.github.com>

* Update frontend/src/routes/worldtravel/+page.svelte

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update backend/server/worldtravel/views.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update frontend/src/routes/map/+page.svelte

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Rohan <Alchez@users.noreply.github.com>
Co-authored-by: Orhun <orhunavcu@gmail.com>
Co-authored-by: vorbeiei <vorbeiei@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-21 22:06:24 -04:00

448 lines
20 KiB
Python

from django.db.models import Q
from django.db.models.functions import Lower
from django.db import transaction
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
from adventures.models import Collection, Location, Transportation, Note, Checklist, CollectionInvite
from adventures.permissions import CollectionShared
from adventures.serializers import CollectionSerializer, CollectionInviteSerializer
from users.models import CustomUser as User
from adventures.utils import pagination
from users.serializers import CustomUserDetailsSerializer as UserSerializer
class CollectionViewSet(viewsets.ModelViewSet):
serializer_class = CollectionSerializer
permission_classes = [CollectionShared]
pagination_class = pagination.StandardResultsSetPagination
def apply_sorting(self, queryset):
order_by = self.request.query_params.get('order_by', 'name')
order_direction = self.request.query_params.get('order_direction', 'asc')
valid_order_by = ['name', 'updated_at', 'start_date']
if order_by not in valid_order_by:
order_by = 'updated_at'
if order_direction not in ['asc', 'desc']:
order_direction = 'asc'
# Apply case-insensitive sorting for the 'name' field
if order_by == 'name':
queryset = queryset.annotate(lower_name=Lower('name'))
ordering = 'lower_name'
if order_direction == 'desc':
ordering = f'-{ordering}'
elif order_by == 'start_date':
ordering = 'start_date'
if order_direction == 'asc':
ordering = 'start_date'
else:
ordering = '-start_date'
else:
order_by == 'updated_at'
ordering = 'updated_at'
if order_direction == 'asc':
ordering = '-updated_at'
return queryset.order_by(ordering)
def get_serializer_context(self):
"""Override to add nested and exclusion contexts based on query parameters"""
context = super().get_serializer_context()
# Handle nested parameter
is_nested = self.request.query_params.get('nested', 'false').lower() == 'true'
if is_nested:
context['nested'] = True
# Handle individual exclusion parameters (if using granular approach)
exclude_params = [
'exclude_transportations',
'exclude_notes',
'exclude_checklists',
'exclude_lodging'
]
for param in exclude_params:
if self.request.query_params.get(param, 'false').lower() == 'true':
context[param] = True
return context
def list(self, request):
# make sure the user is authenticated
if not request.user.is_authenticated:
return Response({"error": "User is not authenticated"}, status=400)
queryset = Collection.objects.filter(user=request.user, is_archived=False)
queryset = self.apply_sorting(queryset)
return self.paginate_and_respond(queryset, request)
@action(detail=False, methods=['get'])
def all(self, request):
if not request.user.is_authenticated:
return Response({"error": "User is not authenticated"}, status=400)
queryset = Collection.objects.filter(
Q(user=request.user)
)
queryset = self.apply_sorting(queryset)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
@action(detail=False, methods=['get'])
def archived(self, request):
if not request.user.is_authenticated:
return Response({"error": "User is not authenticated"}, status=400)
queryset = Collection.objects.filter(
Q(user=request.user.id) & Q(is_archived=True)
)
queryset = self.apply_sorting(queryset)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
# this make the is_public field of the collection cascade to the locations
@transaction.atomic
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
if 'collection' in serializer.validated_data:
new_collection = serializer.validated_data['collection']
# if the new collection is different from the old one and the user making the request is not the owner of the new collection return an error
if new_collection != instance.collection and new_collection.user != request.user:
return Response({"error": "User does not own the new collection"}, status=400)
# Check if the 'is_public' field is present in the update data
if 'is_public' in serializer.validated_data:
new_public_status = serializer.validated_data['is_public']
# if is_public has changed and the user is not the owner of the collection return an error
if new_public_status != instance.is_public and instance.user != request.user:
print(f"User {request.user.id} does not own the collection {instance.id} that is owned by {instance.user}")
return Response({"error": "User does not own the collection"}, status=400)
# Get all locations in this collection
locations_in_collection = Location.objects.filter(collections=instance)
if new_public_status:
# If collection becomes public, make all locations public
locations_in_collection.update(is_public=True)
else:
# If collection becomes private, check each location
# Only set a location to private if ALL of its collections are private
# Collect locations that do NOT belong to any other public collection (excluding the current one)
location_ids_to_set_private = []
for location in locations_in_collection:
has_public_collection = location.collections.filter(is_public=True).exclude(id=instance.id).exists()
if not has_public_collection:
location_ids_to_set_private.append(location.id)
# Bulk update those locations
Location.objects.filter(id__in=location_ids_to_set_private).update(is_public=False)
# Update transportations, notes, and checklists related to this collection
# These still use direct ForeignKey relationships
Transportation.objects.filter(collection=instance).update(is_public=new_public_status)
Note.objects.filter(collection=instance).update(is_public=new_public_status)
Checklist.objects.filter(collection=instance).update(is_public=new_public_status)
# Log the action (optional)
action = "public" if new_public_status else "private"
print(f"Collection {instance.id} and its related objects were set to {action}")
self.perform_update(serializer)
if getattr(instance, '_prefetched_objects_cache', None):
# If 'prefetch_related' has been applied to a queryset, we need to
# forcibly invalidate the prefetch cache on the instance.
instance._prefetched_objects_cache = {}
return Response(serializer.data)
# make an action to retreive all locations that are shared with the user
@action(detail=False, methods=['get'])
def shared(self, request):
if not request.user.is_authenticated:
return Response({"error": "User is not authenticated"}, status=400)
queryset = Collection.objects.filter(
shared_with=request.user
)
queryset = self.apply_sorting(queryset)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
# Created a custom action to share a collection with another user by their UUID
# This action will create a CollectionInvite instead of directly sharing the collection
@action(detail=True, methods=['post'], url_path='share/(?P<uuid>[^/.]+)')
def share(self, request, pk=None, uuid=None):
collection = self.get_object()
if not uuid:
return Response({"error": "User UUID is required"}, status=400)
try:
user = User.objects.get(uuid=uuid, public_profile=True)
except User.DoesNotExist:
return Response({"error": "User not found"}, status=404)
if user == request.user:
return Response({"error": "Cannot share with yourself"}, status=400)
# Check if user is already shared with the collection
if collection.shared_with.filter(id=user.id).exists():
return Response({"error": "Collection is already shared with this user"}, status=400)
# Check if there's already a pending invite for this user
if CollectionInvite.objects.filter(collection=collection, invited_user=user).exists():
return Response({"error": "Invite already sent to this user"}, status=400)
# Create the invite instead of directly sharing
invite = CollectionInvite.objects.create(
collection=collection,
invited_user=user
)
return Response({"success": f"Invite sent to {user.username}"})
# Custom action to list all invites for a user
@action(detail=False, methods=['get'], url_path='invites')
def invites(self, request):
if not request.user.is_authenticated:
return Response({"error": "User is not authenticated"}, status=400)
invites = CollectionInvite.objects.filter(invited_user=request.user)
serializer = CollectionInviteSerializer(invites, many=True)
return Response(serializer.data)
# Add these methods to your CollectionViewSet class
@action(detail=True, methods=['post'], url_path='revoke-invite/(?P<uuid>[^/.]+)')
def revoke_invite(self, request, pk=None, uuid=None):
"""Revoke a pending invite for a collection"""
if not request.user.is_authenticated:
return Response({"error": "User is not authenticated"}, status=400)
collection = self.get_object()
if not uuid:
return Response({"error": "User UUID is required"}, status=400)
try:
user = User.objects.get(uuid=uuid, public_profile=True)
except User.DoesNotExist:
return Response({"error": "User not found"}, status=404)
# Only collection owner can revoke invites
if collection.user != request.user:
return Response({"error": "Only collection owner can revoke invites"}, status=403)
try:
invite = CollectionInvite.objects.get(collection=collection, invited_user=user)
invite.delete()
return Response({"success": f"Invite revoked for {user.username}"})
except CollectionInvite.DoesNotExist:
return Response({"error": "No pending invite found for this user"}, status=404)
@action(detail=True, methods=['post'], url_path='accept-invite')
def accept_invite(self, request, pk=None):
"""Accept a collection invite"""
if not request.user.is_authenticated:
return Response({"error": "User is not authenticated"}, status=400)
collection = self.get_object()
try:
invite = CollectionInvite.objects.get(collection=collection, invited_user=request.user)
except CollectionInvite.DoesNotExist:
return Response({"error": "No pending invite found for this collection"}, status=404)
# Add user to collection's shared_with
collection.shared_with.add(request.user)
# Delete the invite
invite.delete()
return Response({"success": f"Successfully joined collection: {collection.name}"})
@action(detail=True, methods=['post'], url_path='decline-invite')
def decline_invite(self, request, pk=None):
"""Decline a collection invite"""
if not request.user.is_authenticated:
return Response({"error": "User is not authenticated"}, status=400)
collection = self.get_object()
try:
invite = CollectionInvite.objects.get(collection=collection, invited_user=request.user)
invite.delete()
return Response({"success": f"Declined invite for collection: {collection.name}"})
except CollectionInvite.DoesNotExist:
return Response({"error": "No pending invite found for this collection"}, status=404)
# Action to list all users a collection **can** be shared with, excluding those already shared with and those with pending invites
@action(detail=True, methods=['get'], url_path='can-share')
def can_share(self, request, pk=None):
if not request.user.is_authenticated:
return Response({"error": "User is not authenticated"}, status=400)
collection = self.get_object()
# Get users with pending invites and users already shared with
users_with_pending_invites = set(str(uuid) for uuid in CollectionInvite.objects.filter(collection=collection).values_list('invited_user__uuid', flat=True))
users_already_shared = set(str(uuid) for uuid in collection.shared_with.values_list('uuid', flat=True))
# Get all users with public profiles excluding only the owner
all_users = User.objects.filter(public_profile=True).exclude(id=request.user.id)
# Return fully serialized user data with status
serializer = UserSerializer(all_users, many=True)
result_data = []
for user_data in serializer.data:
user_data.pop('has_password', None)
user_data.pop('disable_password', None)
# Add status field
if user_data['uuid'] in users_with_pending_invites:
user_data['status'] = 'pending'
elif user_data['uuid'] in users_already_shared:
user_data['status'] = 'shared'
else:
user_data['status'] = 'available'
result_data.append(user_data)
return Response(result_data)
@action(detail=True, methods=['post'], url_path='unshare/(?P<uuid>[^/.]+)')
def unshare(self, request, pk=None, uuid=None):
if not request.user.is_authenticated:
return Response({"error": "User is not authenticated"}, status=400)
collection = self.get_object()
if not uuid:
return Response({"error": "User UUID is required"}, status=400)
try:
user = User.objects.get(uuid=uuid, public_profile=True)
except User.DoesNotExist:
return Response({"error": "User not found"}, status=404)
if user == request.user:
return Response({"error": "Cannot unshare with yourself"}, status=400)
if not collection.shared_with.filter(id=user.id).exists():
return Response({"error": "Collection is not shared with this user"}, status=400)
# Remove user from shared_with
collection.shared_with.remove(user)
# Handle locations owned by the unshared user that are in this collection
# These locations should be removed from the collection since they lose access
locations_to_remove = collection.locations.filter(user=user)
removed_count = locations_to_remove.count()
if locations_to_remove.exists():
# Remove these locations from the collection
collection.locations.remove(*locations_to_remove)
collection.save()
success_message = f"Unshared with {user.username}"
if removed_count > 0:
success_message += f" and removed {removed_count} location(s) they owned from the collection"
return Response({"success": success_message})
# Action for a shared user to leave a collection
@action(detail=True, methods=['post'], url_path='leave')
def leave(self, request, pk=None):
if not request.user.is_authenticated:
return Response({"error": "User is not authenticated"}, status=400)
collection = self.get_object()
if request.user == collection.user:
return Response({"error": "Owner cannot leave their own collection"}, status=400)
if not collection.shared_with.filter(id=request.user.id).exists():
return Response({"error": "You are not a member of this collection"}, status=400)
# Remove the user from shared_with
collection.shared_with.remove(request.user)
# Handle locations owned by the user that are in this collection
locations_to_remove = collection.locations.filter(user=request.user)
removed_count = locations_to_remove.count()
if locations_to_remove.exists():
# Remove these locations from the collection
collection.locations.remove(*locations_to_remove)
collection.save()
success_message = f"You have left the collection: {collection.name}"
if removed_count > 0:
success_message += f" and removed {removed_count} location(s) you owned from the collection"
return Response({"success": success_message})
def get_queryset(self):
if self.action == 'destroy':
return Collection.objects.filter(user=self.request.user.id)
if self.action in ['update', 'partial_update']:
return Collection.objects.filter(
Q(user=self.request.user.id) | Q(shared_with=self.request.user)
).distinct()
# Allow access to collections with pending invites for accept/decline actions
if self.action in ['accept_invite', 'decline_invite']:
if not self.request.user.is_authenticated:
return Collection.objects.none()
return Collection.objects.filter(
Q(user=self.request.user.id) |
Q(shared_with=self.request.user) |
Q(invites__invited_user=self.request.user)
).distinct()
if self.action == 'retrieve':
if not self.request.user.is_authenticated:
return Collection.objects.filter(is_public=True)
return Collection.objects.filter(
Q(is_public=True) | Q(user=self.request.user.id) | Q(shared_with=self.request.user)
).distinct()
# For list action, include collections owned by the user or shared with the user, that are not archived
return Collection.objects.filter(
(Q(user=self.request.user.id) | Q(shared_with=self.request.user)) & Q(is_archived=False)
).distinct()
def perform_create(self, serializer):
# This is ok because you cannot share a collection when creating it
serializer.save(user=self.request.user)
def paginate_and_respond(self, queryset, request):
paginator = self.pagination_class()
page = paginator.paginate_queryset(queryset, request)
if page is not None:
serializer = self.get_serializer(page, many=True)
return paginator.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
def get_serializer(self, *args, **kwargs):
# Add nested=True to serializer context for GET list requests
context = self.get_serializer_context()
# If this is a list action, make sure nested=True in context
if self.action == 'list':
context['nested'] = True
kwargs['context'] = context
return super().get_serializer(*args, **kwargs)