From c266814c9649ac5170025624d1806c9efc685b53 Mon Sep 17 00:00:00 2001 From: AlexSserb Date: Tue, 4 Nov 2025 12:18:53 +0400 Subject: [PATCH] feat: attr options and selects editing --- models/attr_select.py | 4 +-- repositories/__init__.py | 1 + repositories/attr_option.py | 21 +++++++++++++ repositories/attr_select.py | 19 ++++-------- routers/crm/v1/attr_option.py | 56 +++++++++++++++++++++++++++++++++++ routers/crm/v1/attr_select.py | 39 +++++++++++++++++++----- schemas/attr_option.py | 55 ++++++++++++++++++++++++++++++++++ schemas/attr_select.py | 34 ++++++++++++++++----- services/__init__.py | 1 + services/attr_option.py | 27 +++++++++++++++++ services/attr_select.py | 22 +++++++------- 11 files changed, 239 insertions(+), 40 deletions(-) create mode 100644 repositories/attr_option.py create mode 100644 routers/crm/v1/attr_option.py create mode 100644 schemas/attr_option.py create mode 100644 services/attr_option.py diff --git a/models/attr_select.py b/models/attr_select.py index ce1958f..003d03e 100644 --- a/models/attr_select.py +++ b/models/attr_select.py @@ -13,7 +13,7 @@ if TYPE_CHECKING: class AttributeSelect(BaseModel, IdMixin, SoftDeleteMixin): __tablename__ = "attribute_selects" - label: Mapped[str] = mapped_column() + name: Mapped[str] = mapped_column() is_built_in: Mapped[bool] = mapped_column( default=False, comment="Если встроенный select, то запрещено редактировать пользователю", @@ -33,7 +33,7 @@ class AttributeSelect(BaseModel, IdMixin, SoftDeleteMixin): class AttributeOption(BaseModel, IdMixin, SoftDeleteMixin): __tablename__ = "attribute_options" - label: Mapped[str] = mapped_column() + name: Mapped[str] = mapped_column() select_id: Mapped[int] = mapped_column(ForeignKey("attribute_selects.id")) select: Mapped[AttributeSelect] = relationship( diff --git a/repositories/__init__.py b/repositories/__init__.py index 6091de1..70a7146 100644 --- a/repositories/__init__.py +++ b/repositories/__init__.py @@ -7,3 +7,4 @@ from .deal_tag import DealTagRepository as DealTagRepository from .module import ModuleRepository as ModuleRepository from .project import ProjectRepository as ProjectRepository from .status import StatusRepository as StatusRepository +from .attr_option import AttrOptionRepository as AttrOptionRepository diff --git a/repositories/attr_option.py b/repositories/attr_option.py new file mode 100644 index 0000000..bc63cbb --- /dev/null +++ b/repositories/attr_option.py @@ -0,0 +1,21 @@ +from sqlalchemy import Select +from sqlalchemy.ext.asyncio import AsyncSession + +from models import AttributeOption +from repositories.mixins import RepCrudMixin +from schemas.attr_option import CreateAttrOptionSchema, UpdateAttrOptionSchema + + +class AttrOptionRepository( + RepCrudMixin[AttributeOption, CreateAttrOptionSchema, UpdateAttrOptionSchema], +): + session: AsyncSession + entity_class = AttributeOption + entity_not_found_msg = "Опция не найдена" + + def _process_get_all_stmt_with_args(self, stmt: Select, *args) -> Select: + select_id = args[0] + return stmt.where( + AttributeOption.select_id == select_id, + AttributeOption.is_deleted.is_(False), + ).order_by(AttributeOption.id) diff --git a/repositories/attr_select.py b/repositories/attr_select.py index 5707359..5ad5176 100644 --- a/repositories/attr_select.py +++ b/repositories/attr_select.py @@ -1,19 +1,12 @@ -from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession -from models import AttributeSelect, AttributeOption -from repositories.base import BaseRepository -from repositories.mixins import RepGetAllMixin +from models import AttributeSelect +from repositories.mixins import RepCrudMixin +from schemas.attr_select import UpdateAttrSelectSchema, CreateAttrSelectSchema -class AttrSelectRepository(BaseRepository, RepGetAllMixin[AttributeSelect]): +class AttrSelectRepository( + RepCrudMixin[AttributeSelect, CreateAttrSelectSchema, UpdateAttrSelectSchema], +): session: AsyncSession entity_class = AttributeSelect - - async def get_options(self, select_id: int) -> list[AttributeOption]: - stmt = select(AttributeOption).where( - AttributeOption.select_id == select_id, - AttributeOption.is_deleted.is_(False), - ) - result = await self.session.execute(stmt) - return list(result.scalars().all()) diff --git a/routers/crm/v1/attr_option.py b/routers/crm/v1/attr_option.py new file mode 100644 index 0000000..a24e5fc --- /dev/null +++ b/routers/crm/v1/attr_option.py @@ -0,0 +1,56 @@ +from fastapi import APIRouter, Path + +from backend.dependecies import SessionDependency +from schemas.attr_option import * +from services import AttrOptionService + +router = APIRouter(tags=["attr_option"]) + + +@router.get( + "/{selectId}", + response_model=GetAllAttrSelectOptionsResponse, + operation_id="get_attr_options", +) +async def get_attr_options( + session: SessionDependency, + select_id: int = Path(alias="selectId"), +): + return await AttrOptionService(session).get_all(select_id) + + +@router.post( + "/", + response_model=CreateAttrOptionResponse, + operation_id="create_attr_option", +) +async def create_attr_select( + session: SessionDependency, + request: CreateAttrOptionRequest, +): + return await AttrOptionService(session).create(request) + + +@router.patch( + "/{pk}", + response_model=UpdateAttrOptionResponse, + operation_id="update_attr_option", +) +async def update_attr_option( + session: SessionDependency, + request: UpdateAttrOptionRequest, + pk: int = Path(), +): + return await AttrOptionService(session).update(pk, request) + + +@router.delete( + "/{pk}", + response_model=DeleteAttrOptionResponse, + operation_id="delete_attr_option", +) +async def delete_attr_option( + session: SessionDependency, + pk: int = Path(), +): + return await AttrOptionService(session).delete(pk) diff --git a/routers/crm/v1/attr_select.py b/routers/crm/v1/attr_select.py index 3040023..74d4922 100644 --- a/routers/crm/v1/attr_select.py +++ b/routers/crm/v1/attr_select.py @@ -18,13 +18,38 @@ async def get_attr_selects( return await AttrSelectService(session).get_all() -@router.get( - "/{selectId}", - response_model=GetAllAttrSelectOptionsResponse, - operation_id="get_attr_select_options", +@router.post( + "/", + response_model=CreateAttrSelectResponse, + operation_id="create_attr_select", ) -async def get_attr_select_options( +async def create_attr_select( session: SessionDependency, - select_id: int = Path(alias="selectId"), + request: CreateAttrSelectRequest, ): - return await AttrSelectService(session).get_options(select_id) + return await AttrSelectService(session).create(request) + + +@router.patch( + "/{pk}", + response_model=UpdateAttrSelectResponse, + operation_id="update_attr_select", +) +async def update_attr_select( + session: SessionDependency, + request: UpdateAttrSelectRequest, + pk: int = Path(), +): + return await AttrSelectService(session).update(pk, request) + + +@router.delete( + "/{pk}", + response_model=DeleteAttrSelectResponse, + operation_id="delete_attr_select", +) +async def delete_attr_select( + session: SessionDependency, + pk: int = Path(), +): + return await AttrSelectService(session).delete(pk) diff --git a/schemas/attr_option.py b/schemas/attr_option.py new file mode 100644 index 0000000..0971b25 --- /dev/null +++ b/schemas/attr_option.py @@ -0,0 +1,55 @@ +from schemas.base import BaseSchema, BaseResponse + + +# region Entity + + +class AttrOptionSchema(BaseSchema): + id: int + name: str + + +class CreateAttrOptionSchema(BaseSchema): + name: str + select_id: int + + +class UpdateAttrOptionSchema(BaseSchema): + name: str + + +# endregion + +# region Request + + +class CreateAttrOptionRequest(BaseSchema): + entity: CreateAttrOptionSchema + + +class UpdateAttrOptionRequest(BaseSchema): + entity: UpdateAttrOptionSchema + + +# endregion + +# region Response + + +class GetAllAttrSelectOptionsResponse(BaseSchema): + items: list[AttrOptionSchema] + + +class CreateAttrOptionResponse(BaseSchema): + entity: AttrOptionSchema + + +class UpdateAttrOptionResponse(BaseResponse): + pass + + +class DeleteAttrOptionResponse(BaseSchema): + pass + + +# endregion diff --git a/schemas/attr_select.py b/schemas/attr_select.py index e532812..7be5130 100644 --- a/schemas/attr_select.py +++ b/schemas/attr_select.py @@ -1,4 +1,5 @@ -from schemas.base import BaseSchema +from schemas.attr_option import AttrOptionSchema +from schemas.base import BaseSchema, BaseResponse # region Entity @@ -6,13 +7,16 @@ from schemas.base import BaseSchema class AttrSelectSchema(BaseSchema): id: int - label: str + name: str is_built_in: bool -class AttrOptionSchema(BaseSchema): - id: int - label: str +class CreateAttrSelectSchema(BaseSchema): + name: str + + +class UpdateAttrSelectSchema(BaseSchema): + name: str class AttrSelectWithOptionsSchema(AttrSelectSchema): @@ -24,6 +28,14 @@ class AttrSelectWithOptionsSchema(AttrSelectSchema): # region Request +class CreateAttrSelectRequest(BaseSchema): + entity: CreateAttrSelectSchema + + +class UpdateAttrSelectRequest(BaseSchema): + entity: UpdateAttrSelectSchema + + # endregion # region Response @@ -33,8 +45,16 @@ class GetAllAttrSelectsResponse(BaseSchema): items: list[AttrSelectSchema] -class GetAllAttrSelectOptionsResponse(BaseSchema): - items: list[AttrOptionSchema] +class CreateAttrSelectResponse(BaseResponse): + entity: AttrSelectSchema + + +class UpdateAttrSelectResponse(BaseResponse): + pass + + +class DeleteAttrSelectResponse(BaseResponse): + pass # endregion diff --git a/services/__init__.py b/services/__init__.py index 0412db8..e3bddfb 100644 --- a/services/__init__.py +++ b/services/__init__.py @@ -6,3 +6,4 @@ from .deal_group import DealGroupService as DealGroupService from .deal_tag import DealTagService as DealTagService from .project import ProjectService as ProjectService from .status import StatusService as StatusService +from .attr_option import AttrOptionService as AttrOptionService diff --git a/services/attr_option.py b/services/attr_option.py new file mode 100644 index 0000000..c5379d2 --- /dev/null +++ b/services/attr_option.py @@ -0,0 +1,27 @@ +from sqlalchemy.ext.asyncio import AsyncSession + +from models import AttributeOption +from repositories import AttrOptionRepository +from schemas.attr_option import ( + AttrOptionSchema, + CreateAttrOptionRequest, + UpdateAttrOptionRequest, +) +from services.mixins import ServiceCrudMixin + + +class AttrOptionService( + ServiceCrudMixin[ + AttributeOption, + AttrOptionSchema, + CreateAttrOptionRequest, + UpdateAttrOptionRequest, + ] +): + schema_class = AttrOptionSchema + entity_deleted_msg = "Опция успешно удалена" + entity_updated_msg = "Опция успешно обновлена" + entity_created_msg = "Опция успешно создана" + + def __init__(self, session: AsyncSession): + self.repository = AttrOptionRepository(session) diff --git a/services/attr_select.py b/services/attr_select.py index 88913e2..c10f984 100644 --- a/services/attr_select.py +++ b/services/attr_select.py @@ -4,21 +4,21 @@ from models import AttributeSelect from repositories import AttrSelectRepository from schemas.attr_select import ( AttrSelectSchema, - GetAllAttrSelectOptionsResponse, - AttrOptionSchema, + CreateAttrSelectRequest, + UpdateAttrSelectRequest, ) -from services.mixins import ServiceGetAllMixin +from services.mixins import ServiceCrudMixin -class AttrSelectService(ServiceGetAllMixin[AttributeSelect, AttrSelectSchema]): +class AttrSelectService( + ServiceCrudMixin[ + AttributeSelect, + AttrSelectSchema, + CreateAttrSelectRequest, + UpdateAttrSelectRequest, + ] +): schema_class = AttrSelectSchema def __init__(self, session: AsyncSession): self.repository = AttrSelectRepository(session) - - async def get_options(self, select_id: int) -> GetAllAttrSelectOptionsResponse: - options = await self.repository.get_options(select_id) - - return GetAllAttrSelectOptionsResponse( - items=[AttrOptionSchema.model_validate(option) for option in options] - )