"""The reusable template-field library (字段库). A *library entry* is one heading a paper can contain — "1. Introduction", "2.1 Dataset", "0 Abstract". Fields live in this one global library and are never owned by a template. Why there is no ``parent_id`` ----------------------------- A tree would make a field usable under exactly one parent, so a level-2 heading such as "Background" could not sit under both "1. Introduction" and "2. Related Work". Hierarchy is therefore expressed only by :attr:`TemplateFieldLibrary.level` (1, 2, 3 ...), which is a *rendering hint* — it drives indentation and numbering semantics in the UI — while a field stays free to be attached to any number of templates and any number of parents within them. The number that the reader sees is part of :attr:`name` and is written by the user ("1. Introduction", "0 Abstract"). Nothing derives or rewrites it. Ordering inside a template is *not* stored here: it lives in ``template_field.sort``. This table has no ``sort`` column on purpose, so that one library field can occupy a different position in every template. Naming: the table is ``template_field_library`` and one row is one entry of it — the class is named after the table, as everywhere else in this package. The library is global, but it belongs to the 模板 module: it is the raw material a template is built from, and it is managed from the same menu. """ from decimal import Decimal from sqlalchemy import Integer, Numeric, String, text from sqlalchemy.orm import Mapped, mapped_column from app.db.base import Base from app.models.mixins import TimestampMixin class TemplateFieldLibrary(TimestampMixin, Base): """A single reusable heading, with the typography it should render in.""" __tablename__ = "template_field_library" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) #: Display name, numbering included. Stored and rendered verbatim. name: Mapped[str] = mapped_column(String(255), nullable=False) #: Heading depth: 1 for "1.", 2 for "1.1", 3 for "1.1.1". Rendering hint #: only — it does not link fields to each other. level: Mapped[int] = mapped_column( Integer, nullable=False, default=1, server_default=text("1"), ) #: Font size in points. ``Numeric`` rather than ``Integer`` because the #: conventional Chinese sizes are fractional (五号 = 10.5pt, #: 小四 = 12pt). font_size: Mapped[Decimal] = mapped_column( Numeric(4, 1), nullable=False, default=Decimal("12.0"), server_default=text("12.0"), ) #: Font colour as ``#RRGGBB`` — the canonical RGB encoding. The API #: normalises ``rgb(20, 30, 40)``, ``#abc`` and bare ``aabbcc`` to it on #: write, so the column always holds one comparable format. font_color: Mapped[str] = mapped_column( String(7), nullable=False, default="#000000", server_default=text("'#000000'"), ) def __repr__(self) -> str: # pragma: no cover - debugging aid return ( f"" )