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
This commit is contained in:
Automated Action 2025-05-31 03:41:37 +00:00
parent a0217b10ac
commit 5570e6e49e
7 changed files with 260 additions and 0 deletions

View File

@ -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",
]

25
app/models/base.py Normal file
View File

@ -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()

36
app/models/delivery.py Normal file
View File

@ -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"<Delivery {self.id}>"

39
app/models/menu_item.py Normal file
View File

@ -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"<MenuItem {self.name}>"

78
app/models/order.py Normal file
View File

@ -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"<Order {self.id}>"
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"<OrderItem {self.id}>"

28
app/models/restaurant.py Normal file
View File

@ -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"<Restaurant {self.name}>"

35
app/models/user.py Normal file
View File

@ -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"<User {self.email}>"