backend: add the paper authoring schema, API, and smoke test
A paper is a document written against a template. Its structure is never copied into it: `paper` points at a template and the outline is read live on every render, so switching `template_id` re-shapes the whole document in one write. Sentences are addressed by *position* rather than by a template row id: `paper_sentence.paper_template_filed_sort` holds the sort of the placement the sentence belongs to, and `sort` holds its place inside that paragraph. That indirection is what makes a template switch non-destructive — a sentence that remembers "position 7" lands on whatever the new template puts at position 7 — and it is why a paragraph is any position either the template or the content mentions: the structure survives with no content, and content whose position the template does not define is still rendered, in order, under 未设定. Citations are a table rather than a column, since one sentence may quote several references. `quote` is required — a citation that does not say what it quotes is refused with 422 — while `reference_id` is a plain nullable integer with no foreign key, because the reference library does not exist yet. Deleting a template a paper is written against is refused with 409 and a count, matching how the field library refuses to drop a field still in use. scripts/smoke_papers.py walks the whole loop — create, empty structure, write a paragraph with citations, switch templates, keep unmatched positions, move a paragraph, delete — in 40 checks, and cleans up after itself.
This commit is contained in:
@@ -3,14 +3,34 @@
|
||||
Importing this package registers every model on ``Base.metadata``, which is
|
||||
what ``alembic revision --autogenerate`` inspects. A new model therefore has to
|
||||
be added to the imports below, not only to its own module.
|
||||
|
||||
The import order here is for readability only: relationships are declared by
|
||||
name and resolved through the SQLAlchemy registry after every module has been
|
||||
imported, so a cycle between two model modules is not a problem.
|
||||
"""
|
||||
|
||||
from app.models.mixins import TimestampMixin
|
||||
from app.models.paper import (
|
||||
PAPER_STATUSES,
|
||||
STATUS_DONE,
|
||||
STATUS_DRAFT,
|
||||
STATUS_WRITING,
|
||||
Paper,
|
||||
)
|
||||
from app.models.paper_sentence import PaperSentence
|
||||
from app.models.paper_sentence_reference import PaperSentenceReference
|
||||
from app.models.paper_template import PaperTemplate
|
||||
from app.models.section_field import SectionField
|
||||
from app.models.template_field import TemplateField
|
||||
|
||||
__all__ = [
|
||||
"PAPER_STATUSES",
|
||||
"STATUS_DONE",
|
||||
"STATUS_DRAFT",
|
||||
"STATUS_WRITING",
|
||||
"Paper",
|
||||
"PaperSentence",
|
||||
"PaperSentenceReference",
|
||||
"PaperTemplate",
|
||||
"SectionField",
|
||||
"TemplateField",
|
||||
|
||||
Reference in New Issue
Block a user