[release] Bump to version 69.0
All checks were successful
ci / test (push) Successful in 2m2s
ci / build-and-deploy (push) Successful in 48s

- 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
This commit is contained in:
2026-09-01 13:08:41 -04:00
parent 102380a8ff
commit a2cd0ea1d5
3 changed files with 155 additions and 153 deletions

View File

@ -1,5 +1,158 @@
#+title: CHANGELOG #+title: CHANGELOG
* Version 69.0 [5/5]
** 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 optional =author= (used as an =inauthor:= query term) and returns
the top-level Google Books volume =id= as =google_books_id=.
- ~vrobbler/apps/books/models.py~: added a =google_books_id= CharField to
=Book=. =find_or_create()= now:
- short-circuits on an exact =original_title= match (no lookups);
- after enrichment, reuses an existing book by =google_books_id=, falling
back to a =title= + author match — mirroring how Track keys off
=musicbrainz_id=.
- =fix_metadata()= gained a Google Books fallback so asynchronously enriched
(e.g. KoReader-imported) books also capture =google_books_id=.
- ~vrobbler/apps/books/management/commands/cleanup_book_metadata.py~: passes
the author to the Google lookup and persists =google_books_id= on backfill.
- Migration ~0040_book_google_books_id~ adds the field.
** DONE [#C] Fix dead source icon links on book and brick set pages :books:templates:
:PROPERTIES:
:ID: 35ce01a2-0dca-4bd3-83ea-ca826c734c36
:END:
*** 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~: added =Book.openlibrary_link= (builds
=https://openlibrary.org/works/{olid}= or =/books/{olid}= for edition IDs)
and =Book.bookshop_link= (uses the ISBN, in =/a/{affiliate_id}/{isbn}=
affiliate form when =BOOKSHOP_AFFILIATE_ID= is set, otherwise a
=/books?keywords={isbn}= search link).
- ~vrobbler/settings.py~: added =BOOKSHOP_AFFILIATE_ID= read from
=VROBBLER_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_id= filter flag for books lacking any of
=openlibrary_id= / =google_books_id= / =comicvine_id=.
- ~vrobbler/apps/books/models.py~: added =Book.is_comic= so 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 in =find_or_create()=, =fix_metadata()=, and
~cleanup_book_metadata.py~ now 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:
:PROPERTIES:
:ID: ddfed612-1f4e-b0bb-5310-19adf9cf9336
:END:
*** 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
calls =Book.fix_metadata()= and tags the book =book-enriched= on success or
=enrichment-failed= on failure.
- =create_book_from_row()= in ~vrobbler/apps/books/koreader.py~ now dispatches
that task asynchronously after creating a new book, instead of the commented
out synchronous =fix_metadata()= call.
- Fixed =Book.fix_metadata()= to not reference the removed =locg_slug= field,
and to handle =authors=/=genres= keys returned by the current OpenLibrary
source (previously it would crash).
- Routed =enrich_book_metadata= to the =background= worker queue.
** DONE [#B] There should a global chart aggregator that builds charts across all users :charts:feature:
:PROPERTIES:
:ID: ccc257d5-7d35-42b0-9af2-2941b67880fd
:END:
*** 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 =ChartRecord= rows with a NULL =user=
(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 triggers =scrobbles.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-level =MEDIA_CONFIG= and pulled the aggregation and upsert
logic into shared =_aggregate_media()= and =_upsert_chart_records()=
helpers used by both =build_charts()= and the new =build_global_charts()=.
- Added =build_global_charts()= plus =build_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()= and
=rebuild_yearly_charts()= now also build the matching global charts.
- Added =GlobalChartView= at =/charts/global/= (~charts.GlobalChartView~) with a
new ~charts/global_chart_index.html~ template 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.ChartContextMixin~ queries =user__isnull=True= records
when the request user is anonymous.
- Tests in ~vrobbler/apps/charts/tests/test_views.py~ and new global chart
tests in ~vrobbler/apps/charts/tests/test_utils.py~.
** DONE [#B] Make it possible for anonymous users to view media objects :media:templates:
:PROPERTIES:
:ID: f12fd0b4-b728-4fbc-adf7-d51695d8c155
:END:
*** 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
#+begin_src json
{"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/'>"}
#+end_src
* Version 68.6 [1/1] * Version 68.6 [1/1]
** DONE [#A] Music track scrobbles now frequently find the wrong track :music:metadata:scrobbles: ** DONE [#A] Music track scrobbles now frequently find the wrong track :music:metadata:scrobbles:
:PROPERTIES: :PROPERTIES:

View File

@ -18,7 +18,7 @@ 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 to be sometimes cathartic and sometimes functional as I try to remember when I
did a thing. did a thing.
* Backlog [1/35] :vrobbler:project:personal: * Backlog [0/31] :vrobbler:project:personal:
** TODO [#C] Configure IMAP folder/start in user profile :imap:settings: ** TODO [#C] Configure IMAP folder/start in user profile :imap:settings:
*** Description *** Description
@ -589,154 +589,3 @@ The Edit log form should have from top to bottom:
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. 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 optional =author= (used as an =inauthor:= query term) and returns
the top-level Google Books volume =id= as =google_books_id=.
- ~vrobbler/apps/books/models.py~: added a =google_books_id= CharField to
=Book=. =find_or_create()= now:
- short-circuits on an exact =original_title= match (no lookups);
- after enrichment, reuses an existing book by =google_books_id=, falling
back to a =title= + author match — mirroring how Track keys off
=musicbrainz_id=.
- =fix_metadata()= gained a Google Books fallback so asynchronously enriched
(e.g. KoReader-imported) books also capture =google_books_id=.
- ~vrobbler/apps/books/management/commands/cleanup_book_metadata.py~: passes
the author to the Google lookup and persists =google_books_id= on backfill.
- Migration ~0040_book_google_books_id~ adds the field.
** DONE [#C] Fix dead source icon links on book and brick set pages :books:templates:
:PROPERTIES:
:ID: 35ce01a2-0dca-4bd3-83ea-ca826c734c36
:END:
*** 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~: added =Book.openlibrary_link= (builds
=https://openlibrary.org/works/{olid}= or =/books/{olid}= for edition IDs)
and =Book.bookshop_link= (uses the ISBN, in =/a/{affiliate_id}/{isbn}=
affiliate form when =BOOKSHOP_AFFILIATE_ID= is set, otherwise a
=/books?keywords={isbn}= search link).
- ~vrobbler/settings.py~: added =BOOKSHOP_AFFILIATE_ID= read from
=VROBBLER_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_id= filter flag for books lacking any of
=openlibrary_id= / =google_books_id= / =comicvine_id=.
- ~vrobbler/apps/books/models.py~: added =Book.is_comic= so 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 in =find_or_create()=, =fix_metadata()=, and
~cleanup_book_metadata.py~ now 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:
:PROPERTIES:
:ID: ddfed612-1f4e-b0bb-5310-19adf9cf9336
:END:
*** 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
calls =Book.fix_metadata()= and tags the book =book-enriched= on success or
=enrichment-failed= on failure.
- =create_book_from_row()= in ~vrobbler/apps/books/koreader.py~ now dispatches
that task asynchronously after creating a new book, instead of the commented
out synchronous =fix_metadata()= call.
- Fixed =Book.fix_metadata()= to not reference the removed =locg_slug= field,
and to handle =authors=/=genres= keys returned by the current OpenLibrary
source (previously it would crash).
- Routed =enrich_book_metadata= to the =background= worker queue.
** DONE [#B] There should a global chart aggregator that builds charts across all users :charts:feature:
:PROPERTIES:
:ID: ccc257d5-7d35-42b0-9af2-2941b67880fd
:END:
*** 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 =ChartRecord= rows with a NULL =user=
(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 triggers =scrobbles.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-level =MEDIA_CONFIG= and pulled the aggregation and upsert
logic into shared =_aggregate_media()= and =_upsert_chart_records()=
helpers used by both =build_charts()= and the new =build_global_charts()=.
- Added =build_global_charts()= plus =build_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()= and
=rebuild_yearly_charts()= now also build the matching global charts.
- Added =GlobalChartView= at =/charts/global/= (~charts.GlobalChartView~) with a
new ~charts/global_chart_index.html~ template 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.ChartContextMixin~ queries =user__isnull=True= records
when the request user is anonymous.
- Tests in ~vrobbler/apps/charts/tests/test_views.py~ and new global chart
tests in ~vrobbler/apps/charts/tests/test_utils.py~.
** DONE [#B] Make it possible for anonymous users to view media objects :media:templates:
:PROPERTIES:
:ID: f12fd0b4-b728-4fbc-adf7-d51695d8c155
:END:
*** 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
#+begin_src json
{"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/'>"}
#+end_src

View File

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "vrobbler" name = "vrobbler"
version = "68.6" version = "69.0"
description = "" description = ""
authors = ["Colin Powell <colin@unbl.ink>"] authors = ["Colin Powell <colin@unbl.ink>"]