byceps/services/page/dbmodels.py
"""
byceps.services.page.dbmodels
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Pages of database-stored content. Can contain HTML and template engine
syntax.
:Copyright: 2014-2024 Jochen Kupperschmidt
:License: Revised BSD (see `LICENSE` file for details)
"""
from datetime import datetime
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.orm import Mapped, mapped_column, relationship
from byceps.database import db
from byceps.services.language.dbmodels import DbLanguage
from byceps.services.site.models import SiteID
from byceps.services.site_navigation.models import NavMenuID
from byceps.services.site_navigation.dbmodels import DbNavMenu
from byceps.services.user.dbmodels.user import DbUser
from byceps.services.user.models.user import UserID
from byceps.util.uuid import generate_uuid7
from .models import PageID, PageVersionID
class DbPage(db.Model):
"""A content page.
Any page is expected to have at least one version (the initial one).
"""
__tablename__ = 'pages'
__table_args__ = (
db.UniqueConstraint('site_id', 'name', 'language_code'),
db.UniqueConstraint('site_id', 'language_code', 'url_path'),
)
id: Mapped[PageID] = mapped_column(
db.Uuid, default=generate_uuid7, primary_key=True
)
site_id: Mapped[SiteID] = mapped_column(
db.UnicodeText, db.ForeignKey('sites.id'), index=True
)
name: Mapped[str] = mapped_column(db.UnicodeText, index=True)
language_code: Mapped[str] = mapped_column(
db.UnicodeText,
db.ForeignKey('languages.code'),
index=True,
)
language: Mapped[DbLanguage] = relationship(DbLanguage)
url_path: Mapped[str] = mapped_column(db.UnicodeText, index=True)
published: Mapped[bool]
nav_menu_id: Mapped[NavMenuID | None] = mapped_column(
db.Uuid, db.ForeignKey('site_nav_menus.id')
)
nav_menu: Mapped[DbNavMenu] = relationship(DbNavMenu)
current_version = association_proxy(
'current_version_association', 'version'
)
def __init__(
self, site_id: SiteID, name: str, language_code: str, url_path: str
) -> None:
self.site_id = site_id
self.name = name
self.language_code = language_code
self.url_path = url_path
self.published = False
class DbPageVersion(db.Model):
"""A snapshot of a page at a certain time."""
__tablename__ = 'page_versions'
id: Mapped[PageVersionID] = mapped_column(
db.Uuid, default=generate_uuid7, primary_key=True
)
page_id: Mapped[PageID] = mapped_column(
db.Uuid, db.ForeignKey('pages.id'), index=True
)
page: Mapped[DbPage] = relationship(DbPage)
created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)
creator_id: Mapped[UserID] = mapped_column(
db.Uuid, db.ForeignKey('users.id')
)
creator: Mapped[DbUser] = relationship(DbUser)
title: Mapped[str] = mapped_column(db.UnicodeText)
head: Mapped[str | None] = mapped_column(db.UnicodeText)
body: Mapped[str] = mapped_column(db.UnicodeText)
def __init__(
self,
page: DbPage,
creator_id: UserID,
title: str,
head: str | None,
body: str,
) -> None:
self.page = page
self.creator_id = creator_id
self.title = title
self.head = head
self.body = body
@property
def is_current(self) -> bool:
"""Return `True` if this version is the current version of the
page it belongs to.
"""
return self.id == self.page.current_version.id
class DbCurrentPageVersionAssociation(db.Model):
__tablename__ = 'page_current_versions'
page_id: Mapped[PageID] = mapped_column(
db.Uuid, db.ForeignKey('pages.id'), primary_key=True
)
page: Mapped[DbPage] = relationship(
DbPage, backref=db.backref('current_version_association', uselist=False)
)
version_id: Mapped[PageVersionID] = mapped_column(
db.Uuid, db.ForeignKey('page_versions.id'), unique=True
)
version: Mapped[DbPageVersion] = relationship(DbPageVersion)
def __init__(self, page: DbPage, version: DbPageVersion) -> None:
self.page = page
self.version = version