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:
parent
a0217b10ac
commit
5570e6e49e
@ -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
25
app/models/base.py
Normal 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
36
app/models/delivery.py
Normal 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
39
app/models/menu_item.py
Normal 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
78
app/models/order.py
Normal 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
28
app/models/restaurant.py
Normal 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
35
app/models/user.py
Normal 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}>"
|
Loading…
x
Reference in New Issue
Block a user