diff --git a/Dockerfile b/Dockerfile index be09a305..c691e80b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,7 @@ COPY pyproject.toml pyproject.toml RUN pip install poetry RUN poetry config virtualenvs.create false -RUN poetry install --no-dev +RUN poetry install --no-root COPY . /app diff --git a/README.md b/README.md index e69de29b..b584e219 100644 --- a/README.md +++ b/README.md @@ -0,0 +1 @@ +# SQLAlchemy models diff --git a/docker-compose.yml b/docker-compose.yml index 8fcb739c..77df89f7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,4 @@ -version: '3.8' +#version: '3.8' services: web: @@ -9,7 +9,7 @@ services: - "8000:8000" env_file: - ./ops/environment/.default.env - - ./ops/environment/.local.env + - ops/environment/.env depends_on: - db networks: diff --git a/src/base_settings.py b/src/base_settings.py index 593b83ef..cbb9e6dd 100644 --- a/src/base_settings.py +++ b/src/base_settings.py @@ -1,4 +1,5 @@ from typing import Optional +import os from pydantic import ( BaseModel, @@ -43,4 +44,4 @@ class ProjectSettings(BaseSettings): ) -base_settings = ProjectSettings() +base_settings = ProjectSettings() \ No newline at end of file diff --git a/src/catalogue/admin.py b/src/catalogue/admin.py index 3b9ab8f8..34f1433a 100644 --- a/src/catalogue/admin.py +++ b/src/catalogue/admin.py @@ -7,6 +7,10 @@ ProductDiscount, ProductImage, StockRecord, + Basket, + BasketLine, + OrderLine, + Order, ) @@ -61,6 +65,36 @@ class ProductDiscountAdmin(ModelView, model=ProductDiscount): icon = 'fa-solid fa-percent' category = CATALOGUE_CATEGORY +class BasketAdmin(ModelView, model=Basket): + column_list = [Basket.id, Basket.user_id, Basket.price, Basket.status] + column_searchable_list = [Basket.user_id, Basket.status] + form_columns = ['user_id', 'price', 'status'] + icon = 'fa-solid fa-basket-shopping' + category = CATALOGUE_CATEGORY + +class BasketLineAdmin(ModelView, model=BasketLine): + column_list = [BasketLine.id, BasketLine.basket_id, BasketLine.product_id, BasketLine.quantity, BasketLine.price] + column_searchable_list = [BasketLine.basket_id, BasketLine.product_id] + form_columns = ['basket_id', 'product_id', 'quantity', 'price'] + icon = 'fa-solid fa-cart-shopping' + category = CATALOGUE_CATEGORY + +class OrderLineAdmin(ModelView, model=OrderLine): + column_list = [OrderLine.id, OrderLine.product_id, OrderLine.order_id, OrderLine.quantity, OrderLine.price] + column_searchable_list = [OrderLine.product_id, OrderLine.order_id] + form_columns = ['order_id', 'product_id', 'quantity', 'price'] + icon = 'fa-solid fa-clipboard-list' + category = CATALOGUE_CATEGORY + +class OrderAdmin(ModelView, model=Order): + column_list = [Order.id, Order.number, Order.shipping_method, Order.status, Order.total_price] + column_searchable_list = [Order.shipping_method, Order.status, Order.additional_info] + form_columns = ['number', 'basket_id', 'user_id', 'address_id', + 'total_price', 'shipping_price', 'shipping_method', + 'status', 'additional_info' + ] + icon = 'fa-solid fa-bag-shopping' + category = CATALOGUE_CATEGORY def register_products_admin_views(admin): admin.add_view(ProductAdmin) @@ -69,3 +103,7 @@ def register_products_admin_views(admin): admin.add_view(ProductImageAdmin) admin.add_view(StockRecordAdmin) admin.add_view(ProductDiscountAdmin) + admin.add_view(BasketAdmin) + admin.add_view(BasketLineAdmin) + admin.add_view(OrderLineAdmin) + admin.add_view(OrderAdmin) diff --git a/src/catalogue/models/database.py b/src/catalogue/models/database.py index cc5ba136..988f4c67 100644 --- a/src/catalogue/models/database.py +++ b/src/catalogue/models/database.py @@ -1,4 +1,6 @@ from datetime import datetime +from enum import Enum +from decimal import Decimal from typing import ( List, Optional, @@ -10,6 +12,18 @@ SQLModel, ) +class BasketStatus(str, Enum): + OPEN = "Open" + CLOSED = "Closed" + CANCELLED = "Cancelled" + +class OrderStatus(str, Enum): + OPEN = "Open" + PAID = "Paid" + SENT = "Sent" + RECEIVED = "Received" + CANCELLED = "Cancelled" + RETURNED = "Returned" class Product(SQLModel, table=True): __tablename__ = 'products' @@ -24,6 +38,8 @@ 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") + basket_lines: List["BasketLine"] = Relationship(back_populates="product") + order_lines: List["OrderLine"] = Relationship(back_populates="product") class ProductCategory(SQLModel, table=True): __tablename__ = 'product_categories' @@ -90,3 +106,66 @@ class ProductDiscount(SQLModel, table=True): valid_to: datetime product: Product = Relationship(back_populates="discounts") + +class Basket(SQLModel, table=True): + __tablename__ = "basket" + + id: Optional[int] = Field(default=None, primary_key=True) + user_id: int = Field(foreign_key="users.id") + price: Decimal + status: BasketStatus = Field( + sa_column_kwargs={"nullable": False, "server_default": "Open"} + ) + + lines: List["BasketLine"] = Relationship(back_populates="basket") + order: Optional["Order"] = Relationship(back_populates="basket") + +class BasketLine(SQLModel, table=True): + __tablename__ = 'basket_lines' + + id: Optional[int] = Field(default=None, primary_key=True) + product_id: int = Field(foreign_key="products.id") + basket_id: int = Field(foreign_key="basket.id") + quantity: int + price: Decimal + + product: Product = Relationship(back_populates="basket_lines") + basket: Basket = Relationship(back_populates="lines") + +class Order(SQLModel, table=True): + __tablename__ = 'orders' + + id: Optional[int] = Field(default=None, primary_key=True) + number: int = Field( + sa_column_kwargs={ + "nullable": False, + "unique": True, + "server_default": "10000" + } + ) + basket_id: int = Field(foreign_key="basket.id") + user_id: int = Field(foreign_key="users.id") + address_id: int = Field(foreign_key="addresses.id") + total_price: Decimal + shipping_price: Decimal + shipping_method: Optional[str] = None + status: OrderStatus = Field( + sa_column_kwargs={"nullable": False, "server_default": "Open"} + ) + additional_info: Optional[str] = None + created_at: datetime = Field(default_factory=datetime.utcnow, nullable=False) + + basket: Basket = Relationship(back_populates="order") + lines: List["OrderLine"] = Relationship(back_populates="order") + +class OrderLine(SQLModel, table=True): + __tablename__ = 'order_lines' + + id: Optional[int] = Field(default=None, primary_key=True) + product_id: int = Field(foreign_key="products.id") + order_id: int = Field(foreign_key="orders.id") + quantity: int + price: Decimal + + product: Product = Relationship(back_populates="order_lines") + order: Order = Relationship(back_populates="lines") \ No newline at end of file