Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 15 additions & 10 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,21 @@ border-radius: 128px;
them keeps each one's sort instead of carrying one sort everywhere. A
collection you haven't sorted yet keeps whatever sort you arrive with.
- Clearing a search puts back the sort the search replaced.
- Sort by Alternate Series (ComicInfo AlternateSeries / AlternateNumber).
Comics group by their alternate series and order by its issue number
inside that group. Filter by an alternate series first to sort by that one
instead of the first one alphabetically. Comics with no alternate series
sort by their real series and issue, so they interleave rather than
clumping, and an alternate series with no issue numbers keeps its comics
in their own issue order. Available in cover view as well as the table.
- Read an alternate series as a reading order: pick it in the reader's
reading-order menu and next/prev follow the alternate numbering. Handy for
using alternate series tags as durable reading lists.
- Sort by Reprints, the other editions an issue was published as. Comics
group by their reprint series and order by its issue number inside that
group. Filter by a reprint first to sort by that one instead of the first
one alphabetically. Comics with no reprints sort by their real series and
issue, so they interleave rather than clumping, and a reprint series with
no issue numbers keeps its comics in their own issue order. Available in
cover view as well as the table.
- Read a reprint series as a reading order: pick it in the reader's
reading-order menu and next/prev follow its numbering. Handy for using
reprint tags as durable reading lists.
- "Alternate Series" is now called "Reprints" everywhere — the sort, the
table column, the filter, the metadata panel and the reader's
reading-order menu. It covers MetronInfo Reprints and localized
AlternativeNames, ComicInfo AlternateSeries and CoMet isVersionOf, so it
is named for all of them rather than for one.

- Fixes
- Saving browser settings sent an empty request that stored nothing. The
Expand Down
14 changes: 8 additions & 6 deletions codex/choices/browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
{
"created_at": "Added Time",
"age_rating": "Age Rating",
"reprints": "Alternate Series",
"characters": "Characters",
"child_count": "Child Count",
"community_rating": "Community Rating",
Expand Down Expand Up @@ -47,6 +46,7 @@
"publisher_name": "Publisher",
"date": "Publish Date",
"reading_direction": "Reading Direction",
"reprints": "Reprints",
"scan_info": "Scan Info",
"search_score": "Search Score",
"series_name": "Series",
Expand Down Expand Up @@ -531,11 +531,13 @@ def admin_default_route_for(top_collection: str) -> dict:
"edit_widget": None,
},
"reprints": {
# Alternate & localized series names (comicbox ``reprints``).
# "Reprints" reads as reprint editions to users, so the
# column, order-by entry and filter all say "Alternate
# Series"; only the ORM key stays ``reprints``.
"label": "Alternate Series",
# Other editions of this issue (comicbox ``reprints``):
# MetronInfo Reprints and localized AlternativeNames,
# ComicInfo AlternateSeries, CoMet isVersionOf. The UI says
# "Reprints" like MetronInfo, comicbox and the ORM key do —
# ComicInfo's "alternate series" names only one of those
# sources and misdescribes the localized names.
"label": "Reprints",
"sort_key": "reprints",
"m2m": True,
"editable": False,
Expand Down
6 changes: 3 additions & 3 deletions codex/collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,11 @@ def collection(self) -> str:
}
)

# A reader-only pseudo-collection: the reader can follow an alternate
# A reader-only pseudo-collection: the reader can follow a reprint
# series (ComicInfo ``AlternateSeries``) as a reading order, ordered by
# ``AlternateNumber``. Deliberately *not* a :class:`Collection` member —
# alternate series aren't browsable, and every map above is exhaustive
# reprints aren't browsable, and every map above is exhaustive
# over the enum, so a member without entries would break lookups that
# assume a browse route and a cover exists.
READER_REPRINT_COLLECTION: Final[str] = "reprints"
READER_REPRINT_LABEL: Final[str] = "Alternate Series"
READER_REPRINT_LABEL: Final[str] = "Reprints"
2 changes: 1 addition & 1 deletion codex/librarian/scribe/importer/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -501,7 +501,7 @@ def get_through_model(field: ManyToManyField) -> type[BaseModel]:
*COLLECTION_FIELD_NAMES,
"story_arc_numbers",
"folders",
# Not a browse collection, but the reader reads alternate series as a
# Not a browse collection, but the reader reads reprints as a
# reading order off ``Reprint.updated_at``, so a comic leaving one must
# re-stamp it the same way a story arc does.
REPRINTS_FIELD_NAME,
Expand Down
2 changes: 1 addition & 1 deletion codex/librarian/scribe/importer/query/links_m2m.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def _record_removed_m2m_source_collection(
only re-stamps collections a *current* comic still links into. Mirrors
the FK move capture in ``CreateComicsImporter``; the delete phase folds
these into the force-update map. ``Reprint`` rides along because the
reader reads alternate series as a reading order off its timestamp.
reader reads reprints as a reading order off its timestamp.
Tag-style m2ms (genres, characters, …) are not collections and are
ignored here.
"""
Expand Down
2 changes: 1 addition & 1 deletion codex/librarian/scribe/timestamp_update.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

# Rows whose ``updated_at`` gates a client-side reload. Browse collections
# bust cover caches; ``Reprint`` is not browsable but the reader offers
# alternate series as a reading order, and its arc mtime is read from these
# reprints as a reading order, and its arc mtime is read from these
# rows — without a re-stamp an open reader never notices a re-import.
_TIMESTAMP_MODELS = (*COLLECTION_MODELS, Reprint)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"""
Split Reprint.issue into sortable columns & remember sorts per collection.

The Alternate Series order_by key sorts comics by their ComicInfo
``AlternateNumber`` within an alternate series. ``issue`` is a string,
The Reprints order_by key sorts comics by their ComicInfo
``AlternateNumber`` within a reprint series. ``issue`` is a string,
so sorting it directly puts "#10" before "#2"; these derived columns
mirror ``Comic.issue_number`` / ``issue_suffix``.

Expand Down Expand Up @@ -73,7 +73,6 @@ class Migration(migrations.Migration):
choices=[
("created_at", "Added Time"),
("age_rating", "Age Rating"),
("reprints", "Alternate Series"),
("characters", "Characters"),
("child_count", "Child Count"),
("community_rating", "Community Rating"),
Expand Down Expand Up @@ -102,6 +101,7 @@ class Migration(migrations.Migration):
("publisher_name", "Publisher"),
("date", "Publish Date"),
("reading_direction", "Reading Direction"),
("reprints", "Reprints"),
("scan_info", "Scan Info"),
("search_score", "Search Score"),
("series_name", "Series"),
Expand Down
102 changes: 0 additions & 102 deletions codex/migrations/0054_merge_alternate_number_sort.py

This file was deleted.

4 changes: 2 additions & 2 deletions codex/models/named.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ class Reprint(BaseModel):
"""
An alternate or localized edition of this issue.

Denormalized on purpose: alternate series names must not become
Denormalized on purpose: reprint series names must not become
Series/Volume rows or they'd appear as phantom browser collections.
``series_name`` absorbs comicbox's ``series.sort_name`` when the
reprint carries no ``series.name`` (MetronInfo AlternativeNames do
Expand All @@ -142,7 +142,7 @@ class Reprint(BaseModel):
identifier = ForeignKey(Identifier, on_delete=SET_NULL, null=True)
# ``issue`` split into its sortable parts, mirroring
# ``Comic.issue_number`` / ``issue_suffix``. Without them the
# Alternate Series sort would order "#10" before "#2". Derived
# Reprints sort would order "#10" before "#2". Derived
# in ``presave``, never imported directly; unindexed because they're
# only read after an indexed join on pk or series_name.
issue_number = CoercingDecimalField(decimal_places=2, max_digits=10, null=True)
Expand Down
2 changes: 1 addition & 1 deletion codex/serializers/fields/collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class MtimeCollectionField(BrowserRouteCollectionField):
Browse routes plus the reader's alternate-series pseudo-collection.

The reader probes the mtime of every arc it offers, and one of those
is an alternate series, which has no browse route of its own. Kept
is a reprint series, which has no browse route of its own. Kept
separate from :class:`BrowserRouteCollectionField` so a reader-only
value can't leak into an actual browse route.
"""
Expand Down
2 changes: 1 addition & 1 deletion codex/serializers/fields/reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

# Collections a comic can be read "within". Mostly browse collections;
# p/i/root have no arc of their own (params collapses them to series).
# ``reprints`` is the one reader-only entry — an alternate series is a
# ``reprints`` is the one reader-only entry — a reprint series is a
# reading order without a browse route.
VALID_ARC_COLLECTIONS = (
Collection.SERIES,
Expand Down
8 changes: 6 additions & 2 deletions codex/user_data/restore.py
Original file line number Diff line number Diff line change
Expand Up @@ -559,8 +559,12 @@ def _restore_settings_browser(

# Sort keys retired into another key. Sort-only, because there was never
# an ``alternate_number`` filter column for ``_resolve_filter_column``
# to look for (0054: the Alternate Number sort merged into Alternate
# Series).
# to look for (the Alternate Number sort merged into Reprints before
# either shipped).
#
# No migration pairs with this: the retired key never reached a release,
# so only a sidecar written by a development build can still carry it —
# and a sidecar outlives the database it came from.
_SORT_KEY_RENAMES: Final[dict[str, str]] = {
**_LEGACY_KEY_RENAMES,
"alternate_number": "reprints",
Expand Down
2 changes: 1 addition & 1 deletion codex/views/browser/browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ def _add_table_view_sort_annotations(self, qs):
# Every key that sorts through elected-value aliases needs them
# annotated, extras included — ``_comic_extra_fields`` resolves an
# extra to the same aliases the primary uses. An active reprints
# filter names which alternate series the user is looking at, so
# filter names which reprint series the user is looking at, so
# the election is narrowed to it instead of picking the
# alphabetically first one.
reprint_pks = self.params.get("filters", {}).get("reprints", ())
Expand Down
18 changes: 9 additions & 9 deletions codex/views/browser/columns.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,9 @@
output_field=CharField(),
)

# The Alternate Series sort: a field list like the Name sort's, with
# The Reprints sort: a field list like the Name sort's, with
# each part preferring the alternate value. ``reprints`` is M2M — a
# comic can be in several alternate series — so each ORDER BY column
# comic can be in several reprint series — so each ORDER BY column
# must first collapse to one value per comic, and collapsing columns
# independently would pair one reprint's series with another's issue
# number. Instead a correlated subquery *elects* one reprint per comic
Expand Down Expand Up @@ -146,13 +146,13 @@ def _elected_reprint_value(value: str, reprint_pks: tuple | list = ()) -> Subque
"""Read one annotation off the outer comic's elected reprint."""
election = Reprint.objects.filter(comic=OuterRef("pk"), series_name__gt="")
if reprint_pks:
# A reprints filter names which alternate series the user is
# A reprints filter names which reprint series the user is
# looking at; elect among those instead of alphabetically.
election = election.filter(pk__in=reprint_pks)
election = election.annotate(
_series_key=Lower("series_name"),
_language_key=Lower("language"),
# An alternate series entry with no issue at all offers nothing
# A reprint entry with no issue at all offers nothing
# to sort by; null both parts so the outer Coalesce falls back
# to the comic's own issue *jointly*, never half-and-half.
_no_issue=Case(
Expand All @@ -175,15 +175,15 @@ def _elected_reprint_value(value: str, reprint_pks: tuple | list = ()) -> Subque

def reprints_sort_annotations(reprint_pks: tuple | list = ()) -> dict:
"""
Build the ordered ORDER BY aliases for the Alternate Series sort.
Build the ordered ORDER BY aliases for the Reprints sort.

Alternate series identity (name, volume, language — ``Reprint``'s
Reprint series identity (name, volume, language — ``Reprint``'s
unique key minus the issue) leads so every issue of one alternate
series groups together, then its parsed issue number and suffix
order the group. A comic with no alternate series falls back to its
order the group. A comic with no reprints falls back to its
own series and issue so mixed listings interleave. The fallback
series segment is ``Lower(name)`` rather than ``sort_name`` —
alternate series names sort by their raw name, so the article-moved
reprint series names sort by their raw name, so the article-moved
``sort_name`` would put "The Batman" and its untagged siblings at
opposite ends of the listing.

Expand Down Expand Up @@ -391,7 +391,7 @@ def m2m_columns() -> frozenset[str]:
# plain display aggregate. ``reprints`` is the only one: an alternate
# series has a series name *and* an issue number, so sorting it by the
# display label would order "#10" before "#2", and a comic carrying no
# alternate series would park in one undifferentiated clump. Other M2M
# reprints would park in one undifferentiated clump. Other M2M
# columns (genres, tags, …) have no such structure and keep the
# aggregate sort.

Expand Down
4 changes: 2 additions & 2 deletions codex/views/browser/intersections.py
Original file line number Diff line number Diff line change
Expand Up @@ -808,7 +808,7 @@ def _build_reprints_intersection_sort_sql(
return None
# One composed key per shared reprint, mirroring the field order of
# ``codex.views.browser.columns.reprints_sort_annotations``:
# alternate series identity (name, volume, language) then the issue
# reprint series identity (name, volume, language) then the issue
# rendered at fixed width so "#2" collates before "#10". The key
# reads ONLY reprint columns — the envelope selects ``display_name``
# as a bare column under ``GROUP BY target_id``, so anything read
Expand Down Expand Up @@ -841,7 +841,7 @@ def _build_reprints_intersection_sort_sql(
# RawSQL in a Django ``Coalesce`` would restore the correlated
# subquery to the GROUP BY that ``_IntersectionSortRawSQL`` exists
# to keep it out of. An empty intersection (children share no
# alternate series, or disagree) sorts by the collection's own name.
# reprint series, or disagree) sorts by the collection's own name.
own = _collection_own_sort_sql(collection_model)
sql = f"COALESCE(NULLIF({envelope}, ''), {own})"
return _IntersectionSortRawSQL(sql, [])
Expand Down
2 changes: 1 addition & 1 deletion codex/views/browser/mtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class MtimeView(BrowserCollectionMtimeView):

def _get_reprint_mtime(self, pks):
"""
Get the mtime of an alternate series the reader is reading.
Get the mtime of a reprint series the reader is reading.

``Reprint`` isn't a browse collection, so it has no filtered
queryset to aggregate — read its rows directly.
Expand Down
4 changes: 2 additions & 2 deletions codex/views/browser/order_by.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,9 @@ def _comic_order_fields_head(self, order_key: str, comic_sort_names) -> list:
if order_key in m2m_sort_columns():
# M2M sort through elected-value aliases (``reprints``): a
# field list like the ``sort_name`` head, each part read
# from the comic's elected alternate series with the
# from the comic's elected reprint series with the
# comic's own series and issue as the fallback, so comics
# carrying no alternate series interleave by their real
# carrying no reprints interleave by their real
# series instead of clumping under an empty list.
return list(m2m_sort_order_fields(order_key))
if order_key in m2m_columns():
Expand Down
Loading