refactor: make the abstract a paragraph instead of a paper field
A template's abstract paragraph (`0 Abstract`) *is* the paper's abstract: it has a position in the document, the heading and typography the template gives it, and the same sentence-by-sentence editing as everything else. `paper.abstract` was a second home for that text — one the document never reads, so a paper could show two different abstracts and the column could drift from the body. The column is gone, from the table, the model, the schemas, the API payloads, the paper form, the list subtitle and the paper page. Nothing else changed. The text already written into it is not gone. Revision a83f5c21d7b6 writes each stored abstract into the paper's body first — one sentence per 。 at the paragraph carrying the abstract heading, appended after anything already there rather than replacing it. A template without such a heading keeps the text too, one position above its first paragraph, where the document renders it under 未设定. Verified against the one paper that had an abstract: 450 characters in, 450 out, identical including `|J| ≤ α · I⁻ᵝ`, split into six sentences in the `0 Abstract` paragraph, still with its own edit button. The smoke test no longer assumes an empty database: it records the paper total before it starts and compares against that, so it can run on a real one.
This commit is contained in:
@@ -224,7 +224,7 @@ def read(paper: Paper) -> PaperRead:
|
||||
else 0
|
||||
),
|
||||
)
|
||||
return PaperRead(**item.model_dump(), abstract=paper.abstract)
|
||||
return PaperRead(**item.model_dump())
|
||||
|
||||
|
||||
# --- document assembly -------------------------------------------------------
|
||||
@@ -425,7 +425,7 @@ def update(
|
||||
|
||||
``values`` holds only the keys the client actually sent — the route derives
|
||||
it from ``model_fields_set``, which is the only way to tell "clear the
|
||||
abstract" apart from "leave it alone"; both arrive as ``None``.
|
||||
author" apart from "leave it alone"; both arrive as ``None``.
|
||||
|
||||
When the template changes, every sentence is re-stamped with the new
|
||||
template id. The sentences themselves keep their positions, which is what
|
||||
|
||||
@@ -25,7 +25,7 @@ without a migration.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from sqlalchemy import ForeignKey, Integer, String, Text, text
|
||||
from sqlalchemy import ForeignKey, Integer, String, text
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from app.db.base import Base
|
||||
@@ -69,9 +69,6 @@ class Paper(TimestampMixin, Base):
|
||||
nullable=True,
|
||||
)
|
||||
|
||||
#: 摘要 — the paper's own abstract, free text.
|
||||
abstract: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
|
||||
#: 作者. Free text, since authorship is written as it will be printed.
|
||||
author: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
||||
|
||||
@@ -91,6 +88,12 @@ class Paper(TimestampMixin, Base):
|
||||
#: 投稿目标期刊.
|
||||
target_journal: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
||||
|
||||
#: There is deliberately no ``abstract`` column. A paper's abstract is a
|
||||
#: paragraph of its body — the template's ``0 Abstract`` field — so it has a
|
||||
#: position in the document, the template's own heading, and the same
|
||||
#: sentence-by-sentence editing as everything else. A column here would be a
|
||||
#: second home for the same text, and the document never reads it.
|
||||
|
||||
#: Eager-loaded with the paper: every read of a paper shows its template
|
||||
#: name, and a lazy load there would be one query per row in the table.
|
||||
template: Mapped["Template | None"] = relationship(lazy="joined")
|
||||
|
||||
@@ -217,7 +217,6 @@ class PaperBase(BaseModel):
|
||||
|
||||
title: str = Field(min_length=1, max_length=255)
|
||||
template_id: int | None = None
|
||||
abstract: str | None = None
|
||||
author: str | None = Field(default=None, max_length=255)
|
||||
status: PaperStatus = "draft"
|
||||
keywords: str | None = Field(default=None, max_length=255)
|
||||
@@ -264,7 +263,6 @@ class PaperUpdate(BaseModel):
|
||||
|
||||
title: str | None = Field(default=None, min_length=1, max_length=255)
|
||||
template_id: int | None = None
|
||||
abstract: str | None = None
|
||||
author: str | None = Field(default=None, max_length=255)
|
||||
status: PaperStatus | None = None
|
||||
keywords: str | None = Field(default=None, max_length=255)
|
||||
@@ -317,9 +315,14 @@ class PaperListItem(BaseModel):
|
||||
|
||||
|
||||
class PaperRead(PaperListItem):
|
||||
"""A paper with everything the table does not need."""
|
||||
"""A paper as its own page needs it.
|
||||
|
||||
abstract: str | None
|
||||
Kept separate from :class:`PaperListItem` even though it currently adds no
|
||||
field, because the two are different contracts: the table's row and the
|
||||
page's subject. The paper's abstract is not here — it is a paragraph of the
|
||||
document (see ``app.crud.paper.build_document``), not a property of the
|
||||
paper.
|
||||
"""
|
||||
|
||||
|
||||
class PaperDocumentRead(BaseModel):
|
||||
|
||||
Reference in New Issue
Block a user