add grocery to product

This commit is contained in:
2025-05-26 21:05:15 +02:00
parent 25c09dfecc
commit 7e24d58a94
11 changed files with 661 additions and 84 deletions

View File

@@ -26,14 +26,18 @@ app.add_middleware(
def build_shopping_event_response(event: models.ShoppingEvent, db: Session) -> schemas.ShoppingEventResponse:
"""Build a shopping event response with products from the association table"""
# Get products with their event-specific data including brand information
# Get products with their event-specific data including grocery and brand information
product_data = db.execute(
text("""
SELECT p.id, p.name, p.category, p.organic, p.weight, p.weight_unit,
sep.amount, sep.price, b.id as brand_id, b.name as brand_name,
SELECT p.id, p.name, p.organic, p.weight, p.weight_unit,
sep.amount, sep.price,
g.id as grocery_id, g.name as grocery_name, g.category as grocery_category,
g.created_at as grocery_created_at, g.updated_at as grocery_updated_at,
b.id as brand_id, b.name as brand_name,
b.created_at as brand_created_at, b.updated_at as brand_updated_at
FROM products p
JOIN shopping_event_products sep ON p.id = sep.product_id
JOIN groceries g ON p.grocery_id = g.id
LEFT JOIN brands b ON p.brand_id = b.id
WHERE sep.shopping_event_id = :event_id
"""),
@@ -43,6 +47,14 @@ def build_shopping_event_response(event: models.ShoppingEvent, db: Session) -> s
# Convert to ProductWithEventData objects
products_with_data = []
for row in product_data:
grocery = schemas.Grocery(
id=row.grocery_id,
name=row.grocery_name,
category=row.grocery_category,
created_at=row.grocery_created_at,
updated_at=row.grocery_updated_at
)
brand = None
if row.brand_id is not None:
brand = schemas.Brand(
@@ -56,7 +68,7 @@ def build_shopping_event_response(event: models.ShoppingEvent, db: Session) -> s
schemas.ProductWithEventData(
id=row.id,
name=row.name,
category=row.category,
grocery=grocery,
brand=brand,
organic=row.organic,
weight=row.weight,
@@ -85,6 +97,11 @@ def read_root():
# Product endpoints
@app.post("/products/", response_model=schemas.Product)
def create_product(product: schemas.ProductCreate, db: Session = Depends(get_db)):
# Validate grocery exists
grocery = db.query(models.Grocery).filter(models.Grocery.id == product.grocery_id).first()
if grocery is None:
raise HTTPException(status_code=404, detail="Grocery not found")
# Validate brand exists if brand_id is provided
if product.brand_id is not None:
brand = db.query(models.Brand).filter(models.Brand.id == product.brand_id).first()
@@ -115,8 +132,15 @@ def update_product(product_id: int, product_update: schemas.ProductUpdate, db: S
if product is None:
raise HTTPException(status_code=404, detail="Product not found")
# Validate brand exists if brand_id is being updated
update_data = product_update.dict(exclude_unset=True)
# Validate grocery exists if grocery_id is being updated
if 'grocery_id' in update_data:
grocery = db.query(models.Grocery).filter(models.Grocery.id == update_data['grocery_id']).first()
if grocery is None:
raise HTTPException(status_code=404, detail="Grocery not found")
# Validate brand exists if brand_id is being updated
if 'brand_id' in update_data and update_data['brand_id'] is not None:
brand = db.query(models.Brand).filter(models.Brand.id == update_data['brand_id']).first()
if brand is None:
@@ -237,6 +261,59 @@ def delete_brand(brand_id: int, db: Session = Depends(get_db)):
db.commit()
return {"message": "Brand deleted successfully"}
# Grocery endpoints
@app.post("/groceries/", response_model=schemas.Grocery)
def create_grocery(grocery: schemas.GroceryCreate, db: Session = Depends(get_db)):
db_grocery = models.Grocery(**grocery.dict())
db.add(db_grocery)
db.commit()
db.refresh(db_grocery)
return db_grocery
@app.get("/groceries/", response_model=List[schemas.Grocery])
def read_groceries(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
groceries = db.query(models.Grocery).offset(skip).limit(limit).all()
return groceries
@app.get("/groceries/{grocery_id}", response_model=schemas.Grocery)
def read_grocery(grocery_id: int, db: Session = Depends(get_db)):
grocery = db.query(models.Grocery).filter(models.Grocery.id == grocery_id).first()
if grocery is None:
raise HTTPException(status_code=404, detail="Grocery not found")
return grocery
@app.put("/groceries/{grocery_id}", response_model=schemas.Grocery)
def update_grocery(grocery_id: int, grocery_update: schemas.GroceryUpdate, db: Session = Depends(get_db)):
grocery = db.query(models.Grocery).filter(models.Grocery.id == grocery_id).first()
if grocery is None:
raise HTTPException(status_code=404, detail="Grocery not found")
update_data = grocery_update.dict(exclude_unset=True)
for field, value in update_data.items():
setattr(grocery, field, value)
db.commit()
db.refresh(grocery)
return grocery
@app.delete("/groceries/{grocery_id}")
def delete_grocery(grocery_id: int, db: Session = Depends(get_db)):
grocery = db.query(models.Grocery).filter(models.Grocery.id == grocery_id).first()
if grocery is None:
raise HTTPException(status_code=404, detail="Grocery not found")
# Check if any products reference this grocery
products_with_grocery = db.query(models.Product).filter(models.Product.grocery_id == grocery_id).first()
if products_with_grocery:
raise HTTPException(
status_code=400,
detail="Cannot delete grocery: products are still associated with this grocery"
)
db.delete(grocery)
db.commit()
return {"message": "Grocery deleted successfully"}
# Shopping Event endpoints
@app.post("/shopping-events/", response_model=schemas.ShoppingEventResponse)
def create_shopping_event(event: schemas.ShoppingEventCreate, db: Session = Depends(get_db)):

View File

@@ -28,12 +28,24 @@ class Brand(Base):
# Relationships
products = relationship("Product", back_populates="brand")
class Grocery(Base):
__tablename__ = "groceries"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, nullable=False, index=True)
category = Column(String, nullable=False)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
# Relationships
products = relationship("Product", back_populates="grocery")
class Product(Base):
__tablename__ = "products"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, nullable=False, index=True)
category = Column(String, nullable=False)
grocery_id = Column(Integer, ForeignKey("groceries.id"), nullable=False)
brand_id = Column(Integer, ForeignKey("brands.id"), nullable=True)
organic = Column(Boolean, default=False)
weight = Column(Float, nullable=True) # in grams or kg
@@ -42,6 +54,7 @@ class Product(Base):
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
# Relationships
grocery = relationship("Grocery", back_populates="products")
brand = relationship("Brand", back_populates="products")
shopping_events = relationship("ShoppingEvent", secondary=shopping_event_products, back_populates="products")

View File

@@ -20,10 +20,30 @@ class Brand(BrandBase):
class Config:
from_attributes = True
# Grocery schemas
class GroceryBase(BaseModel):
name: str
category: str
class GroceryCreate(GroceryBase):
pass
class GroceryUpdate(BaseModel):
name: Optional[str] = None
category: Optional[str] = None
class Grocery(GroceryBase):
id: int
created_at: datetime
updated_at: Optional[datetime] = None
class Config:
from_attributes = True
# Base schemas
class ProductBase(BaseModel):
name: str
category: str
grocery_id: int
brand_id: Optional[int] = None
organic: bool = False
weight: Optional[float] = None
@@ -34,7 +54,7 @@ class ProductCreate(ProductBase):
class ProductUpdate(BaseModel):
name: Optional[str] = None
category: Optional[str] = None
grocery_id: Optional[int] = None
brand_id: Optional[int] = None
organic: Optional[bool] = None
weight: Optional[float] = None
@@ -44,6 +64,7 @@ class Product(ProductBase):
id: int
created_at: datetime
updated_at: Optional[datetime] = None
grocery: Grocery
brand: Optional[Brand] = None
class Config:
@@ -80,7 +101,7 @@ class ProductInEvent(BaseModel):
class ProductWithEventData(BaseModel):
id: int
name: str
category: str
grocery: Grocery
brand: Optional[Brand] = None
organic: bool
weight: Optional[float] = None