Add color validation for categories
This commit is contained in:
parent
e0b77d7ff2
commit
8737897ce9
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
Loading…
Reference in a new issue