refactor: prefix every table with its module
Two tables were named after the concept they came from rather than the module
they belong to, so the schema read as if the template tables were part of the
paper module. Renamed (data preserved, `RENAME TABLE` moves rows in place):
paper_template -> template the 模板 module
section_field -> template_field_library the 字段库 the 模板 module owns
The paper tables and `template_field` already followed the rule. The rename
carries through everything that named a module:
models Template, TemplateField, TemplateFieldLibrary
schemas Template*, TemplateFieldLibrary*
crud app/crud/template.py, app/crud/template_field_library.py
API /template-field-library (was /section-fields); handlers are now
named after library entries, which removes the ambiguity with
TemplateField — a placement, a different thing entirely
client src/api/templateFieldLibrary.ts
`paper_template_filed_sort` is deliberately untouched: it is a column of the
paper module, spelled as the feature was specified.
TiDB v8.5 with tidb_enable_foreign_key on — as this cluster runs — enforces
foreign keys rather than ignoring them, so the docs' "TiDB does not enforce
foreign keys" was wrong. Corrected, with what actually follows from it: the
rename was rehearsed (RENAME TABLE carries a referencing constraint along), the
API keeps checking first so a violation names the row instead of surfacing a
driver error, and the ORM cascades stay so behaviour does not depend on a
cluster setting.
Revision f27a1c6d9e04 verified both ways; 40 smoke checks, type-check and build
all pass.
This commit is contained in:
@@ -5,6 +5,6 @@ models. They own the commit: a route calls one function and gets back either a
|
||||
persisted object or ``None``.
|
||||
"""
|
||||
|
||||
from app.crud import paper, paper_template, section_field
|
||||
from app.crud import paper, template, template_field_library
|
||||
|
||||
__all__ = ["paper", "paper_template", "section_field"]
|
||||
__all__ = ["paper", "template", "template_field_library"]
|
||||
|
||||
@@ -36,7 +36,7 @@ from app.models import (
|
||||
Paper,
|
||||
PaperSentence,
|
||||
PaperSentenceReference,
|
||||
PaperTemplate,
|
||||
Template,
|
||||
TemplateField,
|
||||
)
|
||||
from app.schemas.paper import (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Data access for paper templates and their ordered field selections."""
|
||||
"""Data access for templates and their ordered field selections."""
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
@@ -6,8 +6,8 @@ from sqlalchemy import func, or_, select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.crud.filters import LIKE_ESCAPE, like_pattern
|
||||
from app.models import PaperTemplate, SectionField, TemplateField
|
||||
from app.schemas.paper_template import PaperTemplateListItem, TemplateFieldInput
|
||||
from app.models import Template, TemplateFieldLibrary, TemplateField
|
||||
from app.schemas.template import TemplateListItem, TemplateFieldInput
|
||||
|
||||
|
||||
def _conditions(keyword: str | None) -> list:
|
||||
@@ -17,8 +17,8 @@ def _conditions(keyword: str | None) -> list:
|
||||
pattern = like_pattern(keyword)
|
||||
return [
|
||||
or_(
|
||||
PaperTemplate.name.like(pattern, escape=LIKE_ESCAPE),
|
||||
PaperTemplate.abstract.like(pattern, escape=LIKE_ESCAPE),
|
||||
Template.name.like(pattern, escape=LIKE_ESCAPE),
|
||||
Template.abstract.like(pattern, escape=LIKE_ESCAPE),
|
||||
)
|
||||
]
|
||||
|
||||
@@ -27,8 +27,8 @@ def _field_count_column():
|
||||
"""A correlated ``COUNT`` of the template's placement rows."""
|
||||
return (
|
||||
select(func.count(TemplateField.id))
|
||||
.where(TemplateField.template_id == PaperTemplate.id)
|
||||
.correlate(PaperTemplate)
|
||||
.where(TemplateField.template_id == Template.id)
|
||||
.correlate(Template)
|
||||
.scalar_subquery()
|
||||
)
|
||||
|
||||
@@ -39,7 +39,7 @@ def list_templates(
|
||||
keyword: str | None = None,
|
||||
page: int = 1,
|
||||
page_size: int = 20,
|
||||
) -> tuple[list[PaperTemplateListItem], int]:
|
||||
) -> tuple[list[TemplateListItem], int]:
|
||||
"""Return one page of templates with their field counts, plus the total.
|
||||
|
||||
The outline itself is left out: a table row needs the count, not the rows.
|
||||
@@ -48,19 +48,19 @@ def list_templates(
|
||||
conditions = _conditions(keyword)
|
||||
|
||||
total = db.scalar(
|
||||
select(func.count(PaperTemplate.id)).where(*conditions)
|
||||
select(func.count(Template.id)).where(*conditions)
|
||||
) or 0
|
||||
|
||||
stmt = (
|
||||
select(PaperTemplate, _field_count_column().label("field_count"))
|
||||
select(Template, _field_count_column().label("field_count"))
|
||||
.where(*conditions)
|
||||
.order_by(PaperTemplate.updated_at.desc(), PaperTemplate.id.desc())
|
||||
.order_by(Template.updated_at.desc(), Template.id.desc())
|
||||
.offset((page - 1) * page_size)
|
||||
.limit(page_size)
|
||||
)
|
||||
|
||||
items = [
|
||||
PaperTemplateListItem(
|
||||
TemplateListItem(
|
||||
id=template.id,
|
||||
name=template.name,
|
||||
abstract=template.abstract,
|
||||
@@ -73,21 +73,21 @@ def list_templates(
|
||||
return items, total
|
||||
|
||||
|
||||
def get(db: Session, template_id: int) -> PaperTemplate | None:
|
||||
def get(db: Session, template_id: int) -> Template | None:
|
||||
"""Return one template with its outline already loaded, or ``None``.
|
||||
|
||||
``items`` (ordered by ``sort``) and each item's ``field`` are both
|
||||
configured for eager loading on the relationships, so this is a fixed
|
||||
number of queries rather than one per field.
|
||||
"""
|
||||
return db.get(PaperTemplate, template_id)
|
||||
return db.get(Template, template_id)
|
||||
|
||||
|
||||
def name_taken(db: Session, name: str, *, exclude_id: int | None = None) -> bool:
|
||||
"""Whether another template already uses ``name``."""
|
||||
stmt = select(func.count(PaperTemplate.id)).where(PaperTemplate.name == name)
|
||||
stmt = select(func.count(Template.id)).where(Template.name == name)
|
||||
if exclude_id is not None:
|
||||
stmt = stmt.where(PaperTemplate.id != exclude_id)
|
||||
stmt = stmt.where(Template.id != exclude_id)
|
||||
return bool(db.scalar(stmt))
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ def missing_field_ids(db: Session, field_ids: Sequence[int]) -> list[int]:
|
||||
if not wanted:
|
||||
return []
|
||||
found = set(
|
||||
db.scalars(select(SectionField.id).where(SectionField.id.in_(wanted))).all()
|
||||
db.scalars(select(TemplateFieldLibrary.id).where(TemplateFieldLibrary.id.in_(wanted))).all()
|
||||
)
|
||||
return sorted(wanted - found)
|
||||
|
||||
@@ -117,9 +117,9 @@ def create(
|
||||
name: str,
|
||||
abstract: str | None,
|
||||
fields: Sequence[TemplateFieldInput],
|
||||
) -> PaperTemplate:
|
||||
) -> Template:
|
||||
"""Insert a template together with its ordered selection."""
|
||||
template = PaperTemplate(name=name, abstract=abstract)
|
||||
template = Template(name=name, abstract=abstract)
|
||||
template.items = _build_items(fields)
|
||||
db.add(template)
|
||||
db.commit()
|
||||
@@ -129,13 +129,13 @@ def create(
|
||||
|
||||
def update(
|
||||
db: Session,
|
||||
template: PaperTemplate,
|
||||
template: Template,
|
||||
*,
|
||||
name: str | None = None,
|
||||
abstract: str | None = None,
|
||||
abstract_provided: bool = False,
|
||||
fields: Sequence[TemplateFieldInput] | None = None,
|
||||
) -> PaperTemplate:
|
||||
) -> Template:
|
||||
"""Apply a partial update. ``fields=None`` leaves the selection untouched.
|
||||
|
||||
``abstract_provided`` distinguishes "clear the abstract" from "leave it" —
|
||||
@@ -159,7 +159,7 @@ def update(
|
||||
return template
|
||||
|
||||
|
||||
def delete(db: Session, template: PaperTemplate) -> None:
|
||||
def delete(db: Session, template: Template) -> None:
|
||||
"""Delete a template and its placement rows."""
|
||||
db.delete(template)
|
||||
db.commit()
|
||||
@@ -171,7 +171,7 @@ def delete_many(db: Session, template_ids: Sequence[int]) -> int:
|
||||
return 0
|
||||
templates = list(
|
||||
db.scalars(
|
||||
select(PaperTemplate).where(PaperTemplate.id.in_(list(template_ids)))
|
||||
select(Template).where(Template.id.in_(list(template_ids)))
|
||||
).all()
|
||||
)
|
||||
for template in templates:
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Data access for the reusable section-field library (字段管理)."""
|
||||
"""Data access for the reusable template-field library (字段管理)."""
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
@@ -6,8 +6,11 @@ from sqlalchemy import Select, func, select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.crud.filters import LIKE_ESCAPE, like_pattern
|
||||
from app.models import SectionField, TemplateField
|
||||
from app.schemas.section_field import SectionFieldCreate, SectionFieldUpdate
|
||||
from app.models import TemplateField, TemplateFieldLibrary
|
||||
from app.schemas.template_field_library import (
|
||||
TemplateFieldLibraryCreate,
|
||||
TemplateFieldLibraryUpdate,
|
||||
)
|
||||
|
||||
|
||||
def _conditions(keyword: str | None, level: int | None) -> list:
|
||||
@@ -15,10 +18,10 @@ def _conditions(keyword: str | None, level: int | None) -> list:
|
||||
conditions = []
|
||||
if keyword:
|
||||
conditions.append(
|
||||
SectionField.name.like(like_pattern(keyword), escape=LIKE_ESCAPE)
|
||||
TemplateFieldLibrary.name.like(like_pattern(keyword), escape=LIKE_ESCAPE)
|
||||
)
|
||||
if level is not None:
|
||||
conditions.append(SectionField.level == level)
|
||||
conditions.append(TemplateFieldLibrary.level == level)
|
||||
return conditions
|
||||
|
||||
|
||||
@@ -30,7 +33,7 @@ def _ordered(stmt: Select) -> Select:
|
||||
ordered by ``name``: names carry hand-written numbering ("1.", "10.",
|
||||
"2."), and string ordering would scramble it.
|
||||
"""
|
||||
return stmt.order_by(SectionField.level.asc(), SectionField.id.asc())
|
||||
return stmt.order_by(TemplateFieldLibrary.level.asc(), TemplateFieldLibrary.id.asc())
|
||||
|
||||
|
||||
def list_fields(
|
||||
@@ -40,16 +43,16 @@ def list_fields(
|
||||
level: int | None = None,
|
||||
page: int = 1,
|
||||
page_size: int = 20,
|
||||
) -> tuple[list[SectionField], int]:
|
||||
) -> tuple[list[TemplateFieldLibrary], int]:
|
||||
"""Return one page of the field library, plus the unpaged total."""
|
||||
conditions = _conditions(keyword, level)
|
||||
|
||||
total = db.scalar(
|
||||
select(func.count(SectionField.id)).where(*conditions)
|
||||
select(func.count(TemplateFieldLibrary.id)).where(*conditions)
|
||||
) or 0
|
||||
|
||||
stmt = _ordered(
|
||||
select(SectionField)
|
||||
select(TemplateFieldLibrary)
|
||||
.where(*conditions)
|
||||
.offset((page - 1) * page_size)
|
||||
.limit(page_size)
|
||||
@@ -57,16 +60,16 @@ def list_fields(
|
||||
return list(db.scalars(stmt).all()), total
|
||||
|
||||
|
||||
def get(db: Session, field_id: int) -> SectionField | None:
|
||||
def get(db: Session, field_id: int) -> TemplateFieldLibrary | None:
|
||||
"""Return one field, or ``None``."""
|
||||
return db.get(SectionField, field_id)
|
||||
return db.get(TemplateFieldLibrary, field_id)
|
||||
|
||||
|
||||
def get_many(db: Session, field_ids: Sequence[int]) -> list[SectionField]:
|
||||
def get_many(db: Session, field_ids: Sequence[int]) -> list[TemplateFieldLibrary]:
|
||||
"""Return every field whose id is in ``field_ids`` (missing ids ignored)."""
|
||||
if not field_ids:
|
||||
return []
|
||||
stmt = select(SectionField).where(SectionField.id.in_(list(field_ids)))
|
||||
stmt = select(TemplateFieldLibrary).where(TemplateFieldLibrary.id.in_(list(field_ids)))
|
||||
return list(db.scalars(stmt).all())
|
||||
|
||||
|
||||
@@ -89,16 +92,20 @@ def usage_counts(db: Session, field_ids: Sequence[int]) -> dict[int, int]:
|
||||
return {field_id: count for field_id, count in db.execute(stmt).all()}
|
||||
|
||||
|
||||
def create(db: Session, data: SectionFieldCreate) -> SectionField:
|
||||
def create(db: Session, data: TemplateFieldLibraryCreate) -> TemplateFieldLibrary:
|
||||
"""Insert a field."""
|
||||
field = SectionField(**data.model_dump())
|
||||
field = TemplateFieldLibrary(**data.model_dump())
|
||||
db.add(field)
|
||||
db.commit()
|
||||
db.refresh(field)
|
||||
return field
|
||||
|
||||
|
||||
def update(db: Session, field: SectionField, data: SectionFieldUpdate) -> SectionField:
|
||||
def update(
|
||||
db: Session,
|
||||
field: TemplateFieldLibrary,
|
||||
data: TemplateFieldLibraryUpdate,
|
||||
) -> TemplateFieldLibrary:
|
||||
"""Apply a partial update to a field.
|
||||
|
||||
``exclude_unset`` is what makes PATCH semantics work: a key the client did
|
||||
@@ -112,7 +119,7 @@ def update(db: Session, field: SectionField, data: SectionFieldUpdate) -> Sectio
|
||||
return field
|
||||
|
||||
|
||||
def delete(db: Session, field: SectionField) -> None:
|
||||
def delete(db: Session, field: TemplateFieldLibrary) -> None:
|
||||
"""Delete a field. Callers must check :func:`usage_counts` first."""
|
||||
db.delete(field)
|
||||
db.commit()
|
||||
Reference in New Issue
Block a user