33 KiB
Vrobbler Project
- Overview
- Backlog
[1/35]- Configure IMAP folder/start in user profile
- Verify Last.fm timestamp timezone handling
- After transition to linux add curl_cffi as webpage scrapper again
- Create small utility to clean up tracks scrobbled with wonky playback times
- Move to using more robust mopidy-webhooks pacakge form pypi
- Fix exporting so it works reliably
- Implement loguru into project
- Make podcast date format configurable in settings
- Extract zombie scrobble query into custom manager
- Allow profile to set start of week
- Add constants for data dictionary keys (multiple files)
- Add track number and rating to CSV export
- Simplify Track creation API in scrobblers
- Enrich books/authors after creation (recurring pattern)
- Enrich TMDB resultw with TMDB URL
- Enrich bricksets from website on creation
- Make WebDAV paths configurable per-user
- Add auto-population of serial scrobble IDs for bricksets
- Raise exception on WebDAV fetch failure :webdav:error-handling:
- Add AllTrails as a source for Trail data
- Add `garmin_activity_id` to the TrailLogData class
- Make IMAP and WebDAV configurable
- Allow browing a user's favorited media
- Find page numbers for comic books from ComicVine
- Is there way to create unique slugs for media instances
- Add async media enrichment after scrobbling (recurring pattern)
- Auto sync board game scrobbles to BGG
- Scrape ComicBookRoundUp ratings for comic book metadata
- Update how board game scrobbles work
- Remove vestigial "Grid view" from all templates
- Fix links on book page to sources
- Make book matching use Google Books ID or author name
- Fix dead source icon links on book and brick set pages
- Books created via koreader do not get enriched
- There should a global chart aggregator that builds charts across all users
- Make it possible for anonymous users to view media objects
We should convert this PROJECT file to put tickets in a subdirectory, tickets, with each ticket having it's own shortid_title.org
Overview
Vrobbler began humbly enough as a way to use Jellyfin's webhook to keep track of the shows and movies I was watching. More specifically, I broke my ankle a few days after Christmas in 2022 and spent the next four months very slowly recovering after surgical repair. So once I had the webhook working, and scrobbling videos, it was only a matter of time till I expaned it to mopidy to replicate LastFM. Then I added board games, books via KoReader, sports events, podcasts … it just keeps going. Vrobbler is now a sort of Frankenstein's monster of scrobbling an entire life.
I am still unconvinced I can keep this going, but being able to scrobble org tasks, Todoist tasks, web pages I've read and trails I've hiked has turned out to be sometimes cathartic and sometimes functional as I try to remember when I did a thing.
Backlog [1/35] vrobbler project personal
TODO [C] Configure IMAP folder/start in user profile imap settings
Description
Refactor the IMAP import flow as something that can be configured in a tab of the user profile's settings.
Implementation
Mostly I used webdav now, but we should maintain IMAP importing as an option in user settings.
- File:
vrobbler/apps/scrobbles/importers/imap.py(line 30) - Mailbox folder is hardcoded to
"INBOX". Should be configurable in the user's IMAP profile settings, especially for users with complex folder structures.
TODO [C] Verify Last.fm timestamp timezone handling lastfm timestamps
Description
Last.FM timezone handling may not work 100% correctly. And maybe that's okay.
Implementation
- File:
vrobbler/apps/scrobbles/importers/lastfm.py(line 148) - A TODO questions whether the current
utcfromtimestamp()+.replace()pattern is correct. The commented-outdatetime.fromtimestamp(..., UTC)may be the right approach. Needs review.
TODO [C] After transition to linux add curl_cffi as webpage scrapper again webpages metadata
TODO [C] Create small utility to clean up tracks scrobbled with wonky playback times bug music scrobbles
TODO [C] Move to using more robust mopidy-webhooks pacakge form pypi utility improvement
Example payloads from mopidy-webhooks
Podcast playback ended
{
"type": "event",
"event": "track_playback_ended",
"data": {
"tl_track": {
"__model__": "TlTrack",
"tlid": 13,
"track": {
"__model__": "Track",
"uri": "file:///var/lib/mopidy/media/podcasts/The%20Prince/2022-09-28-Wolf-warriors.mp3",
"name": "Wolf warriors",
"artists": [
{
"__model__": "Artist",
"name": "The Economist"
}
],
"album": {
"__model__": "Album",
"name": "The Prince",
"date": "2022"
},
"genre": "Blues",
"date": "2022",
"length": 2437778,
"bitrate": 127988
}
},
"time_position": 3290
}
}
Podcast playback state changes
{
"type": "event",
"event": "playback_state_changed",
"data": {
"old_state": "paused",
"new_state": "playing"
}
}
{
"type": "event",
"event": "playback_state_changed",
"data": {
"old_state": "stopped",
"new_state": "playing"
}
}
Podcast playback started
{
"type": "event",
"event": "track_playback_started",
"data": {
"tl_track": {
"__model__": "TlTrack",
"tlid": 13,
"track": {
"__model__": "Track",
"uri": "file:///var/lib/mopidy/media/podcasts/The%20Prince/2022-09-28-Wolf-warriors.mp3",
"name": "Wolf warriors",
"artists": [
{
"__model__": "Artist",
"name": "The Economist"
}
],
"album": {
"__model__": "Album",
"name": "The Prince",
"date": "2022"
},
"genre": "Blues",
"date": "2022",
"length": 2437778,
"bitrate": 127988
}
}
}
}
Podcast playback paused
{
"type": "status",
"data": {
"state": "paused",
"current_track": {
"__model__": "Track",
"uri": "file:///var/lib/mopidy/media/podcasts/The%20Prince/2022-09-28-Wolf-warriors.mp3",
"name": "Wolf warriors",
"artists": [
{
"__model__": "Artist",
"name": "The Economist"
}
],
"album": {
"__model__": "Album",
"name": "The Prince",
"date": "2022"
},
"genre": "Blues",
"date": "2022",
"length": 2437778,
"bitrate": 127988
},
"time_position": 2350
}
}
Track playback started
{
"type": "event",
"event": "track_playback_started",
"data": {
"tl_track": {
"__model__": "TlTrack",
"tlid": 14,
"track": {
"__model__": "Track",
"uri": "local:track:Various%20Artists%20-%202008%20-%20Twilight%20OST/01-muse-supermassive_black_hole.mp3",
"name": "Supermassive Black Hole",
"artists": [
{
"__model__": "Artist",
"uri": "local:artist:md5:250dd6551b66a58a6b4897aa697f200c",
"name": "Muse",
"musicbrainz_id": "9c9f1380-2516-4fc9-a3e6-f9f61941d090"
}
],
"album": {
"__model__": "Album",
"uri": "local:album:md5:455343d54cdd89cb5a3b5ad537ea99d0",
"name": "Twilight: Original Motion Picture Soundtrack",
"artists": [
{
"__model__": "Artist",
"uri": "local:artist:md5:54e4db2d5624f80b0cc290346e696756",
"name": "Various Artists",
"musicbrainz_id": "89ad4ac3-39f7-470e-963a-56509c546377"
}
],
"num_tracks": 12,
"num_discs": 1,
"date": "2008-11-04",
"musicbrainz_id": "b4889eaf-d9f4-434c-a68d-69227b12b6a4"
},
"composers": [
{
"__model__": "Artist",
"uri": "local:artist:md5:4d49cbca0b347e0a89047bb019d2779d",
"name": "Matt Bellamy"
}
],
"genre": "Rock",
"track_no": 1,
"disc_no": 1,
"date": "2008-11-04",
"length": 211121,
"musicbrainz_id": "ff1e3e1a-f6e8-4692-b426-355880383bb6",
"last_modified": 1672712949510
}
}
}
}
Track playback in progress
{
"type": "status",
"data": {
"state": "playing",
"current_track": {
"__model__": "Track",
"uri": "local:track:Various%20Artists%20-%202008%20-%20Twilight%20OST/01-muse-supermassive_black_hole.mp3",
"name": "Supermassive Black Hole",
"artists": [
{
"__model__": "Artist",
"uri": "local:artist:md5:250dd6551b66a58a6b4897aa697f200c",
"name": "Muse",
"musicbrainz_id": "9c9f1380-2516-4fc9-a3e6-f9f61941d090"
}
],
"album": {
"__model__": "Album",
"uri": "local:album:md5:455343d54cdd89cb5a3b5ad537ea99d0",
"name": "Twilight: Original Motion Picture Soundtrack",
"artists": [
{
"__model__": "Artist",
"uri": "local:artist:md5:54e4db2d5624f80b0cc290346e696756",
"name": "Various Artists",
"musicbrainz_id": "89ad4ac3-39f7-470e-963a-56509c546377"
}
],
"num_tracks": 12,
"num_discs": 1,
"date": "2008-11-04",
"musicbrainz_id": "b4889eaf-d9f4-434c-a68d-69227b12b6a4"
},
"composers": [
{
"__model__": "Artist",
"uri": "local:artist:md5:4d49cbca0b347e0a89047bb019d2779d",
"name": "Matt Bellamy"
}
],
"genre": "Rock",
"track_no": 1,
"disc_no": 1,
"date": "2008-11-04",
"length": 211121,
"musicbrainz_id": "ff1e3e1a-f6e8-4692-b426-355880383bb6",
"last_modified": 1672712949510
},
"time_position": 17031
}
}
Track event playback paused
{
"type": "event",
"event": "track_playback_paused",
"data": {
"tl_track": {
"__model__": "TlTrack",
"tlid": 14,
"track": {
"__model__": "Track",
"uri": "local:track:Various%20Artists%20-%202008%20-%20Twilight%20OST/01-muse-supermassive_black_hole.mp3",
"name": "Supermassive Black Hole",
"artists": [
{
"__model__": "Artist",
"uri": "local:artist:md5:250dd6551b66a58a6b4897aa697f200c",
"name": "Muse",
"musicbrainz_id": "9c9f1380-2516-4fc9-a3e6-f9f61941d090"
}
],
"album": {
"__model__": "Album",
"uri": "local:album:md5:455343d54cdd89cb5a3b5ad537ea99d0",
"name": "Twilight: Original Motion Picture Soundtrack",
"artists": [
{
"__model__": "Artist",
"uri": "local:artist:md5:54e4db2d5624f80b0cc290346e696756",
"name": "Various Artists",
"musicbrainz_id": "89ad4ac3-39f7-470e-963a-56509c546377"
}
],
"num_tracks": 12,
"num_discs": 1,
"date": "2008-11-04",
"musicbrainz_id": "b4889eaf-d9f4-434c-a68d-69227b12b6a4"
},
"composers": [
{
"__model__": "Artist",
"uri": "local:artist:md5:4d49cbca0b347e0a89047bb019d2779d",
"name": "Matt Bellamy"
}
],
"genre": "Rock",
"track_no": 1,
"disc_no": 1,
"date": "2008-11-04",
"length": 211121,
"musicbrainz_id": "ff1e3e1a-f6e8-4692-b426-355880383bb6",
"last_modified": 1672712949510
}
},
"time_position": 67578
}
}
TODO [C] Fix exporting so it works reliably exporting feature
Description
The existing export function is very naive. It runs in the web process, takes too long and just dumps tracks. We should make it more robust by creating one CSV file per scrobble media type and writing them into a zip file that gets placed in the media directory:
`/media/exports/user_<user_id>/<timestamp>-export.zip`
And this should all be done in a celery task that is just kicked off by the "Export" button on the frontend
TODO [C] Implement loguru into project feature loguru logging
Description
Would be great to formalize how we log so we can search for errors and such more easily. And our exposure to PII is really low at this point in the project, so we can probably use backtrace=True and diagnose=True to help us root cause bugs faster.
TODO [C] Make podcast date format configurable in settings podcasts configuration
Description
PODCAST_DATE_FORMAT is hardcoded to "YYYY-MM-DD". Should be in Django settings or environment variables for deploy-specific configuration.
File: vrobbler/apps/podcasts/utils.py (line 13)
TODO [C] Extract zombie scrobble query into custom manager refactoring manager
Description
The zombie scrobble cleanup query lives in a utility function. Should be a
custom model manager method (e.g. Scrobble.objects.zombies()).
File: vrobbler/apps/scrobbles/utils.py (line 204)
TODO [C] Allow profile to set start of week profiles configuration
Description
start_of_week() and end_of_week() use Monday as default. Should be a user
profile setting for different cultural week start conventions.
File: vrobbler/apps/profiles/utils.py (lines 39, 44)
TODO [C] Add constants for data dictionary keys (multiple files) refactoring constants
Description
Multiple files use magic string literals for dict keys. Should be extracted to named constants for maintainability.
-
Files:
vrobbler/apps/locations/models.py(line 63) –"lat","lon"etc.vrobbler/apps/webpages/models.py(line 290) –"url"vrobbler/apps/scrobbles/importers/tsv.py(line 55) –"S"completion status
TODO [C] Add track number and rating to CSV export export metadata
Description
Track number and rating are missing in the CSV export of scrobbles.
Implementation
Also need to generally clean up how exporting works.
- File:
vrobbler/apps/scrobbles/export.py(lines 51-52) - Track number is hardcoded to
0and rating is hardcoded to"S". The data model may need extension to support these fields.
TODO [C] Simplify Track creation API in scrobblers refactoring api
- File:
vrobbler/apps/scrobbles/scrobblers.py(line 79) Track.find_or_create()requires individual kwargs extracted frompost_data. Should accept a dict or have a cleaner unified interface.
TODO [C] Enrich books/authors after creation (recurring pattern) books enrichment
Description
Generally move enrichment of media to follow-up tasks, so long as we have media length and title from the input source.
Implementation
-
Files:
vrobbler/apps/books/models.py(lines 239, 320, 548)vrobbler/apps/books/koreader.py(lines 79, 119)
- Multiple places where Authors and Books are created without follow-up enrichment. Should fire async enrichment tasks (Celery) after creation.
TODO [C] Enrich TMDB resultw with TMDB URL videos enrichment
- File:
vrobbler/apps/videos/sources/tmdb.py(lines 46, 56) - Cover URLs are constructed but the actual TMDB page URL for the movie/show is not stored. Minor enrichment.
TODO [C] Enrich bricksets from website on creation bricksets enrichment
Implementation
- File:
vrobbler/apps/bricksets/models.py(line 75) BrickSet.find_or_create()only stores the set number. Should fetch additional data (name, piece count, image) from brickset.com or similar.
TODO [B] Make WebDAV paths configurable per-user webdav settings
Description
This may be a no-op now. A lot of webdav code has changed.
Implementation
- File:
vrobbler/apps/scrobbles/importers/webdav.py(lines 125, 410) - Both the GPX path and scale path are hardcoded via
DEFAULT_GPX_PATHandDEFAULT_SCALE_PATH. Should be configurable in user profile settings.
TODO [B] Add auto-population of serial scrobble IDs for bricksets bricksets serials
Implementation
When scrobbling bricksets (LEGO), should check for past scrobbles and auto-populate a serial scrobble ID for tracking multiple builds of the same set.
File: vrobbler/apps/scrobbles/scrobblers.py (line 1245)
TODO [B] Raise exception on WebDAV fetch failure :webdav:error-handling:
Implementation
- File:
vrobbler/apps/books/koreader.py(line 418) fetch_file_from_webdav()returns an empty string on failure, which could silently propagate. Should raise an exception for proper error handling.
TODO [B] Add AllTrails as a source for Trail data trails feature
Description
Pretty clear, I would love to make trails more useful. Historically I wasn't hiking a lot, which made the source for this a bit silly. But it's clear that AllTrails is the best source, though having TrailForks is nice to.
TODO [B] Add `garmin_activity_id` to the TrailLogData class trails feature
Description
Would be nice to have some loose connection to the actual event in my Garmin profile.
TODO [B] Make IMAP and WebDAV configurable webdav feature imap importers
Description
Currently we have webdav able to import post types of file-based incoming data, usually in the form of CSVs but also gpx files, bgstats json files, and audioscrobbler TSV files.
What if the user could specify via their profile (settings) which imports they wanted to use IMAP for and which ones they wanted to use WebDAV for.
Then we'd have two celery tasks that would be kicked off periodically via celerybeat, one for IMAP imports every 12 minutes and one for WebDAV every 3 minutes. Both would be responsible for checking if a user has an configured imports of their type, check if an import needs to run, and dispatch the needed import celery task. This is how the WebDAV celery task currently works.
This would also be an opporunity to clean up the code around WebDAV imports and make them more re-usable for other import services.
TODO [B] Allow browing a user's favorited media favorites feature
Description
We should have a global view `/favorites/` that shows the logged in users's favorited media objects.
TODO [B] Find page numbers for comic books from ComicVine feature books
TODO [B] Is there way to create unique slugs for media instances media_types
TODO [B] Add async media enrichment after scrobbling (recurring pattern) async enrichment
- Files:
vrobbler/apps/scrobbles/scrobblers.py(lines 1179, 1211, 1244) - Three separate TODOs for beer, puzzle, and brickset scrobbles all request firing an async process to enrich media after creation. This is the same pattern repeated across media types – should be solved generically.
TODO [B] Auto sync board game scrobbles to BGG boardgames
Description
TODO [B] Scrape ComicBookRoundUp ratings for comic book metadata books feature comicbook
Description
TODO [B] Update how board game scrobbles work boardgames
Description
When we scrobble a board game from a BGG URL, instead of going to the media detail page, we should go to the scrobble detail page, with the Edit Log form expanded by default.
The Edit log form should have from top to bottom:
- Board/Variant (one or many BoardGameVariant in a multi-select widget)
- People (which should be similar to the Bird widget on BirdLocation and allow setting per user score, win true/false, rank, new true/false, seat_ordrer)
- Expansion ids (which should a multi-select widget of expansions for this game)
- Location (which should be a drop down of BoardGameLocations for this user)
TODO [B] Remove vestigial "Grid view" from all templates templates ui
TODO [B] Fix links on book page to sources ui templates bug
Description
Currently the images on the book source page go nowhere. We should add links to Amazon and Goodreads, and maybe Bookshop.org if we can find a way to generate that link easily.
DONE [B] Make book matching use Google Books ID or author name bug books matching
Description
This may be a no-op situation as we've moved away from Google Books. But maybe we haven't.
Turns out we hadn't. Google Books is still a primary metadata source alongside
OpenLibrary and ComicVine. Book.find_or_create() only matched by
original_title, which is fragile. It now also matches by Google Books ID or
author name (like Track does with musicbrainz_id).
Implementation
vrobbler/apps/books/sources/google.py:lookup_book_from_google()now accepts an optionalauthor(used as aninauthor:query term) and returns the top-level Google Books volumeidasgoogle_books_id.-
vrobbler/apps/books/models.py: added agoogle_books_idCharField toBook.find_or_create()now:- short-circuits on an exact
original_titlematch (no lookups); - after enrichment, reuses an existing book by
google_books_id, falling back to atitle+ author match — mirroring how Track keys offmusicbrainz_id. fix_metadata()gained a Google Books fallback so asynchronously enriched (e.g. KoReader-imported) books also capturegoogle_books_id.
- short-circuits on an exact
vrobbler/apps/books/management/commands/cleanup_book_metadata.py: passes the author to the Google lookup and persistsgoogle_books_idon backfill.- Migration
0040_book_google_books_idadds the field.
DONE [C] Fix dead source icon links on book and brick set pages books templates
Description
The book detail page rendered OpenLibrary and Amazon source icons via
object.openlibrary_link and object.amazon_link, but neither property
existed on Book (the model has no Amazon ASIN field at all), so both icons
were dead links around broken images. The brick set detail page was a copy of
the book template and had the same broken icons for a type with no
OpenLibrary/Amazon presence.
Implementation
vrobbler/apps/books/models.py: addedBook.openlibrary_link(buildshttps://openlibrary.org/works/{olid}or/books/{olid}for edition IDs) andBook.bookshop_link(uses the ISBN, in/a/{affiliate_id}/{isbn}affiliate form whenBOOKSHOP_AFFILIATE_IDis set, otherwise a/books?keywords={isbn}search link).vrobbler/settings.py: addedBOOKSHOP_AFFILIATE_IDread fromVROBBLER_BOOKSHOP_AFFILIATE_ID.vrobbler/templates/books/book_detail.html: source icons now render conditionally (only when a link exists, like the music pages), link to OpenLibrary and Bookshop, and the dead Amazon icon is gone.vrobbler/templates/bricksets/brickset_detail.html: removed the dead OpenLibrary/Amazon source icon row entirely.- Added official OpenLibrary and Bookshop logos at
vrobbler/apps/scrobbles/static/images/(openlibrary-logo.png,bookshop-logo.png). - Tests in ~tests/books_tests/test_book_source_links.py= cover both link properties (work vs edition OLIDs, ISBN-10/13, with/without affiliate ID).
vrobbler/apps/books/management/commands/cleanup_book_metadata.py: added a--missing-source_idfilter flag for books lacking any ofopenlibrary_id/google_books_id/comicvine_id.vrobbler/apps/books/models.py: addedBook.is_comicso ComicVine is only queried for books that already carry comic source data (comicvine_id,volume_comicvine_id,readcomics_url, issue/volume fields) or whose title contains "Issue "/"Volume ". The blanket ComicVine fallback infind_or_create(),fix_metadata(), andcleanup_book_metadata.pynow respects this, so unrelated prose books no longer get matched to comics.
DONE [B] Books created via koreader do not get enriched books metadata bug
Description
When a new books arrives inside of a KoReader import, the metadata is not enriched.
Implementation
- Added
scrobbles.tasks.enrich_book_metadata(), a background Celery task that callsBook.fix_metadata()and tags the bookbook-enrichedon success orenrichment-failedon failure. create_book_from_row()invrobbler/apps/books/koreader.pynow dispatches that task asynchronously after creating a new book, instead of the commented out synchronousfix_metadata()call.- Fixed
Book.fix_metadata()to not reference the removedlocg_slugfield, and to handleauthors=/=genreskeys returned by the current OpenLibrary source (previously it would crash). - Routed
enrich_book_metadatato thebackgroundworker queue.
DONE [B] There should a global chart aggregator that builds charts across all users charts feature
Description
Currently we build charts for individual users … noting content that has been tops for a given year, month, week or day. We should also build such global charts across all users, but limit it to just the top three items (gold, silver, bronze). There should also be a toggle in profile settings that allows a user to include their scrobbles in global aggregation. If that check box is not selected (and it should off by default) that user is not included in the global charts.
Implementation
- Global chart records are stored as
ChartRecordrows with a NULLuser(the column is already nullable), so they reuse the existing model, upserting, indexing and chart rendering infrastructure. - Added
UserProfile.include_in_global_charts(default off) plus a settings form checkbox; users who do not opt in are excluded from aggregation. Toggling the setting triggersscrobbles.tasks.rebuild_global_charts_for_user()which rebuilds every global chart period the user has scrobbles in, so the change is reflected immediately instead of waiting for the next scheduled chart build. - Refactored
vrobbler/apps/charts/utils.py: extracted the media type config into a module-levelMEDIA_CONFIGand pulled the aggregation and upsert logic into shared_aggregate_media()and_upsert_chart_records()helpers used by bothbuild_charts()and the newbuild_global_charts(). - Added
build_global_charts()plusbuild_global_daily/weekly/monthly/yearly_charts()wrappers. Global charts aggregate scrobbles from opted-in users only and keep just the top three items (gold, silver, bronze) per media type/period. - Wired global chart building into the scheduled Celery tasks
(
vrobbler/apps/scrobbles/tasks.py):create_yesterdays_charts(),rebuild_weekly_charts(),rebuild_monthly_charts()andrebuild_yearly_charts()now also build the matching global charts. - Added
GlobalChartViewat/charts/global/(charts.GlobalChartView) with a newcharts/global_chart_index.htmltemplate showing the medal top three per media type, with the same year/month/week navigation as user charts. - Anonymous users now see the global top-three charts on media detail pages:
scrobbles.views.ChartContextMixinqueriesuser__isnull=Truerecords when the request user is anonymous. - Tests in
vrobbler/apps/charts/tests/test_views.pyand new global chart tests invrobbler/apps/charts/tests/test_utils.py.
DONE [B] Make it possible for anonymous users to view media objects media templates
Description
Currently if you click on a Book link when logged out (like via a shared URL: https://life.lab.unbl.ink/shared/KkViBro/ ) you get a 500 error due to the anonymous user not having some properties of an authenticated user. Anonymous users should be able view media on the site, just without an scrobble data being present.
Error
{"levelname": "ERROR", "name": "django.request", "funcName": "log_response", "lineno": 241, "asctime": "2026-09-01 08:49:52,844", "message": "Internal Server Error: /books/0879dd15-fe34-4ed4-b6b0-f68c924aed23/", "exc_info": "Traceback (most recent call last):\n File \"/usr/local/lib/python3.11/site-packages/django/db/models/fields/__init__.py\", line 2053, in get_prep_value\n return int(value)\n ^^^^^^^^^^\nTypeError: int() argument must be a string, a bytes-like object or a real number, not 'SimpleLazyObject'\n\nThe above exception was the direct cause of the following exception:\n\nTraceback (most recent call last):\n File \"/usr/local/lib/python3.11/site-packages/django/core/handlers/exception.py\", line 55, in inner\n response = get_response(request)\n ^^^^^^^^^^^^^^^^^^^^^\n File \"/usr/local/lib/python3.11/site-packages/django/core/handlers/base.py\", line 197, in _get_response\n response = wrapped_callback(request, *callback_args, **callback_kwargs)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/usr/local/lib/python3.11/site-packages/django/views/generic/base.py\", line 104, in view\n return self.dispatch(request, *args, **kwargs)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/usr/local/lib/python3.11/site-packages/django/views/generic/base.py\", line 143, in dispatch\n return handler(request, *args, **kwargs)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/usr/local/lib/python3.11/site-packages/django/views/generic/detail.py\", line 109, in get\n context = self.get_context_data(object=self.object)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/usr/local/lib/python3.11/site-packages/vrobbler/apps/scrobbles/views.py\", line 219, in get_context_data\n qs = media.scrobble_set.filter(user=self.request.user)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/usr/local/lib/python3.11/site-packages/django/db/models/manager.py\", line 87, in manager_method\n return getattr(self.get_queryset(), name)(*args, **kwargs)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/usr/local/lib/python3.11/site-packages/django/db/models/query.py\", line 1436, in filter\n return self._filter_or_exclude(False, args, kwargs)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/usr/local/lib/python3.11/site-packages/django/db/models/query.py\", line 1454, in _filter_or_exclude\n clone._filter_or_exclude_inplace(negate, args, kwargs)\n File \"/usr/local/lib/python3.11/site-packages/django/db/models/query.py\", line 1461, in _filter_or_exclude_inplace\n self._query.add_q(Q(*args, **kwargs))\n File \"/usr/local/lib/python3.11/site-packages/django/db/models/sql/query.py\", line 1546, in add_q\n clause, _ = self._add_q(q_object, self.used_aliases)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/usr/local/lib/python3.11/site-packages/django/db/models/sql/query.py\", line 1577, in _add_q\n child_clause, needed_inner = self.build_filter(\n ^^^^^^^^^^^^^^^^^^\n File \"/usr/local/lib/python3.11/site-packages/django/db/models/sql/query.py\", line 1492, in build_filter\n condition = self.build_lookup(lookups, col, value)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/usr/local/lib/python3.11/site-packages/django/db/models/sql/query.py\", line 1319, in build_lookup\n lookup = lookup_class(lhs, rhs)\n ^^^^^^^^^^^^^^^^^^^^^^\n File \"/usr/local/lib/python3.11/site-packages/django/db/models/lookups.py\", line 27, in __init__\n self.rhs = self.get_prep_lookup()\n ^^^^^^^^^^^^^^^^^^^^^^\n File \"/usr/local/lib/python3.11/site-packages/django/db/models/fields/related_lookups.py\", line 166, in get_prep_lookup\n self.rhs = target_field.get_prep_value(self.rhs)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/usr/local/lib/python3.11/site-packages/django/db/models/fields/__init__.py\", line 2055, in get_prep_value\n raise e.__class__(\nTypeError: Field 'id' expected a number but got <SimpleLazyObject: <django.contrib.auth.models.AnonymousUser object at 0x8ffebe31690>>.", "status_code": 500, "request": "<WSGIRequest: GET '/books/0879dd15-fe34-4ed4-b6b0-f68c924aed23/'>"}