From 5570e6e49e107fcadb2723e5b3dc9fc55bbb7708 Mon Sep 17 00:00:00 2001 From: Automated Action Date: Sat, 31 May 2025 03:41:37 +0000 Subject: [PATCH] Add database models for food delivery API - Create base model with timestamp mixin - Add user model with roles - Add restaurant model - Add menu item model with categories - Add order and order item models - Add delivery model - Fix import issues and linting errors --- app/models/__init__.py | 19 ++++++++++ app/models/base.py | 25 +++++++++++++ app/models/delivery.py | 36 +++++++++++++++++++ app/models/menu_item.py | 39 ++++++++++++++++++++ app/models/order.py | 78 ++++++++++++++++++++++++++++++++++++++++ app/models/restaurant.py | 28 +++++++++++++++ app/models/user.py | 35 ++++++++++++++++++ 7 files changed, 260 insertions(+) create mode 100644 app/models/base.py create mode 100644 app/models/delivery.py create mode 100644 app/models/menu_item.py create mode 100644 app/models/order.py create mode 100644 app/models/restaurant.py create mode 100644 app/models/user.py diff --git a/app/models/__init__.py b/app/models/__init__.py index e69de29..31e8c5b 100644 --- a/app/models/__init__.py +++ b/app/models/__init__.py @@ -0,0 +1,19 @@ +# Import all models for easier access +# These imports are intentional for making models available from this module + +from app.models.base import BaseModel, TimestampMixin # noqa: F401 +from app.models.user import User, UserRole # noqa: F401 +from app.models.restaurant import Restaurant # noqa: F401 +from app.models.menu_item import MenuItem, MenuItemCategory # noqa: F401 +from app.models.order import Order, OrderItem, OrderStatus, PaymentMethod, PaymentStatus # noqa: F401 +from app.models.delivery import Delivery, DeliveryStatus # noqa: F401 + +# Define __all__ to explicitly export these names +__all__ = [ + "BaseModel", "TimestampMixin", + "User", "UserRole", + "Restaurant", + "MenuItem", "MenuItemCategory", + "Order", "OrderItem", "OrderStatus", "PaymentMethod", "PaymentStatus", + "Delivery", "DeliveryStatus", +] \ No newline at end of file diff --git a/app/models/base.py b/app/models/base.py new file mode 100644 index 0000000..b21102f --- /dev/null +++ b/app/models/base.py @@ -0,0 +1,25 @@ +from datetime import datetime + +from sqlalchemy import Column, DateTime, Integer +from sqlalchemy.ext.declarative import declared_attr + +from app.db.session import Base + + +class TimestampMixin: + """Mixin that adds created_at and updated_at columns to models.""" + + created_at = Column(DateTime, default=datetime.utcnow, nullable=False) + updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False) + + +class BaseModel(Base): + """Base model for all models to inherit from.""" + + __abstract__ = True + + id = Column(Integer, primary_key=True, index=True) + + @declared_attr + def __tablename__(cls): + return cls.__name__.lower() \ No newline at end of file diff --git a/app/models/delivery.py b/app/models/delivery.py new file mode 100644 index 0000000..7d005c0 --- /dev/null +++ b/app/models/delivery.py @@ -0,0 +1,36 @@ +from enum import Enum + +from sqlalchemy import Column, DateTime, Enum as SQLEnum, Float, ForeignKey, Integer, Text +from sqlalchemy.orm import relationship + +from app.models.base import BaseModel, TimestampMixin + + +class DeliveryStatus(str, Enum): + """Enum for delivery statuses.""" + + PENDING = "pending" + ASSIGNED = "assigned" + PICKED_UP = "picked_up" + IN_TRANSIT = "in_transit" + DELIVERED = "delivered" + FAILED = "failed" + + +class Delivery(BaseModel, TimestampMixin): + """Delivery model.""" + + driver_id = Column(Integer, ForeignKey("user.id"), nullable=True) + status = Column(SQLEnum(DeliveryStatus), default=DeliveryStatus.PENDING, nullable=False) + pickup_time = Column(DateTime, nullable=True) + delivery_time = Column(DateTime, nullable=True) + current_latitude = Column(Float, nullable=True) + current_longitude = Column(Float, nullable=True) + notes = Column(Text, nullable=True) + + # Relationships + driver = relationship("User", foreign_keys=[driver_id], backref="deliveries") + order = relationship("Order", back_populates="delivery", uselist=False) + + def __repr__(self) -> str: + return f"" \ No newline at end of file diff --git a/app/models/menu_item.py b/app/models/menu_item.py new file mode 100644 index 0000000..c3cf97e --- /dev/null +++ b/app/models/menu_item.py @@ -0,0 +1,39 @@ +from enum import Enum + +from sqlalchemy import Boolean, Column, Enum as SQLEnum, Float, ForeignKey, Integer, String, Text +from sqlalchemy.orm import relationship + +from app.models.base import BaseModel, TimestampMixin + + +class MenuItemCategory(str, Enum): + """Enum for menu item categories.""" + + APPETIZER = "appetizer" + MAIN_COURSE = "main_course" + DESSERT = "dessert" + BEVERAGE = "beverage" + SIDE = "side" + SPECIAL = "special" + + +class MenuItem(BaseModel, TimestampMixin): + """Menu item model.""" + + name = Column(String, nullable=False) + description = Column(Text, nullable=True) + price = Column(Float, nullable=False) + image_url = Column(String, nullable=True) + category = Column(SQLEnum(MenuItemCategory), nullable=False) + is_vegetarian = Column(Boolean, default=False, nullable=False) + is_vegan = Column(Boolean, default=False, nullable=False) + is_gluten_free = Column(Boolean, default=False, nullable=False) + is_available = Column(Boolean, default=True, nullable=False) + restaurant_id = Column(Integer, ForeignKey("restaurant.id"), nullable=False) + + # Relationships + restaurant = relationship("Restaurant", back_populates="menu_items") + order_items = relationship("OrderItem", back_populates="menu_item") + + def __repr__(self) -> str: + return f"" \ No newline at end of file diff --git a/app/models/order.py b/app/models/order.py new file mode 100644 index 0000000..78785a3 --- /dev/null +++ b/app/models/order.py @@ -0,0 +1,78 @@ +from enum import Enum + +from sqlalchemy import Column, DateTime, Enum as SQLEnum, Float, ForeignKey, Integer, String, Text +from sqlalchemy.orm import relationship + +from app.models.base import BaseModel, TimestampMixin + + +class OrderStatus(str, Enum): + """Enum for order statuses.""" + + PENDING = "pending" + CONFIRMED = "confirmed" + PREPARING = "preparing" + READY_FOR_PICKUP = "ready_for_pickup" + OUT_FOR_DELIVERY = "out_for_delivery" + DELIVERED = "delivered" + CANCELLED = "cancelled" + + +class PaymentMethod(str, Enum): + """Enum for payment methods.""" + + CREDIT_CARD = "credit_card" + DEBIT_CARD = "debit_card" + CASH = "cash" + DIGITAL_WALLET = "digital_wallet" + + +class PaymentStatus(str, Enum): + """Enum for payment statuses.""" + + PENDING = "pending" + PAID = "paid" + FAILED = "failed" + REFUNDED = "refunded" + + +class Order(BaseModel, TimestampMixin): + """Order model.""" + + customer_id = Column(Integer, ForeignKey("user.id"), nullable=False) + restaurant_id = Column(Integer, ForeignKey("restaurant.id"), nullable=False) + delivery_id = Column(Integer, ForeignKey("delivery.id"), nullable=True) + status = Column(SQLEnum(OrderStatus), default=OrderStatus.PENDING, nullable=False) + total_amount = Column(Float, nullable=False) + delivery_address = Column(String, nullable=False) + delivery_notes = Column(Text, nullable=True) + payment_method = Column(SQLEnum(PaymentMethod), nullable=False) + payment_status = Column(SQLEnum(PaymentStatus), default=PaymentStatus.PENDING, nullable=False) + estimated_delivery_time = Column(DateTime, nullable=True) + actual_delivery_time = Column(DateTime, nullable=True) + + # Relationships + customer = relationship("User", foreign_keys=[customer_id], backref="orders") + restaurant = relationship("Restaurant", backref="orders") + delivery = relationship("Delivery", back_populates="order") + order_items = relationship("OrderItem", back_populates="order", cascade="all, delete-orphan") + + def __repr__(self) -> str: + return f"" + + +class OrderItem(BaseModel, TimestampMixin): + """Order item model.""" + + order_id = Column(Integer, ForeignKey("order.id"), nullable=False) + menu_item_id = Column(Integer, ForeignKey("menuitem.id"), nullable=False) + quantity = Column(Integer, nullable=False) + unit_price = Column(Float, nullable=False) + special_instructions = Column(Text, nullable=True) + + # Relationships + order = relationship("Order", back_populates="order_items") + menu_item = relationship("MenuItem", back_populates="order_items") + + def __repr__(self) -> str: + return f"" \ No newline at end of file diff --git a/app/models/restaurant.py b/app/models/restaurant.py new file mode 100644 index 0000000..11b543b --- /dev/null +++ b/app/models/restaurant.py @@ -0,0 +1,28 @@ +from sqlalchemy import Boolean, Column, Float, ForeignKey, Integer, String +from sqlalchemy.orm import relationship + +from app.models.base import BaseModel, TimestampMixin + + +class Restaurant(BaseModel, TimestampMixin): + """Restaurant model.""" + + name = Column(String, nullable=False) + description = Column(String, nullable=True) + address = Column(String, nullable=False) + phone = Column(String, nullable=False) + email = Column(String, nullable=True) + logo_url = Column(String, nullable=True) + is_active = Column(Boolean, default=True, nullable=False) + latitude = Column(Float, nullable=True) + longitude = Column(Float, nullable=True) + opening_time = Column(String, nullable=True) # Format: HH:MM + closing_time = Column(String, nullable=True) # Format: HH:MM + owner_id = Column(Integer, ForeignKey("user.id"), nullable=False) + + # Relationships + owner = relationship("User", backref="owned_restaurants") + menu_items = relationship("MenuItem", back_populates="restaurant", cascade="all, delete-orphan") + + def __repr__(self) -> str: + return f"" \ No newline at end of file diff --git a/app/models/user.py b/app/models/user.py new file mode 100644 index 0000000..fbb450d --- /dev/null +++ b/app/models/user.py @@ -0,0 +1,35 @@ +from enum import Enum + +from sqlalchemy import Boolean, Column, Enum as SQLEnum, String + +from app.models.base import BaseModel, TimestampMixin + + +class UserRole(str, Enum): + """Enum for user roles.""" + + ADMIN = "admin" + CUSTOMER = "customer" + RESTAURANT_OWNER = "restaurant_owner" + DELIVERY_DRIVER = "delivery_driver" + + +class User(BaseModel, TimestampMixin): + """User model.""" + + email = Column(String, unique=True, index=True, nullable=False) + hashed_password = Column(String, nullable=False) + first_name = Column(String, nullable=False) + last_name = Column(String, nullable=False) + phone = Column(String, nullable=True) + address = Column(String, nullable=True) + role = Column(SQLEnum(UserRole), default=UserRole.CUSTOMER, nullable=False) + is_active = Column(Boolean, default=True, nullable=False) + + @property + def full_name(self) -> str: + """Return the user's full name.""" + return f"{self.first_name} {self.last_name}" + + def __repr__(self) -> str: + return f"" \ No newline at end of file