From 8f944432cacf6a571191ed4c470503faac86afc4 Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Sun, 30 Aug 2026 12:53:16 -0700 Subject: [PATCH] Fold 0054 into 0053 and rename Alternate Series to Reprints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 0054 data migration remapped stored alternate_number sort keys, but it can only fire on a database that already applied 0053 (so 0053 never re-runs) AND holds the key — the dev database, which is being rebuilt. On a fresh database it finds zero rows, and the key never reached a release, so it is dead by construction. 0053 is the only migration again. The sidecar restore rename stays: a sidecar written by a development build outlives the database it came from, and that is now the only way the retired key can enter a fresh install. The concept is renamed to Reprints across the sort, table column, filter, metadata panel, tag editor and the reader's reading-order menu. "Alternate Series" is ComicInfo's word for one of five sources — MetronInfo contributes both Reprints and localized AlternativeNames, CoMet isVersionOf, and the Metron and ComicVine APIs their own — and it misdescribes the localized names. MetronInfo, comicbox and the ORM all already say reprints, so the UI now matches the data model end to end and the internal vocabulary stops needing translation. 0053's choices tuple is edited in place (unreleased); released 0049 keeps its historical label, since the final migrated state comes from 0053. The redundant metadata TAG_NAMES override is dropped now that the label is the capital-cased key. Co-Authored-By: Claude Fable 5 --- NEWS.md | 25 ++-- codex/choices/browser.py | 14 +- codex/collection.py | 6 +- codex/librarian/scribe/importer/const.py | 2 +- .../scribe/importer/query/links_m2m.py | 2 +- codex/librarian/scribe/timestamp_update.py | 2 +- ...ssue_number_and_collection_order_memory.py | 6 +- .../0054_merge_alternate_number_sort.py | 102 -------------- codex/models/named.py | 4 +- codex/serializers/fields/collection.py | 2 +- codex/serializers/fields/reader.py | 2 +- codex/user_data/restore.py | 8 +- codex/views/browser/browser.py | 2 +- codex/views/browser/columns.py | 18 +-- codex/views/browser/intersections.py | 4 +- codex/views/browser/mtime.py | 2 +- codex/views/browser/order_by.py | 4 +- codex/views/const.py | 2 +- codex/views/reader/arcs.py | 8 +- .../browser/card/order-by-caption.vue | 2 +- .../browser/toolbars/top/filter-sub-menu.vue | 2 +- .../metadata/edit-mode/edit-panel.vue | 8 +- .../components/online-tag/prompt-popup.vue | 2 +- .../reader/toolbars/top/reader-arc-select.vue | 2 +- frontend/src/stores/metadata.js | 1 - .../tests/unit/edit-panel-reprints.test.js | 12 +- frontend/tests/unit/metadata-reprints.test.js | 17 ++- frontend/tests/unit/order-by-caption.test.js | 4 +- frontend/tests/unit/prompt-popup.test.js | 4 +- frontend/tests/unit/reader-arc-select.test.js | 6 +- tests/test_alternate_series_sort_remap.py | 130 ------------------ tests/test_browser_reprints_column.py | 42 +++--- tests/test_metadata_reprints.py | 2 +- tests/test_reader.py | 16 +-- tests/test_reprints.py | 2 +- ...nate_series.py => test_search_reprints.py} | 2 +- 36 files changed, 123 insertions(+), 346 deletions(-) delete mode 100644 codex/migrations/0054_merge_alternate_number_sort.py delete mode 100644 tests/test_alternate_series_sort_remap.py rename tests/{test_search_alternate_series.py => test_search_reprints.py} (99%) diff --git a/NEWS.md b/NEWS.md index 6fd5d2e31..55cb85634 100644 --- a/NEWS.md +++ b/NEWS.md @@ -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 diff --git a/codex/choices/browser.py b/codex/choices/browser.py index 011af59dd..369af09e8 100644 --- a/codex/choices/browser.py +++ b/codex/choices/browser.py @@ -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", @@ -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", @@ -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, diff --git a/codex/collection.py b/codex/collection.py index 974879fac..7a3ec6fca 100644 --- a/codex/collection.py +++ b/codex/collection.py @@ -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" diff --git a/codex/librarian/scribe/importer/const.py b/codex/librarian/scribe/importer/const.py index d5e798c38..702508a98 100644 --- a/codex/librarian/scribe/importer/const.py +++ b/codex/librarian/scribe/importer/const.py @@ -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, diff --git a/codex/librarian/scribe/importer/query/links_m2m.py b/codex/librarian/scribe/importer/query/links_m2m.py index 0df6e3b1c..03fca77f8 100644 --- a/codex/librarian/scribe/importer/query/links_m2m.py +++ b/codex/librarian/scribe/importer/query/links_m2m.py @@ -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. """ diff --git a/codex/librarian/scribe/timestamp_update.py b/codex/librarian/scribe/timestamp_update.py index 7434d2659..af10f3633 100644 --- a/codex/librarian/scribe/timestamp_update.py +++ b/codex/librarian/scribe/timestamp_update.py @@ -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) diff --git a/codex/migrations/0053_reprint_issue_number_and_collection_order_memory.py b/codex/migrations/0053_reprint_issue_number_and_collection_order_memory.py index bc01c8b67..6e21b184f 100644 --- a/codex/migrations/0053_reprint_issue_number_and_collection_order_memory.py +++ b/codex/migrations/0053_reprint_issue_number_and_collection_order_memory.py @@ -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``. @@ -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"), @@ -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"), diff --git a/codex/migrations/0054_merge_alternate_number_sort.py b/codex/migrations/0054_merge_alternate_number_sort.py deleted file mode 100644 index 5516c58cf..000000000 --- a/codex/migrations/0054_merge_alternate_number_sort.py +++ /dev/null @@ -1,102 +0,0 @@ -""" -Merge the Alternate Number sort into Alternate Series. - -The two sorts each did half the job — ``reprints`` ordered by a display -label (so "#10" preceded "#2") and ``alternate_number`` ordered by the -parsed number but ignored which alternate series it belonged to. They -are now one key, ``reprints``. - -0053 dropped ``alternate_number`` from the field's choices, but choices -aren't enforced by SQLite and 0053 has already been applied on every -database that could hold the value, so the stored settings need this -separate pass. Left in place, a stale key reaches ORDER BY unvalidated -(settings load raw) and raises FieldError. - -The key lives in three places on SettingsBrowser — the ``order_by`` -column, the ``order_extra_keys`` list, and the per-top-collection -``collection_order_memory`` map — and saved views are more rows in the -same table, so every row is remapped regardless of name or client. -""" - -from django.db import migrations - -_OLD_KEY = "alternate_number" -_NEW_KEY = "reprints" - - -def _remap_extra_keys(entries) -> tuple[list, bool]: - """Remap the sort key in an extras list, dropping a duplicate.""" - if not isinstance(entries, list): - return entries, False - remapped: list = [] - changed = False - seen: set = set() - for entry in entries: - key = entry.get("key") if isinstance(entry, dict) else None - if key == _OLD_KEY: - entry = {**entry, "key": _NEW_KEY} # noqa: PLW2901 - key = _NEW_KEY - changed = True - if key in seen: - # The row already sorted by the surviving key. Two entries - # for one column is not a state the sort can express, so the - # first occurrence wins, as the serializer's own extras - # cleaner does. - changed = True - continue - seen.add(key) - remapped.append(entry) - return remapped, changed - - -def _remap_memory(memory) -> tuple[dict, bool]: - """Remap the sort key inside a collection_order_memory map.""" - if not isinstance(memory, dict): - return memory, False - changed = False - for remembered in memory.values(): - if not isinstance(remembered, dict): - continue - if remembered.get("order_by") == _OLD_KEY: - remembered["order_by"] = _NEW_KEY - changed = True - extras, extras_changed = _remap_extra_keys(remembered.get("order_extra_keys")) - if extras_changed: - remembered["order_extra_keys"] = extras - changed = True - return memory, changed - - -def _remap_browser_settings(apps, _schema_editor) -> None: - settings_browser = apps.get_model("codex", "SettingsBrowser") - settings_browser.objects.filter(order_by=_OLD_KEY).update(order_by=_NEW_KEY) - # JSON payloads need a python pass; JSONField key lookups are - # unsupported on SQLite, so scan and rewrite sparsely. - rows = [] - for row in settings_browser.objects.only( - "pk", "order_extra_keys", "collection_order_memory" - ): - extras, extras_changed = _remap_extra_keys(row.order_extra_keys) - memory, memory_changed = _remap_memory(row.collection_order_memory) - if extras_changed or memory_changed: - row.order_extra_keys = extras - row.collection_order_memory = memory - rows.append(row) - if rows: - settings_browser.objects.bulk_update( - rows, ["order_extra_keys", "collection_order_memory"] - ) - - -class Migration(migrations.Migration): - """Remap the retired alternate_number sort key onto reprints.""" - - dependencies = [ - ("codex", "0053_reprint_issue_number_and_collection_order_memory"), - ] - - operations = [ - # Irreversible by design: both former keys map onto ``reprints``, - # so a reverse pass can't know which rows to send back. - migrations.RunPython(_remap_browser_settings, migrations.RunPython.noop), - ] diff --git a/codex/models/named.py b/codex/models/named.py index 5aadde84c..e9f7c2b57 100644 --- a/codex/models/named.py +++ b/codex/models/named.py @@ -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 @@ -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) diff --git a/codex/serializers/fields/collection.py b/codex/serializers/fields/collection.py index c66fbbc15..3d381637c 100644 --- a/codex/serializers/fields/collection.py +++ b/codex/serializers/fields/collection.py @@ -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. """ diff --git a/codex/serializers/fields/reader.py b/codex/serializers/fields/reader.py index 9b4ee07e8..7ae3a35c4 100644 --- a/codex/serializers/fields/reader.py +++ b/codex/serializers/fields/reader.py @@ -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, diff --git a/codex/user_data/restore.py b/codex/user_data/restore.py index d18d2c323..6602c7d5d 100644 --- a/codex/user_data/restore.py +++ b/codex/user_data/restore.py @@ -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", diff --git a/codex/views/browser/browser.py b/codex/views/browser/browser.py index 9fddc3fa7..08639c1b1 100644 --- a/codex/views/browser/browser.py +++ b/codex/views/browser/browser.py @@ -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", ()) diff --git a/codex/views/browser/columns.py b/codex/views/browser/columns.py index 860818e6f..f00cee034 100644 --- a/codex/views/browser/columns.py +++ b/codex/views/browser/columns.py @@ -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 @@ -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( @@ -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. @@ -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. diff --git a/codex/views/browser/intersections.py b/codex/views/browser/intersections.py index c66b0fc34..38411a253 100644 --- a/codex/views/browser/intersections.py +++ b/codex/views/browser/intersections.py @@ -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 @@ -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, []) diff --git a/codex/views/browser/mtime.py b/codex/views/browser/mtime.py index 053ee0cb2..c19b60ce0 100644 --- a/codex/views/browser/mtime.py +++ b/codex/views/browser/mtime.py @@ -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. diff --git a/codex/views/browser/order_by.py b/codex/views/browser/order_by.py index 8230ca665..6c6783d2d 100644 --- a/codex/views/browser/order_by.py +++ b/codex/views/browser/order_by.py @@ -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(): diff --git a/codex/views/const.py b/codex/views/const.py index 093a90ff3..f666408df 100644 --- a/codex/views/const.py +++ b/codex/views/const.py @@ -82,7 +82,7 @@ COMIC_COLLECTION: "pk", FOLDER_COLLECTION: "parent_folder", STORY_ARC_COLLECTION: "story_arc_numbers__story_arc", - # Reader-only: alternate series are a reading order, not a browse + # Reader-only: reprints are a reading order, not a browse # collection. Browse callers only ever index this map with a # URL-regex-validated collection, so the extra key is inert there. READER_REPRINT_COLLECTION: "reprints", diff --git a/codex/views/reader/arcs.py b/codex/views/reader/arcs.py index 6b9c616c2..e9680a42c 100644 --- a/codex/views/reader/arcs.py +++ b/codex/views/reader/arcs.py @@ -34,7 +34,7 @@ # Arc collections whose rows are groups rather than a single row, so the # requested ids may be a stale subset of the current group. Both story -# arcs (grouped by sort_name) and alternate series (grouped by identity) +# arcs (grouped by sort_name) and reprints (grouped by identity) # accept an intersecting id set as the same arc. _MULTI_ROW_ARC_COLLECTIONS = frozenset( {STORY_ARC_COLLECTION, READER_REPRINT_COLLECTION} @@ -136,7 +136,7 @@ def _get_story_arcs(self, comic: Comic, arcs, max_mtime: int | None): def _get_reprint_arcs(self, comic: Comic, arcs, max_mtime: int | None): """Append the alternate series (ComicInfo AlternateSeries) arcs.""" - # An alternate series is identified by everything but the issue — + # A reprint series is identified by everything but the issue — # that's ``Reprint``'s unique key minus ``issue``. Splitting on # volume and language keeps a v1 and a v2, or an English and a # Spanish edition, from merging into one reading order. @@ -156,7 +156,7 @@ def _get_reprint_arcs(self, comic: Comic, arcs, max_mtime: int | None): language=language, ) - # Every issue of an alternate series is its own ``Reprint`` row, so + # Every issue of a reprint series is its own ``Reprint`` row, so # the arc handle has to be the whole group's pks, not just this # comic's. Keying on one comic's row would make the *next* book # report a different id set and silently drop the reading order. @@ -183,7 +183,7 @@ def _get_reprint_arcs(self, comic: Comic, arcs, max_mtime: int | None): def _fallback_arc_collection(arcs) -> str: """Pick a collection this comic actually has an arc for.""" # The requested collection can be valid yet absent for this comic - # (an alternate series the comic isn't in, a story arc it lost on + # (a reprint series the comic isn't in, a story arc it lost on # re-tag). Reading must still work, so fall back to the most # series-like arc available instead of raising. for collection in _ARC_COLLECTION_FALLBACK_ORDER: diff --git a/frontend/src/components/browser/card/order-by-caption.vue b/frontend/src/components/browser/card/order-by-caption.vue index 3a74830ca..6ca53c243 100644 --- a/frontend/src/components/browser/card/order-by-caption.vue +++ b/frontend/src/components/browser/card/order-by-caption.vue @@ -105,7 +105,7 @@ export default { return n.toFixed(2).replace(/\.?0+$/, ""); }, /* - * The alternate series order_value is the JSON array the table cell + * The reprint series order_value is the JSON array the table cell * renders. Collection rows sort by a fallback the caption can't * show, so only comics get a caption. */ diff --git a/frontend/src/components/browser/toolbars/top/filter-sub-menu.vue b/frontend/src/components/browser/toolbars/top/filter-sub-menu.vue index f36443b60..747b8fbfc 100644 --- a/frontend/src/components/browser/toolbars/top/filter-sub-menu.vue +++ b/frontend/src/components/browser/toolbars/top/filter-sub-menu.vue @@ -128,7 +128,7 @@ const FILTER_TITLE_OVERRIDES = { ageRatingMetron: "Age Rating", // "Reprints" reads as reprint editions; these are alternate and // localized series names. - reprints: "Alternate Series", + reprints: "Reprints", }; // Tab values are the filter keys; "Standardized" is the default tab. const AGE_RATING_DEFAULT_TAB = "ageRatingMetron"; diff --git a/frontend/src/components/metadata/edit-mode/edit-panel.vue b/frontend/src/components/metadata/edit-mode/edit-panel.vue index 03fbba135..ce6c48302 100644 --- a/frontend/src/components/metadata/edit-mode/edit-panel.vue +++ b/frontend/src/components/metadata/edit-mode/edit-panel.vue @@ -1037,7 +1037,7 @@ -
Alternate Series
+
Reprints
@@ -1110,7 +1110,7 @@ }) " > - + Add Alternate Series + + Add Reprint @@ -2054,7 +2054,7 @@ export default { })); } - // Alternate series — the panel renders the composed `name`, but the + // Reprints — the panel renders the composed `name`, but the // editor rebuilds comicbox's nested reprint from the flat columns. if (this.md.reprints?.length) { this.reprints = this.md.reprints.map((reprint) => ({ @@ -2267,7 +2267,7 @@ export default { } } - // Alternate series — only include if changed + // Reprints — only include if changed if (changed.has("reprints")) { const reprints = this.buildReprints(); if (reprints.length) cbPatch.reprints = reprints; diff --git a/frontend/src/components/online-tag/prompt-popup.vue b/frontend/src/components/online-tag/prompt-popup.vue index 9e2eedc3b..550de578d 100644 --- a/frontend/src/components/online-tag/prompt-popup.vue +++ b/frontend/src/components/online-tag/prompt-popup.vue @@ -60,7 +60,7 @@ {{ Math.round(candidate.score * 100) }}% -
b.text().includes(label)); } -describe("EditPanel alternate series rows", () => { +describe("EditPanel reprints rows", () => { test("seeds one row per reprint from the flat columns", async () => { const wrapper = await mountPanel({ md: { reprints: SHAPED_REPRINTS } }); @@ -90,7 +90,7 @@ describe("EditPanel alternate series rows", () => { test("the add button appends a blank row", async () => { const wrapper = await mountPanel(); - await findButton(wrapper, "Add Alternate Series").trigger("click"); + await findButton(wrapper, "Add Reprint").trigger("click"); expect(wrapper.vm.reprints).toStrictEqual([ { series_name: "", volume: "", issue: "", language: null }, @@ -98,7 +98,7 @@ describe("EditPanel alternate series rows", () => { }); }); -describe("EditPanel alternate series patch", () => { +describe("EditPanel reprints patch", () => { test("re-nests the parts into comicbox reprints", async () => { const wrapper = await mountPanel({ md: { reprints: SHAPED_REPRINTS } }); wrapper.vm.reprints[0].issue = "1"; @@ -193,7 +193,7 @@ describe("EditPanel alternate series patch", () => { }); }); -describe("EditPanel alternate series format support", () => { +describe("EditPanel reprints format support", () => { test("ComicInfo disables the MetronInfo-only volume and language", async () => { const wrapper = await mountPanel({ formats: ["COMIC_INFO"], @@ -217,7 +217,7 @@ describe("EditPanel alternate series format support", () => { formats: ["METRON_INFO"], md: { reprints: SHAPED_REPRINTS }, }); - const btn = findButton(wrapper, "Add Alternate Series"); + const btn = findButton(wrapper, "Add Reprint"); expect(btn.element.disabled).toBe(false); expect(wrapper.vm.isFieldDisabled("reprint_volume")).toBe(false); diff --git a/frontend/tests/unit/metadata-reprints.test.js b/frontend/tests/unit/metadata-reprints.test.js index 3e41be1b9..f9756b3bf 100644 --- a/frontend/tests/unit/metadata-reprints.test.js +++ b/frontend/tests/unit/metadata-reprints.test.js @@ -1,10 +1,10 @@ /* - * The metadata pane's alternate-series row. + * The metadata pane's reprints row. * * The backend composes ``Reprint.name`` from whichever of series name, * volume, issue and language the reprint carries, so the client just - * renders it like any other ``{pk, name, url}`` tag row. Only the row - * label differs from the capital-cased key: "Alternate Series". + * renders it like any other ``{pk, name, url}`` tag row, under the + * capital-cased key. * * Chips route through the ``reprints`` browser filter, the key phase E * registers. @@ -24,34 +24,33 @@ const REPRINTS = Object.freeze([ { pk: 8, name: "Capitan Sciencia v1 (es)" }, ]); -describe("metadata store alternate series row", () => { +describe("metadata store reprints row", () => { beforeEach(() => { setActivePinia(createPinia()); }); - test("labels the reprints row Alternate Series", () => { + test("labels the reprints row Reprints", () => { const store = useMetadataStore(); // Copied: the tags getter sorts the row in place. store.md = { reprints: [...REPRINTS] }; - const row = store.tags["Alternate Series"]; + const row = store.tags.Reprints; expect(row.filter).toBe("reprints"); expect(row.tags.map((tag) => tag.name)).toStrictEqual([ "Capitan Sciencia v1 (es)", "Kapitän Wissenschaft", ]); - expect(store.tags.Reprints).toBeUndefined(); }); test("omits the row when there are no reprints", () => { const store = useMetadataStore(); store.md = { reprints: [] }; - expect(store.tags["Alternate Series"]).toBeUndefined(); + expect(store.tags["Reprints"]).toBeUndefined(); }); }); -describe("alternate series chips", () => { +describe("reprint chips", () => { function mountTags() { const pinia = createTestingPinia({ initialState: { diff --git a/frontend/tests/unit/order-by-caption.test.js b/frontend/tests/unit/order-by-caption.test.js index 9a7d85ec4..0047434a9 100644 --- a/frontend/tests/unit/order-by-caption.test.js +++ b/frontend/tests/unit/order-by-caption.test.js @@ -25,7 +25,7 @@ function mountCaption(orderBy, item) { } describe("order by caption", () => { - test("alternate series joins the label list on a comic card", () => { + test("reprint series joins the label list on a comic card", () => { const wrapper = mountCaption("reprints", { orderValue: JSON.stringify(["Crossover v2", "Otra Serie (es)"]), collection: "comics", @@ -33,7 +33,7 @@ describe("order by caption", () => { expect(wrapper.text()).toBe("Crossover v2, Otra Serie (es)"); }); - test("alternate series shows nothing on a collection card", () => { + test("reprint series shows nothing on a collection card", () => { const wrapper = mountCaption("reprints", { orderValue: JSON.stringify(["Crossover"]), collection: "series", diff --git a/frontend/tests/unit/prompt-popup.test.js b/frontend/tests/unit/prompt-popup.test.js index 3e4c1887a..49e143140 100644 --- a/frontend/tests/unit/prompt-popup.test.js +++ b/frontend/tests/unit/prompt-popup.test.js @@ -1,7 +1,7 @@ /* * Tests for the Online Tagging match-review popup. * - * Comicbox scores candidates against alternate series names, so a comic filed + * Comicbox scores candidates against reprint series names, so a comic filed * under a localized title matches a canonical series that looks nothing like * its filename. The popup shows those aliases so the match is explicable, and * a pick carries the candidate's volume id so the apply replay can narrow to @@ -61,7 +61,7 @@ function mountPopup(candidates) { } describe("OnlineTagPromptPopup", () => { - describe("alternate series names", () => { + describe("reprint series names", () => { test("shows the aliases that explain an off-filename match", async () => { const { wrapper } = mountPopup([ candidate({ diff --git a/frontend/tests/unit/reader-arc-select.test.js b/frontend/tests/unit/reader-arc-select.test.js index 634792cb9..5a074b1e3 100644 --- a/frontend/tests/unit/reader-arc-select.test.js +++ b/frontend/tests/unit/reader-arc-select.test.js @@ -24,7 +24,7 @@ function mountArcSelect(arcs, arc = { collection: "series", ids: "1" }) { } describe("reader arc select", () => { - test("an alternate series arc gets its own subtitle and icon", () => { + test("a reprint arc gets its own subtitle and icon", () => { const wrapper = mountArcSelect({ series: { 1: { name: "Ser" } }, reprints: { "2,3": { name: "Crossover" } }, @@ -32,7 +32,7 @@ describe("reader arc select", () => { const items = wrapper.vm.items; const reprint = items.find((item) => item.collection === "reprints"); expect(reprint).toBeTruthy(); - expect(reprint.subtitle).toBe("Alternate Series"); + expect(reprint.subtitle).toBe("Reprints"); expect(reprint.title).toBe("Crossover"); expect(reprint.prependIcon).toBeTruthy(); }); @@ -48,6 +48,6 @@ describe("reader arc select", () => { ); expect(byCollection.series).toBe("Series"); expect(byCollection.arcs).toBe("Story Arc"); - expect(byCollection.reprints).toBe("Alternate Series"); + expect(byCollection.reprints).toBe("Reprints"); }); }); diff --git a/tests/test_alternate_series_sort_remap.py b/tests/test_alternate_series_sort_remap.py deleted file mode 100644 index dd03474d5..000000000 --- a/tests/test_alternate_series_sort_remap.py +++ /dev/null @@ -1,130 +0,0 @@ -""" -0054 remaps the retired ``alternate_number`` sort key onto ``reprints``. - -The key lived in three places on ``SettingsBrowser`` — the ``order_by`` -column, the ``order_extra_keys`` list and the per-top-collection -``collection_order_memory`` map. Stored settings load without -re-validation, so anything the migration misses reaches ORDER BY and -raises. Runs the migration's helper against live models: every surface -it touches still exists post-migration. -""" - -import importlib -from typing import Final - -from django.apps import apps -from django.test import TestCase - -from codex.models.settings import SettingsBrowser, SettingsBrowserShow - -_MIGRATION = importlib.import_module( - "codex.migrations.0054_merge_alternate_number_sort" -) -_remap_browser_settings = _MIGRATION._remap_browser_settings # noqa: SLF001 - -_OLD: Final = "alternate_number" -_NEW: Final = "reprints" - - -class Migration0054RemapTestCase(TestCase): - """The retired sort key is rewritten everywhere it can be stored.""" - - @staticmethod - def _make_row(**overrides) -> SettingsBrowser: - show, _ = SettingsBrowserShow.objects.get_or_create() - fields = { - "show": show, - "order_by": "sort_name", - "order_extra_keys": [], - "collection_order_memory": {}, - } - fields.update(overrides) - return SettingsBrowser.objects.create(**fields) - - def test_order_by_column_remapped(self) -> None: - """The plain sort key moves over; other rows are left alone.""" - remapped = self._make_row(order_by=_OLD) - untouched = self._make_row(order_by="sort_name") - - _remap_browser_settings(apps, None) - - remapped.refresh_from_db() - untouched.refresh_from_db() - assert remapped.order_by == _NEW - assert untouched.order_by == "sort_name" - - def test_extra_sort_keys_remapped(self) -> None: - """A multi-sort extra on the retired key moves over, keeping its direction.""" - row = self._make_row( - order_extra_keys=[ - {"key": "sort_name", "reverse": False}, - {"key": _OLD, "reverse": True}, - ] - ) - - _remap_browser_settings(apps, None) - - row.refresh_from_db() - assert row.order_extra_keys == [ - {"key": "sort_name", "reverse": False}, - {"key": _NEW, "reverse": True}, - ] - - def test_extra_sort_keys_dedupe(self) -> None: - """A row already sorting by reprints doesn't end up with it twice.""" - # One column can only carry one sort, so the first wins. - row = self._make_row( - order_extra_keys=[ - {"key": _NEW, "reverse": False}, - {"key": _OLD, "reverse": True}, - ] - ) - - _remap_browser_settings(apps, None) - - row.refresh_from_db() - assert row.order_extra_keys == [{"key": _NEW, "reverse": False}] - - def test_collection_order_memory_remapped(self) -> None: - """The per-top-collection sort memory is rewritten too.""" - # Missed here, the dead key gets re-injected into params the - # next time the user switches back to that top collection. - row = self._make_row( - collection_order_memory={ - "comics": { - "order_by": _OLD, - "order_reverse": True, - "order_extra_keys": [{"key": _OLD, "reverse": False}], - }, - "folders": { - "order_by": "sort_name", - "order_reverse": False, - "order_extra_keys": [], - }, - } - ) - - _remap_browser_settings(apps, None) - - row.refresh_from_db() - assert row.collection_order_memory == { - "comics": { - "order_by": _NEW, - "order_reverse": True, - "order_extra_keys": [{"key": _NEW, "reverse": False}], - }, - "folders": { - "order_by": "sort_name", - "order_reverse": False, - "order_extra_keys": [], - }, - } - - def test_saved_views_remapped(self) -> None: - """Saved views are more rows in the same table and get the same pass.""" - saved = self._make_row(name="A Saved View", order_by=_OLD) - - _remap_browser_settings(apps, None) - - saved.refresh_from_db() - assert saved.order_by == _NEW diff --git a/tests/test_browser_reprints_column.py b/tests/test_browser_reprints_column.py index dfb827ba5..312ba2f70 100644 --- a/tests/test_browser_reprints_column.py +++ b/tests/test_browser_reprints_column.py @@ -164,7 +164,7 @@ def test_collection_row_sort_by_reprints(self) -> None: self.comic.reprints.add( Reprint.objects.create(series_name="Zulu", volume_number=2) ) - # Each series' alternate series sorts opposite its own name, so + # Each series' reprint series sorts opposite its own name, so # this order can only come from the intersection SQL — falling # back to ``sort_name`` would put "Aaa" first. self._create_comic("C2", 1, series=self._create_series("Aaa")).reprints.add( @@ -234,17 +234,17 @@ def test_filter_narrows_to_tagged_comics(self) -> None: class BrowserAlternateSeriesSortTestCase(_ReprintsFixtureTestCase): """ - The Alternate Series sort: alternate series identity, then its issue. + The Reprints sort: reprint series identity, then its issue. - One key does both halves. It leads with the alternate series so every - issue of one alternate series groups together, then orders within + One key does both halves. It leads with the reprint series so every + issue of one reprint series groups together, then orders within that group by the parsed ComicInfo AlternateNumber so "#2" precedes "#10", and falls back to the comic's own series and issue so untagged comics interleave instead of clumping. """ def _tag(self, comic: Comic, issue: str, series_name: str = "Crossover") -> Reprint: - """Put ``comic`` in an alternate series at ``issue``.""" + """Put ``comic`` in a reprint series at ``issue``.""" reprint = Reprint.objects.create(series_name=series_name, issue=issue) comic.reprints.add(reprint) return reprint @@ -263,7 +263,7 @@ def _book_names(self) -> list[str]: return [book["name"] for book in body["books"]] def test_sorts_numerically_not_lexically(self) -> None: - """#2 sorts before #10 within an alternate series.""" + """#2 sorts before #10 within a reprint series.""" # ``self.comic`` is C1. Issue numbers are deliberately the # reverse of the alternate numbers so a fallback to the regular # issue sort can't accidentally produce the expected order. @@ -304,7 +304,7 @@ def test_suffix_breaks_ties(self) -> None: assert self._book_names() == ["C1", "C2"] def test_number_and_suffix_come_from_the_same_reprint(self) -> None: - """A comic in one alternate series twice keys on one whole issue.""" + """A comic in one reprint series twice keys on one whole issue.""" # C1 is in Crossover at both #10 and #2a, so its key is the # lesser of those two *whole* issues, "2a" — after C2's plain # "#2". Aggregating the number and the suffix separately would @@ -323,7 +323,7 @@ def test_number_and_suffix_come_from_the_same_reprint(self) -> None: assert self._book_names() == ["C2", "C1"] def test_election_is_atomic_across_alternate_series(self) -> None: - """A comic in two alternate series keys on one whole reprint.""" + """A comic in two reprint series keys on one whole reprint.""" # C1 is in "Aaa" at #10 and "Zzz" at #2. Its key must be # (aaa, 10) — the elected reprint whole — never (aaa, 2), the # min series paired with the min number from a different @@ -337,8 +337,8 @@ def test_election_is_atomic_across_alternate_series(self) -> None: assert self._book_names() == ["C2", "C1"] def test_untagged_comic_falls_back_to_its_own_series_and_issue(self) -> None: - """A comic with no alternate series sorts by its real series and issue.""" - # C1's alternate series "Zulu" sorts after C2's real series + """A comic with no reprints sorts by its real series and issue.""" + # C1's reprint series "Zulu" sorts after C2's real series # "Ser", so the fallback has to place C2 first — the opposite of # both the issue order (C1 #1, C2 #50) and the pk order. tagged = self._tag(self.comic, "2", series_name="Zulu") @@ -353,7 +353,7 @@ def test_untagged_comic_falls_back_to_its_own_series_and_issue(self) -> None: def test_fallback_uses_raw_series_name_not_sort_name(self) -> None: """The fallback series segment is the raw name, like alternate names.""" - # Alternate series names sort raw, so the fallback compares + # Reprint series names sort raw, so the fallback compares # ``lower(name)`` too. "cats" sits between "batman, the" (the # article-moved sort_name) and "the batman" (the raw name): # only the raw-name fallback puts the Cats comic first. @@ -367,7 +367,7 @@ def test_fallback_uses_raw_series_name_not_sort_name(self) -> None: assert names == ["C3", "C2"], body def test_alternate_series_name_folds_case(self) -> None: - """Alternate series names compare case-insensitively.""" + """Reprint series names compare case-insensitively.""" # Stored raw, "Zebra" (0x5A) would sort before "apple" (0x61) # under SQLite's binary collation — which a column's NOCASE # collation does not survive being composed into a sort key. @@ -382,7 +382,7 @@ def test_alternate_series_name_folds_case(self) -> None: assert self._book_names() == ["C1", "C2"] def test_groups_by_alternate_series_before_issue(self) -> None: - """With no filter, comics group by alternate series, then by issue.""" + """With no filter, comics group by reprint series, then by issue.""" # Neither the issue order (C1, C2, C3) nor the pk order can # produce this: the two Alpha issues must come out together and # in numeric order, ahead of Zulu. @@ -394,8 +394,8 @@ def test_groups_by_alternate_series_before_issue(self) -> None: assert self._book_names() == ["C3", "C2", "C1"] def test_alternate_series_without_an_issue_uses_the_comics_own(self) -> None: - """Comics in an alternate series with no AlternateNumber keep issue order.""" - # Both alternate series rows carry no issue at all, so the issue + """Comics in a reprint series with no AlternateNumber keep issue order.""" + # Both reprint series rows carry no issue at all, so the issue # segment falls through to the comic's own — without that they # would share one key and land in pk order. first = Reprint.objects.create(series_name="Crossover") @@ -407,7 +407,7 @@ def test_alternate_series_without_an_issue_uses_the_comics_own(self) -> None: assert self._book_names() == ["C2", "C1"] def test_collection_rows_sort_by_their_shared_alternate_issue(self) -> None: - """Series rows sort by the alternate series their children share.""" + """Series rows sort by the reprint series their children share.""" # "Ser" shares Crossover #3 and "Aaa" shares Crossover #10, so # the numeric order is the reverse of both the alphabetical # sort_name order and the lexical label order ("#10" < "#3"). @@ -439,9 +439,9 @@ def test_collection_rows_without_a_shared_alternate_series_use_their_name( self, ) -> None: """A collection whose children disagree sorts by its own name.""" - # "Ser"'s two children share no alternate series, so its + # "Ser"'s two children share no reprints, so its # intersection is empty and it sorts under "ser" — between - # "Aaa"'s alternate series "aaa" and "Zzz"'s "zzz". Without the + # "Aaa"'s reprint series "aaa" and "Zzz"'s "zzz". Without the # fallback the empty key would clump it at one end. self._tag(self.comic, "1", series_name="mmm") self._create_comic("C2", 2) @@ -463,7 +463,7 @@ def test_collection_rows_without_a_shared_alternate_series_use_their_name( class BrowserReprintsCoverSortTestCase(_ReprintsFixtureTestCase): - """The Alternate Series sort outside table view (cover cards, OPDS).""" + """The Reprints sort outside table view (cover cards, OPDS).""" def test_cover_view_sorts_comics_by_label(self) -> None: """Cover view can sort by the M2M label without a missing-alias error.""" @@ -486,9 +486,9 @@ def test_cover_view_sorts_comics_by_label(self) -> None: assert [book["name"] for book in body["books"]] == ["C2", "C1"], body def test_untagged_comic_falls_back_to_series_name(self) -> None: - """A comic with no alternate series sorts by its real series name.""" + """A comic with no reprints sorts by its real series name.""" # All three live in series "Ser" (sort_name "ser"). C1 and C3 - # carry alternate series that bracket it alphabetically, so the + # carry reprint series that bracket it alphabetically, so the # untagged C2 must land *between* them. Without the fallback its # key would be the empty aggregate and it would clump at one end. self.comic.reprints.add(Reprint.objects.create(series_name="zzz")) diff --git a/tests/test_metadata_reprints.py b/tests/test_metadata_reprints.py index 30fbd0a5b..e0a4ffa0a 100644 --- a/tests/test_metadata_reprints.py +++ b/tests/test_metadata_reprints.py @@ -1,5 +1,5 @@ """ -The metadata pane serves alternate series names as ready-to-render chips. +The metadata pane serves reprint series names as ready-to-render chips. ``Reprint.name`` is a property composed from four columns, so the metadata endpoint has to hydrate all of them plus the optional diff --git a/tests/test_reader.py b/tests/test_reader.py index a252ba7af..1ea8921cf 100644 --- a/tests/test_reader.py +++ b/tests/test_reader.py @@ -33,7 +33,7 @@ _HTTP_OK: Final = 200 _TMP_DIR: Final = Path("/tmp/codex.tests.reader") # noqa: S108 _ALT_TMP_DIR: Final = Path("/tmp/codex.tests.reader_alt_series") # noqa: S108 -# The alternate series fixture holds three comics. +# The reprint series fixture holds three comics. _ALT_SERIES_LEN: Final = 3 @@ -156,7 +156,7 @@ def test_scoped_patch_persists_on_series_row(self) -> None: class ReaderAlternateSeriesArcTestCase(TestCase): - """Reading an alternate series (ComicInfo AlternateSeries) as a reading order.""" + """Reading a reprint series (ComicInfo AlternateSeries) as a reading order.""" @override def setUp(self) -> None: @@ -236,7 +236,7 @@ def _alt_arc(self, data: dict) -> tuple[str, dict]: return ids, info def test_alternate_series_is_offered_as_an_arc(self) -> None: - """The reader lists the alternate series among its reading orders.""" + """The reader lists the reprint series among its reading orders.""" data = self._reader(self.c_first) assert "reprints" in data["arcs"], data["arcs"] _, info = self._alt_arc(data) @@ -245,7 +245,7 @@ def test_alternate_series_is_offered_as_an_arc(self) -> None: def test_arc_ids_cover_the_whole_group(self) -> None: """The arc handle is every Reprint row in the series, not just this comic's.""" - # Each issue of an alternate series is its own Reprint row, so a + # Each issue of a reprint series is its own Reprint row, so a # per-comic handle would change from book to book. ids, _ = self._alt_arc(self._reader(self.c_first)) expected = sorted( @@ -264,7 +264,7 @@ def test_books_follow_the_alternate_number(self) -> None: assert data["books"]["next"]["pk"] == self.c_last.pk, data["books"] def test_arc_selection_survives_the_next_book(self) -> None: - """The same ids keep selecting the alternate series on another comic.""" + """The same ids keep selecting the reprint series on another comic.""" ids, _ = self._alt_arc(self._reader(self.c_first)) arc = {"collection": "reprints", "ids": [int(pk) for pk in str(ids).split(",")]} @@ -275,7 +275,7 @@ def test_arc_selection_survives_the_next_book(self) -> None: assert data["arc"]["count"] == _ALT_SERIES_LEN, data["arc"] def test_absent_alternate_series_falls_back(self) -> None: - """Requesting an alternate series a comic isn't in still reads.""" + """Requesting a reprint series a comic isn't in still reads.""" path = _ALT_TMP_DIR / "lonely.cbz" path.touch() lonely = Comic.objects.create( @@ -308,7 +308,7 @@ def test_reader_settings_ignore_the_reprint_scope(self) -> None: assert "global" in scopes, scopes def test_mtime_probe_accepts_the_alternate_series_arc(self) -> None: - """The reader probes every arc it offers, alternate series included.""" + """The reader probes every arc it offers, reprint series included.""" ids, _ = self._alt_arc(self._reader(self.c_first)) collections = json.dumps([{"collection": "reprints", "pks": str(ids)}]) response = self.client.get(f"/api/v4/mtime?collections={collections}") @@ -316,7 +316,7 @@ def test_mtime_probe_accepts_the_alternate_series_arc(self) -> None: assert _v4(response)["maxMtime"], response.content def test_timestamp_updater_restamps_reprints(self) -> None: - """A changed comic advances its alternate series' mtime.""" + """A changed comic advances its reprint series' mtime.""" # Without this the reader's mtime probe never notices a re-import # and an open reader keeps showing stale books. reprint = Reprint.objects.get(comic=self.c_first) diff --git a/tests/test_reprints.py b/tests/test_reprints.py index 33f0887cf..4978cae33 100644 --- a/tests/test_reprints.py +++ b/tests/test_reprints.py @@ -1,5 +1,5 @@ """ -Alternate series names, from the archive to the browser and back out. +Reprint series names, from the archive to the browser and back out. ``tests/files/comicbox-2-example.cbz`` carries two MetronInfo alternate names, one of which supplies only a series ``sort_name``. Importing it diff --git a/tests/test_search_alternate_series.py b/tests/test_search_reprints.py similarity index 99% rename from tests/test_search_alternate_series.py rename to tests/test_search_reprints.py index 91cc53889..95dbf278e 100644 --- a/tests/test_search_alternate_series.py +++ b/tests/test_search_reprints.py @@ -1,5 +1,5 @@ """ -Alternate series names are searchable. +Reprint series names are searchable. comicbox ``reprints`` become :class:`codex.models.named.Reprint` rows and are indexed into a dedicated ``ComicFTS.alternate_series`` column rather