Add color validation for categories

This commit is contained in:
Peter Vacho 2024-12-25 20:24:11 +01:00
parent e0b77d7ff2
commit 8737897ce9
Signed by: school
GPG key ID: 8CFC3837052871B4
4 changed files with 36 additions and 16 deletions

View file

@ -15,6 +15,7 @@ dependencies = [
"python-jose>=3.3.0", "python-jose>=3.3.0",
"python-multipart>=0.0.17", "python-multipart>=0.0.17",
"bcrypt>=4.2.1", "bcrypt>=4.2.1",
"pydantic-extra-types>=2.10.1",
] ]
readme = "README.md" readme = "README.md"
requires-python = ">= 3.12" requires-python = ">= 3.12"

View file

@ -72,8 +72,11 @@ pydantic==2.10.2
# via event-management # via event-management
# via fastapi # via fastapi
# via lazy-model # via lazy-model
# via pydantic-extra-types
pydantic-core==2.27.1 pydantic-core==2.27.1
# via pydantic # via pydantic
pydantic-extra-types==2.10.1
# via event-management
pymongo==4.9.2 pymongo==4.9.2
# via motor # via motor
python-decouple==3.8 python-decouple==3.8
@ -101,6 +104,7 @@ typing-extensions==4.12.2
# via fastapi # via fastapi
# via pydantic # via pydantic
# via pydantic-core # via pydantic-core
# via pydantic-extra-types
uvicorn==0.32.1 uvicorn==0.32.1
# via event-management # via event-management
virtualenv==20.28.0 virtualenv==20.28.0

View file

@ -56,8 +56,11 @@ pydantic==2.10.2
# via event-management # via event-management
# via fastapi # via fastapi
# via lazy-model # via lazy-model
# via pydantic-extra-types
pydantic-core==2.27.1 pydantic-core==2.27.1
# via pydantic # via pydantic
pydantic-extra-types==2.10.1
# via event-management
pymongo==4.9.2 pymongo==4.9.2
# via motor # via motor
python-decouple==3.8 python-decouple==3.8
@ -83,5 +86,6 @@ typing-extensions==4.12.2
# via fastapi # via fastapi
# via pydantic # via pydantic
# via pydantic-core # via pydantic-core
# via pydantic-extra-types
uvicorn==0.32.1 uvicorn==0.32.1
# via event-management # via event-management

View file

@ -3,7 +3,8 @@ from typing import Annotated, final
from beanie import PydanticObjectId from beanie import PydanticObjectId
from fastapi import APIRouter, Body, HTTPException, Response, status from fastapi import APIRouter, Body, HTTPException, Response, status
from pydantic import BaseModel from pydantic import BaseModel, StringConstraints, field_validator
from pydantic_extra_types.color import Color
from src.api.auth.dependencies import LoggedInDep from src.api.auth.dependencies import LoggedInDep
from src.db.models.category import Category from src.db.models.category import Category
@ -21,13 +22,26 @@ categories_router = APIRouter(tags=["Categories"], prefix="/categories", depende
base_router = APIRouter(tags=["Categories"], dependencies=[LoggedInDep]) base_router = APIRouter(tags=["Categories"], dependencies=[LoggedInDep])
class _BaseCategoryData(BaseModel):
"""Base class for all category data classes."""
name: Annotated[str, StringConstraints(max_length=50)]
color: Color
@field_validator("color", mode="after")
@classmethod
def validate_color(cls, value: Color) -> Color:
"""Validate the color."""
if len(value.as_rgb_tuple()) == 4:
raise ValueError("Alpha channel is not allowed in colors")
return value
@final @final
class CategoryData(BaseModel): class CategoryData(_BaseCategoryData):
"""Data about a category sent to the user.""" """Data about a category sent to the user."""
owner_user_id: PydanticObjectId owner_user_id: PydanticObjectId
name: str
color: str
created_at: datetime created_at: datetime
@classmethod @classmethod
@ -37,26 +51,23 @@ class CategoryData(BaseModel):
raise ValueError("Got a category without id") raise ValueError("Got a category without id")
return cls( return cls(
owner_user_id=category.id,
name=category.name, name=category.name,
color=category.color, color=Color(category.color),
owner_user_id=category.id,
created_at=category.created_at, created_at=category.created_at,
) )
@final @final
class CategoryCreateData(BaseModel): class CategoryCreateData(_BaseCategoryData):
"""Data necessary to create a new category. """Data necessary to create a new category.
This structure is intended to be used for POST & PUT requests. This structure is intended to be used for POST & PUT requests.
""" """
name: str
color: str
async def create_category(self, user: User) -> Category: async def create_category(self, user: User) -> Category:
"""Create a new category in the database.""" """Create a new category in the database."""
cat = Category(user=user, name=self.name, color=self.color) cat = Category(user=user, name=self.name, color=self.color.as_hex(format="long"))
return await cat.create() return await cat.create()
async def update_category(self, category: Category) -> Category: async def update_category(self, category: Category) -> Category:
@ -67,7 +78,7 @@ class CategoryCreateData(BaseModel):
category.name = self.name category.name = self.name
updated = True updated = True
if category.color != self.color: if category.color != self.color:
category.color = self.color category.color = self.color.as_hex(format="long")
updated = True updated = True
if updated: if updated:
@ -76,14 +87,14 @@ class CategoryCreateData(BaseModel):
@final @final
class PartialCategoryUpdateData(BaseModel): class PartialCategoryUpdateData(_BaseCategoryData):
"""Data necessary to perform a partial update of the category. """Data necessary to perform a partial update of the category.
This structure is intended to be used for PATCH requests. This structure is intended to be used for PATCH requests.
""" """
name: str | None = None name: Annotated[str, StringConstraints(max_length=50)] | None = None # pyright: ignore[reportIncompatibleVariableOverride]
color: str | None = None color: Color | None = None # pyright: ignore[reportIncompatibleVariableOverride]
async def update_category(self, category: Category) -> Category: async def update_category(self, category: Category) -> Category:
"""Update an existing category, overwriting it with data that were specified.""" """Update an existing category, overwriting it with data that were specified."""
@ -93,7 +104,7 @@ class PartialCategoryUpdateData(BaseModel):
category.name = self.name category.name = self.name
updated = True updated = True
if self.color and category.color != self.color: if self.color and category.color != self.color:
category.color = self.color category.color = self.color.as_hex(format="long")
updated = True updated = True
if updated: if updated: