Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ WORKDIR /app

COPY pyproject.toml pyproject.toml

RUN pip install poetry
RUN poetry config virtualenvs.create false
RUN poetry install --no-dev
RUN pip install --no-cache-dir poetry \
&& poetry config virtualenvs.create false

RUN poetry install --no-root --no-interaction --no-ansi

COPY . /app

Expand Down
4 changes: 2 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
version: '3.8'

services:
web:
build: .
Expand All @@ -23,6 +21,8 @@ services:
POSTGRES_USER: user
POSTGRES_PASSWORD: password
POSTGRES_DB: fastapi_shop
ports:
- "5432:5432"
networks:
- web_network

Expand Down
20 changes: 20 additions & 0 deletions src/catalogue/admin.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
from sqladmin import ModelView

from src.catalogue.models.database import (
AdditionalProduct,
Category,
Product,
ProductCategory,
ProductDiscount,
ProductImage,
RecommendedProduct,
StockRecord,
)

Expand Down Expand Up @@ -62,10 +64,28 @@ class ProductDiscountAdmin(ModelView, model=ProductDiscount):
category = CATALOGUE_CATEGORY


class AdditionalProductsAdmin(ModelView, model=AdditionalProduct):
column_list = [AdditionalProduct.primary_product, AdditionalProduct.additional_product]
column_searchable_list = [AdditionalProduct.primary_id, AdditionalProduct.additional_id]
form_columns = ['primary_product', 'additional_product']
icon = 'fa-solid fa-plus'
category = CATALOGUE_CATEGORY


class RecommendedProductsAdmin(ModelView, model=RecommendedProduct):
column_list = [RecommendedProduct.primary_product, RecommendedProduct.recommended_product]
column_searchable_list = [RecommendedProduct.primary_id, RecommendedProduct.recommended_id]
form_columns = ['primary_product', 'recommended_product']
icon = 'fa-solid fa-star'
category = CATALOGUE_CATEGORY


def register_products_admin_views(admin):
admin.add_view(ProductAdmin)
admin.add_view(ProductCategoryAdmin)
admin.add_view(CategoryAdmin)
admin.add_view(ProductImageAdmin)
admin.add_view(StockRecordAdmin)
admin.add_view(ProductDiscountAdmin)
admin.add_view(AdditionalProductsAdmin)
admin.add_view(RecommendedProductsAdmin)
43 changes: 43 additions & 0 deletions src/catalogue/models/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,17 @@ class Product(SQLModel, table=True):
images: List["ProductImage"] = Relationship(back_populates="product")
stock_records: List["StockRecord"] = Relationship(back_populates="product")
discounts: List["ProductDiscount"] = Relationship(back_populates="product")
additional_products: List["AdditionalProduct"] = Relationship(
back_populates="primary_product",
sa_relationship_kwargs={"foreign_keys": "AdditionalProduct.primary_id"}
)
recommended_products: List["RecommendedProduct"] = Relationship(
back_populates="primary_product",
sa_relationship_kwargs={"foreign_keys": "RecommendedProduct.primary_id"}
)

def __str__(self) -> str:
return f"{self.id} - {self.title}"

class ProductCategory(SQLModel, table=True):
__tablename__ = 'product_categories'
Expand Down Expand Up @@ -90,3 +101,35 @@ class ProductDiscount(SQLModel, table=True):
valid_to: datetime

product: Product = Relationship(back_populates="discounts")


class AdditionalProduct(SQLModel, table=True):
__tablename__ = 'additional_products'

id: Optional[int] = Field(default=None, primary_key=True)
primary_id: int = Field(foreign_key="products.id")
additional_id: int = Field(foreign_key="products.id")

primary_product: Product = Relationship(
back_populates="additional_products",
sa_relationship_kwargs={"foreign_keys": "AdditionalProduct.primary_id"}
)
additional_product: Product = Relationship(
sa_relationship_kwargs={"foreign_keys": "AdditionalProduct.additional_id"}
)


class RecommendedProduct(SQLModel, table=True):
__tablename__ = 'recommended_products'

id: Optional[int] = Field(default=None, primary_key=True)
primary_id: int = Field(foreign_key="products.id")
recommended_id: int = Field(foreign_key="products.id")

primary_product: Product = Relationship(
back_populates="recommended_products",
sa_relationship_kwargs={"foreign_keys": "RecommendedProduct.primary_id"}
)
recommended_product: Product = Relationship(
sa_relationship_kwargs={"foreign_keys": "RecommendedProduct.recommended_id"}
)
20 changes: 19 additions & 1 deletion src/catalogue/repository.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from fastapi import Depends
from sqlalchemy.ext.asyncio import AsyncSession

from src.catalogue.models.database import Product
from src.catalogue.models.database import Product, AdditionalProduct, RecommendedProduct
from src.common.databases.postgres import get_session
from src.common.repository.sqlalchemy import BaseSQLAlchemyRepository

Expand All @@ -13,3 +13,21 @@ def __init__(self, session: AsyncSession):

def get_product_repository(session: AsyncSession = Depends(get_session)) -> ProductRepository:
return ProductRepository(session=session)


class AdditionalProductsRepository(BaseSQLAlchemyRepository[AdditionalProduct]):
def __init__(self, session: AsyncSession):
super().__init__(model=AdditionalProduct, session=session)


def get_additional_products_repository(session: AsyncSession = Depends(get_session)) -> AdditionalProductsRepository:
return AdditionalProductsRepository(session=session)


class RecommendedProductsRepository(BaseSQLAlchemyRepository[RecommendedProduct]):
def __init__(self, session: AsyncSession):
super().__init__(model=RecommendedProduct, session=session)


def get_recommended_products_repository(session: AsyncSession = Depends(get_session)) -> RecommendedProductsRepository:
return RecommendedProductsRepository(session=session)
10 changes: 10 additions & 0 deletions src/catalogue/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,17 @@

class CatalogueRoutesPrefixes:
product: str = '/product'
additional_products: str = '/additional-products'
recommended_products: str = '/recommended-products'


class ProductRoutesPrefixes(BaseCrudPrefixes):
...


class AdditionalProductsRoutesPrefixes(BaseCrudPrefixes):
...


class RecommendedProductsRoutesPrefixes(BaseCrudPrefixes):
...
24 changes: 23 additions & 1 deletion src/catalogue/services.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
from fastapi import Depends

from src.catalogue.models.database import Product
from src.catalogue.models.database import Product, AdditionalProduct, RecommendedProduct
from src.catalogue.repository import (
ProductRepository,
get_product_repository,
AdditionalProductsRepository,
get_additional_products_repository,
RecommendedProductsRepository,
get_recommended_products_repository,
)
from src.common.service import BaseService

Expand All @@ -15,3 +19,21 @@ def __init__(self, repository: ProductRepository):

def get_product_service(repo: ProductRepository = Depends(get_product_repository)) -> ProductService:
return ProductService(repository=repo)


class AdditionalProductsService(BaseService[AdditionalProduct]):
def __init__(self, repository: AdditionalProductsRepository):
super().__init__(repository)


def get_additional_products_service(repo: AdditionalProductsRepository = Depends(get_additional_products_repository)) -> AdditionalProductsService:
return AdditionalProductsService(repository=repo)


class RecommendedProductsService(BaseService[RecommendedProduct]):
def __init__(self, repository: RecommendedProductsRepository):
super().__init__(repository)


def get_recommended_products_service(repo: RecommendedProductsRepository = Depends(get_recommended_products_repository)) -> RecommendedProductsService:
return RecommendedProductsService(repository=repo)
2 changes: 2 additions & 0 deletions src/catalogue/views/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
from .product import router as product_router
from .additional_products import router as additional_products_router
from .recommended_products import router as recommended_products_router
139 changes: 139 additions & 0 deletions src/catalogue/views/additional_products.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
from typing import (
Annotated,
Union,
)

from fastapi import (
APIRouter,
Depends,
HTTPException,
Response,
status,
)

from src.catalogue.models.database import AdditionalProduct
from src.catalogue.routes import (
CatalogueRoutesPrefixes,
AdditionalProductsRoutesPrefixes,
)
from src.catalogue.services import get_additional_products_service
from src.common.exceptions.base import ObjectDoesNotExistException
from src.common.schemas.common import ErrorResponse


router = APIRouter(prefix=CatalogueRoutesPrefixes.additional_products)


@router.get(
AdditionalProductsRoutesPrefixes.root,
status_code=status.HTTP_200_OK,
response_model=list[AdditionalProduct],
)
async def additional_products_list(service: Annotated[get_additional_products_service, Depends()]) -> list[AdditionalProduct]:
"""
Get list of additional products.

Returns:
Response with list of additional products.
"""
return await service.list()


@router.get(
AdditionalProductsRoutesPrefixes.detail,
responses={
status.HTTP_200_OK: {'model': AdditionalProduct},
status.HTTP_404_NOT_FOUND: {'model': ErrorResponse},
},
status_code=status.HTTP_200_OK,
response_model=Union[AdditionalProduct, ErrorResponse],
)
async def additional_products_detail(
response: Response,
pk: int,
service: Annotated[get_additional_products_service, Depends()],
) -> Union[Response, ErrorResponse]:
"""
Retrieve additional product.

Returns:
Response with additional product details.
"""
try:
response = await service.detail(pk=pk)
except ObjectDoesNotExistException as exc:
response.status_code = status.HTTP_404_NOT_FOUND
return ErrorResponse(message=exc.message)

return response


@router.post(
AdditionalProductsRoutesPrefixes.root,
status_code=status.HTTP_201_CREATED,
response_model=AdditionalProduct,
)
async def additional_products_create(
additional_product_data: AdditionalProduct,
service: Annotated[get_additional_products_service, Depends()],
) -> AdditionalProduct:
"""
Create additional product.

Returns:
Response with created additional product.
"""
return await service.create(additional_product_data)


@router.put(
AdditionalProductsRoutesPrefixes.detail,
responses={
status.HTTP_200_OK: {'model': AdditionalProduct},
status.HTTP_404_NOT_FOUND: {'model': ErrorResponse},
},
status_code=status.HTTP_200_OK,
response_model=Union[AdditionalProduct, ErrorResponse],
)
async def additional_products_update(
response: Response,
pk: int,
additional_product_data: AdditionalProduct,
service: Annotated[get_additional_products_service, Depends()],
) -> Union[Response, ErrorResponse]:
"""
Update additional product.

Returns:
Response with updated additional product.
"""
try:
response = await service.update(pk=pk, update_data=additional_product_data)
except ObjectDoesNotExistException as exc:
response.status_code = status.HTTP_404_NOT_FOUND
return ErrorResponse(message=exc.message)

return response


@router.delete(
AdditionalProductsRoutesPrefixes.detail,
status_code=status.HTTP_204_NO_CONTENT,
)
async def additional_products_delete(
pk: int,
service: Annotated[get_additional_products_service, Depends()],
) -> None:
"""
Delete additional product.

Returns:
Response confirming deletion.
"""
try:
await service.delete(pk=pk)
except ObjectDoesNotExistException as exc:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=exc.message
)
Loading