From 89c20338dd26002de675c9ba8cf16197901e757d Mon Sep 17 00:00:00 2001 From: Julien Aldon Date: Thu, 23 Apr 2026 18:17:31 +0200 Subject: [PATCH] add(templates): base models and dto for girasol backend --- backend/.gitignore | 176 +++++++++++++++++++++++++++++++++ backend/README.md | 6 ++ backend/pyproject.toml | 72 ++++++++++++++ backend/src/__about__.py | 6 ++ backend/src/__init__.py | 3 + backend/src/events/dto.py | 95 ++++++++++++++++++ backend/src/events/models.py | 145 +++++++++++++++++++++++++++ backend/src/files/dto.py | 27 +++++ backend/src/files/models.py | 31 ++++++ backend/src/links/dto.py | 30 ++++++ backend/src/links/models.py | 36 +++++++ backend/src/main.py | 3 + backend/src/news/dto.py | 34 +++++++ backend/src/news/models.py | 32 ++++++ backend/src/sitewide/dto.py | 50 ++++++++++ backend/src/sitewide/models.py | 55 +++++++++++ backend/src/sponsors/dto.py | 29 ++++++ backend/src/sponsors/models.py | 28 ++++++ 18 files changed, 858 insertions(+) create mode 100644 backend/.gitignore create mode 100644 backend/README.md create mode 100644 backend/pyproject.toml create mode 100644 backend/src/__about__.py create mode 100644 backend/src/__init__.py create mode 100644 backend/src/events/dto.py create mode 100644 backend/src/events/models.py create mode 100644 backend/src/files/dto.py create mode 100644 backend/src/files/models.py create mode 100644 backend/src/links/dto.py create mode 100644 backend/src/links/models.py create mode 100644 backend/src/main.py create mode 100644 backend/src/news/dto.py create mode 100644 backend/src/news/models.py create mode 100644 backend/src/sitewide/dto.py create mode 100644 backend/src/sitewide/models.py create mode 100644 backend/src/sponsors/dto.py create mode 100644 backend/src/sponsors/models.py diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 0000000..ad4a1f1 --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,176 @@ +# Created by https://www.toptal.com/developers/gitignore/api/python +# Edit at https://www.toptal.com/developers/gitignore?templates=python + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +### Python Patch ### +# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration +poetry.toml + +# ruff +.ruff_cache/ + +# LSP config files +pyrightconfig.json + +# End of https://www.toptal.com/developers/gitignore/api/python diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 0000000..149c933 --- /dev/null +++ b/backend/README.md @@ -0,0 +1,6 @@ +# FastAPI Backend + +```sh +hatch shell +fastapi dev +``` \ No newline at end of file diff --git a/backend/pyproject.toml b/backend/pyproject.toml new file mode 100644 index 0000000..e80d220 --- /dev/null +++ b/backend/pyproject.toml @@ -0,0 +1,72 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "backend" +dynamic = ["version"] +description = '' +readme = "README.md" +requires-python = ">=3.11" +license = "MIT" +keywords = [] +authors = [ + { name = "Julien Aldon", email = "julien.aldon@wanadoo.fr" }, +] +classifiers = [ + "Development Status :: 4 - Beta", + "Programming Language :: Python", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", +] +dependencies = [ + "fastapi[standard]", + "sqlmodel", + "psycopg2-binary", + "PyJWT", + "cryptography", + "pytest", + "pytest-cov", + "pytest-mock", + "pylint", +] + +[project.urls] +Documentation = "" +Issues = "" +Source = "" + +[tool.hatch.version] +path = "src/__about__.py" + +[tool.hatch.envs.types] +extra-dependencies = [ + "mypy>=1.0.0", +] +[tool.hatch.envs.types.scripts] +check = "mypy --install-types --non-interactive {args:src tests}" + +[tool.coverage.run] +source_pkgs = ["backend", "tests"] +branch = true +parallel = true +omit = [ + "src/__about__.py", +] + +[tool.coverage.paths] +backend = ["src", "*/backend/src/"] +tests = ["tests", "*/backend/tests"] + +[tool.coverage.report] +exclude_lines = [ + "no cov", + "if __name__ == .__main__.:", + "if TYPE_CHECKING:", +] + +[tool.hatch.build.targets.wheel] +packages = ["src"] +include = ["src/**/*.py"] \ No newline at end of file diff --git a/backend/src/__about__.py b/backend/src/__about__.py new file mode 100644 index 0000000..eacc950 --- /dev/null +++ b/backend/src/__about__.py @@ -0,0 +1,6 @@ +"""About +""" +# SPDX-FileCopyrightText: 2026-present Julien Aldon +# +# SPDX-License-Identifier: MIT +__version__ = "0.0.1" diff --git a/backend/src/__init__.py b/backend/src/__init__.py new file mode 100644 index 0000000..e9a63bc --- /dev/null +++ b/backend/src/__init__.py @@ -0,0 +1,3 @@ +# SPDX-FileCopyrightText: 2026-present Julien Aldon +# +# SPDX-License-Identifier: MIT diff --git a/backend/src/events/dto.py b/backend/src/events/dto.py new file mode 100644 index 0000000..aae754c --- /dev/null +++ b/backend/src/events/dto.py @@ -0,0 +1,95 @@ +"""Event module DTO +""" +import datetime +import uuid +from typing import List, Optional + +from sqlmodel import Field, SQLModel +from src.events.models import BaseEdition, BaseSeries, BaseTag +from src.files.dto import ReadFile + + +class CreateSeries(BaseSeries): + """CreateSeries DTO + """ + logotype_id: uuid.UUID = Field() + + +class UpdateSeries(SQLModel): + """UpdateSeries DTO + """ + title: Optional[str] = None + color_light: Optional[str] = None + color_dark: Optional[str] = None + color_strong: Optional[str] = None + + +class ReadSeriesLight(BaseSeries): + """Lighter version of ReadSeries for nested usage. + """ + id: uuid.UUID + logotype: ReadFile + + +class ReadSeries(BaseSeries): + """ReadSeries DTO + """ + id: uuid.UUID + logotype: ReadFile + editions: List['ReadEdition'] = Field(default_factory=list) + + +class CreateEdition(BaseEdition): + """CreateEdition DTO + """ + flyer_id: uuid.UUID = Field() + hero_id: uuid.UUID = Field() + series_id: uuid.UUID = Field() + + +class UpdateEdition(SQLModel): + """UpdateEdition DTO + """ + title: Optional[str] = None + long_description: Optional[dict] = None + short_description: Optional[str] = None + start_date: Optional[datetime.datetime] = None + end_date: Optional[datetime.datetime] = None + contact_information: Optional[dict] = None + + +class ReadEdition(BaseEdition): + """ReadEdition DTO + """ + id: uuid.UUID + sponsors: List['ReadSponsor'] = Field(default_factory=list) + flyer: ReadFile + hero: ReadFile + series: ReadSeriesLight + tags: List['ReadTagLight'] = Field(default_factory=list) + gallery: List[ReadFile] = Field(default_factory=list) + + +class CreateTag(BaseTag): + """CreateTag DTO + """ + + +class UpdateTag(SQLModel): + """UpdateTag DTO + """ + title: Optional[str] = None + color: Optional[str] = None + + +class ReadTagLight(BaseTag): + """Lighter version of ReadTag for nested usage. + """ + id: uuid.UUID + + +class ReadTag(BaseTag): + """ReadTag DTO + """ + id: uuid.UUID + editions: List['ReadEdition'] = Field(default_factory=list) diff --git a/backend/src/events/models.py b/backend/src/events/models.py new file mode 100644 index 0000000..f3c1f06 --- /dev/null +++ b/backend/src/events/models.py @@ -0,0 +1,145 @@ +"""Event module models +""" +import datetime +import uuid + +from sqlalchemy.dialects.postgresql import JSONB +from sqlmodel import Field, Relationship, SQLModel + + +class BaseSeries(SQLModel): + """Base model for a series. + + Attributes: + title (str): Name of the series. + color_light (str): Light theme color. + color_dark (str): Dark theme color. + color_strong (str): Accent / Primary color. + """ + title: str + color_light: str + color_dark: str + color_strong: str + + +class Series(BaseSeries, table=True): + """Database model representing a series of editions. + + A series of events under the same branding + + Attributes: + id (uuid.UUID): unique identifier of the series. + logotype_id (uuid.UUID): Reference to the logotype file. + editions (list['Edition']): List of editions belonging to this series. + """ + id: uuid.UUID | None = Field( + default=None, default_factory=uuid.uuid4, primary_key=True) + logotype_id: uuid.UUID = Field(foreign_key="file.id") + editions: list['Edition'] = Relationship(back_populates='series') + + +class BaseEdition(SQLModel): + """Base model for an edition + + Attributes: + title (str): Name of the edition. + long_description (dict): Description block of the edition. + short_description (str): Description of the edition for SEO. + start_date (datetime.datetime): Start date of the edition. + end_date (datetime.datetime): End date of the edition. + contact_information (dict): Contact information block for contact page. + """ + title: str + long_description: dict = Field(sa_type=JSONB, nullable=False) + short_description: str + start_date: datetime.datetime + end_date: datetime.datetime + contact_information: dict = Field(sa_type=JSONB, nullable=False) + + +class EditionTagLink(SQLModel, table=True): + """Association table linking editions and tags + + Represents a many-to-many relationship between editions and tags. + """ + edition_id: uuid.UUID = Field(foreign_key="edition.id", primary_key=True) + tag_id: uuid.UUID = Field(foreign_key="tag.id", primary_key=True) + + +class EditionFileLink(SQLModel, table=True): + """Association table linking editions and files + + Represents a many-to-many relationship between editions and files. + """ + edition_id: uuid.UUID = Field(foreign_key="edition.id", primary_key=True) + file_id: uuid.UUID = Field(foreign_key="file.id", primary_key=True) + + +class EditionSponsorLink(SQLModel, table=True): + """Association table linking editions and sponsors. + + Represents a many-to-many relationship between editions and sponsors. + """ + edition_id: uuid.UUID = Field(foreign_key="edition.id", primary_key=True) + sponsor_id: uuid.UUID = Field(foreign_key="sponsor.id", primary_key=True) + + +class Edition(BaseEdition, table=True): + """Database model representing an A single iteration of an event. + + An edition will have it's own page to display its informations. + + Attributes: + id (uuid.UUID): unique identifier of the edition. + series_id (uuid.UUID): Reference to edition's series + flyer_id (uuid.UUID): Reference to the flyer file. + Image used as flyer under herobanner in edition Page. + hero_id (uuid.UUID): Reference to the hero file. + Image used as herobanner for edition Page. + sponsors (List['Sponsor']): List of sponsors belonging to this edition. + series (Series): Series Object reference. + tags (List('Tag')): List of tags associated to this edition. + tags (List('File')): List of Images associated to this edition. + """ + id: uuid.UUID | None = Field( + default=None, default_factory=uuid.uuid4, primary_key=True) + series_id: uuid.UUID = Field(foreign_key="series.id") + flyer_id: uuid.UUID = Field(foreign_key="file.id") + hero_id: uuid.UUID = Field(foreign_key="file.id") + sponsors: list['Sponsor'] = Relationship( + cascade_delete=True, + link_model=EditionSponsorLink, + ) + series: Series = Relationship(back_populates="editions") + tags: list['Tag'] = Relationship( + link_model=EditionTagLink + ) + gallery: list['File'] = Relationship( + link_model=EditionFileLink + ) + + +class BaseTag(SQLModel): + """Base model for a Tag + + Attributes: + title (str): Name of the tag. + color (str): Color of the tag. + """ + title: str + color: str + + +class Tag(BaseTag, table=True): + """Database model representing a Tag + + A tag is a way to group events with a mutual interests. + + Attributes: + id (uudi.UUID): Unique identifier of a tag + """ + id: uuid.UUID | None = Field( + default=None, default_factory=uuid.uuid4, primary_key=True) + editions: list['Edition'] = Relationship( + link_model=EditionTagLink + ) diff --git a/backend/src/files/dto.py b/backend/src/files/dto.py new file mode 100644 index 0000000..918afba --- /dev/null +++ b/backend/src/files/dto.py @@ -0,0 +1,27 @@ +"""Files module DTO +""" +import uuid +from typing import Optional + +from sqlmodel import SQLModel +from src.files.models import BaseFile + + +class CreateFile(BaseFile): + """CreateFile DTO + """ + + +class UpdateFile(SQLModel): + """UpdateFile DTO + """ + name: Optional[str] = None + type: Optional[str] = None + + +class ReadFile(BaseFile): + """ReadFile DTO + """ + id: uuid.UUID + path_full: str + path_preview: str | None = None diff --git a/backend/src/files/models.py b/backend/src/files/models.py new file mode 100644 index 0000000..f372889 --- /dev/null +++ b/backend/src/files/models.py @@ -0,0 +1,31 @@ +"""File module models +""" +import uuid + +from sqlmodel import Field, SQLModel + + +class BaseFile(SQLModel): + """Base model for Files + + Attributes: + name (str): Name of the file (used in alt). + type (str): Type of the file. + """ + name: str + type: str + + +class File(BaseFile, table=True): + """Database model representing a File + + A file is a stored file through the media center, mostly images. + + Attributes: + path_full (str): Path toward the stored file. + path_preview (str | None): Path toward a preview. + """ + id: uuid.UUID | None = Field( + default=None, default_factory=uuid.uuid4, primary_key=True) + path_full: str + path_preview: str | None = None diff --git a/backend/src/links/dto.py b/backend/src/links/dto.py new file mode 100644 index 0000000..cc22917 --- /dev/null +++ b/backend/src/links/dto.py @@ -0,0 +1,30 @@ +"""Links module DTO +""" +import uuid +from typing import Optional + +from sqlmodel import SQLModel +from src.events.dto import ReadEdition +from src.links.models import BaseLink + + +class CreateLink(BaseLink): + """CreateLink DTO + """ + edition_id: uuid.UUID + + +class UpdateLink(SQLModel): + """UpdateLink DTO + """ + title: Optional[str] = None + icon: Optional[str] = None + url: Optional[str] = None + order: Optional[int] = None + + +class ReadLink(BaseLink): + """ReadLink DTO + """ + id: uuid.UUID + edition: Optional[ReadEdition] = None diff --git a/backend/src/links/models.py b/backend/src/links/models.py new file mode 100644 index 0000000..2598bab --- /dev/null +++ b/backend/src/links/models.py @@ -0,0 +1,36 @@ +"""Link module models +""" +import uuid + +from sqlmodel import Field, SQLModel + + +class BaseLink(SQLModel): + """Base model for a Link + + Attributes: + title (str): Name of the link. + icon (str): Icon to display this link (using icon library). + url (str): Url of the link. + order (int): Order of occurence in page. + """ + title: str + icon: str + url: str + order: int + + +class Link(BaseLink, table=True): + """Database model representing a Link + + A link featured on an edition's page or in the website's header. + + Attributes: + id (uudi.UUID): Unique identifier for a link. + edition_id (uuid.UUID | None): Unique identifier of the + edition associated to this link (*optional*) + """ + id: uuid.UUID | None = Field( + default=None, default_factory=uuid.uuid4, primary_key=True) + edition_id: uuid.UUID | None = Field( + default=None, foreign_key="edition.id") diff --git a/backend/src/main.py b/backend/src/main.py new file mode 100644 index 0000000..9bb71ec --- /dev/null +++ b/backend/src/main.py @@ -0,0 +1,3 @@ +from fastapi import FastAPI + +app = FastAPI() diff --git a/backend/src/news/dto.py b/backend/src/news/dto.py new file mode 100644 index 0000000..46d38d8 --- /dev/null +++ b/backend/src/news/dto.py @@ -0,0 +1,34 @@ +"""News module DTO +""" +import datetime +import uuid +from typing import Optional + +from sqlmodel import SQLModel +from src.files.dto import ReadFile +from src.news.models import BaseNews + + +class CreateNews(BaseNews): + """CreateNews DTO + """ + hero_id: uuid.UUID + + +class UpdateNews(SQLModel): + """UpdateNews DTO + """ + title: Optional[str] = None + subtitle: Optional[str] = None + long_description: Optional[dict] = None + short_description: Optional[str] = None + carousel: Optional[bool] = None + banner: Optional[bool] = None + expiry_date: Optional[datetime.datetime] = None + + +class ReadNews(BaseNews): + """ReadNews DTO + """ + id: uuid.UUID + hero: Optional[ReadFile] = None diff --git a/backend/src/news/models.py b/backend/src/news/models.py new file mode 100644 index 0000000..1041c04 --- /dev/null +++ b/backend/src/news/models.py @@ -0,0 +1,32 @@ +"""News module models +""" +import datetime +import uuid + +from sqlalchemy.dialects.postgresql import JSONB +from sqlmodel import Field, SQLModel + + +class BaseNews(SQLModel): + """Base model for a News + + Attributes: + """ + title: str + subtitle: str + long_description: dict = Field(sa_type=JSONB, nullable=False) + short_description: str + carousel: bool + banner: bool + expiry_date: datetime.datetime + + +class News(BaseNews, table=True): + """Database model representing a News + + A plain blog post unrelated to a specific event, + appears in the homepage carousel, as a site-wide banner or both + """ + id: uuid.UUID | None = Field( + default=None, default_factory=uuid.uuid4, primary_key=True) + hero_id: uuid.UUID = Field(foreign_key="file.id") diff --git a/backend/src/sitewide/dto.py b/backend/src/sitewide/dto.py new file mode 100644 index 0000000..ac4bdc6 --- /dev/null +++ b/backend/src/sitewide/dto.py @@ -0,0 +1,50 @@ +"""Sitewide module DTO +""" +import uuid +from typing import Optional + +from sqlmodel import SQLModel +from src.files.dto import ReadFile +from src.sitewide.models import (BaseEmbeddedBlock, BaseSite, + EmbeddedBlockLayout, EmbeddedBlockLocation) + + +class CreateSite(BaseSite): + """CreateSite DTO + """ + image_id: uuid.UUID + + +class UpdateSite(SQLModel): + """UpdateSite DTO + """ + legal: Optional[dict] = None + about: Optional[dict] = None + short_description: Optional[str] = None + + +class ReadSite(BaseSite): + """ReadSite DTO + """ + id: uuid.UUID + + +class CreateEnbeddedBlock(BaseEmbeddedBlock): + """CreateEnbeddedBlock DTO + """ + + +class UpdateEnbeddedBlock(SQLModel): + """UpdateEnbeddedBlock DTO + """ + title: Optional[str] = None + layout: Optional[EmbeddedBlockLayout] = None + location: Optional[EmbeddedBlockLocation] = None + description: Optional[dict] = None + + +class ReadEnbeddedBlock(BaseEmbeddedBlock): + """ReadEnbeddedBlock DTO + """ + id: uuid.UUID + image: ReadFile diff --git a/backend/src/sitewide/models.py b/backend/src/sitewide/models.py new file mode 100644 index 0000000..59d02d5 --- /dev/null +++ b/backend/src/sitewide/models.py @@ -0,0 +1,55 @@ +"""Sitewide module models +""" +import uuid +from enum import StrEnum + +from sqlalchemy.dialects.postgresql import JSONB +from sqlmodel import Field, SQLModel + + +class EmbeddedBlockLayout(StrEnum): + """Relative layout of an embedded block's image and description + """ + LEFT = 'left' + RIGHT = 'right' + CENTER = 'center' + + +class EmbeddedBlockLocation(StrEnum): + """The location where an embedded block should appear + """ + HOME = 'home' + CONTACT = 'contact' + + +class BaseSite(SQLModel): + """BaseSite + """ + legal: dict = Field(sa_type=JSONB, nullable=False) + about: dict = Field(sa_type=JSONB, nullable=False) + short_description: str + + +class Site(BaseSite, table=True): + """Database model representing the Site-wide configuration + """ + id: uuid.UUID | None = Field( + default=None, default_factory=uuid.uuid4, primary_key=True) + + +class BaseEmbeddedBlock(SQLModel): + """BaseEmbeddedBlock + """ + title: str + layout: EmbeddedBlockLayout + location: EmbeddedBlockLocation + description: dict = Field(sa_type=JSONB, nullable=False) + + +class EmbeddedBlock(BaseEmbeddedBlock, table=True): + """A paragraph displayed on the home page or contact page + with information about Girasol + """ + id: uuid.UUID | None = Field( + default=None, default_factory=uuid.uuid4, primary_key=True) + image_id: uuid.UUID = Field(foreign_key="file.id") diff --git a/backend/src/sponsors/dto.py b/backend/src/sponsors/dto.py new file mode 100644 index 0000000..d9e67ee --- /dev/null +++ b/backend/src/sponsors/dto.py @@ -0,0 +1,29 @@ +"""Sponsor module DTO +""" +import uuid +from typing import List, Optional + +from sqlmodel import Field, SQLModel +from src.events.dto import ReadEdition +from src.sponsors.models import BaseSponsor + + +class CreateSponsor(BaseSponsor): + """CreateSponsor DTO + """ + image_id: uuid.UUID + + +class UpdateSponsor(SQLModel): + """UpdateSponsor DTO + """ + title: Optional[str] = None + sitewide: Optional[bool] = None + url: Optional[str] = None + + +class ReadSponsor(BaseSponsor): + """ReadSponsor DTO + """ + id: uuid.UUID + editions: List['ReadEdition'] = Field(default_factory=list) diff --git a/backend/src/sponsors/models.py b/backend/src/sponsors/models.py new file mode 100644 index 0000000..cd08b05 --- /dev/null +++ b/backend/src/sponsors/models.py @@ -0,0 +1,28 @@ +"""Sponsor module model +""" +import uuid + +from sqlmodel import Field, Relationship, SQLModel +from src.sponsors.models import EditionSponsorLink + + +class BaseSponsor(SQLModel): + """Base Sponsor + """ + title: str + sitewide: bool + url: str + + +class Sponsor(BaseSponsor, table=True): + """Database model representing a Sponsor + + A sponsor for Girasol's activities + """ + id: uuid.UUID | None = Field( + default=None, default_factory=uuid.uuid4, primary_key=True) + image_id: uuid.UUID = Field(foreign_key="file.id") + editions: list['Edition'] = Relationship( + cascade_delete=True, + link_model=EditionSponsorLink, + )