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