Big refactor of gift categories model
This commit is contained in:
@@ -0,0 +1,87 @@
|
|||||||
|
"""refactor_gift_categories_to_support_multiple_events
|
||||||
|
|
||||||
|
Revision ID: 60c6bfef5416
|
||||||
|
Revises: 38bf9e7e74b3
|
||||||
|
Create Date: 2025-03-16 14:48:08.137051
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.dialects import postgresql
|
||||||
|
from sqlalchemy import text
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = '60c6bfef5416'
|
||||||
|
down_revision: Union[str, None] = '38bf9e7e74b3'
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table('event_gift_categories',
|
||||||
|
sa.Column('event_id', sa.UUID(), nullable=False),
|
||||||
|
sa.Column('category_id', sa.UUID(), nullable=False),
|
||||||
|
sa.Column('display_order', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('is_visible', sa.Boolean(), nullable=True),
|
||||||
|
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(['category_id'], ['gift_categories.id'], ),
|
||||||
|
sa.ForeignKeyConstraint(['event_id'], ['events.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('event_id', 'category_id')
|
||||||
|
)
|
||||||
|
|
||||||
|
# Migrate existing data from gift_categories to event_gift_categories
|
||||||
|
# Get connection
|
||||||
|
connection = op.get_bind()
|
||||||
|
|
||||||
|
# Get all existing gift categories
|
||||||
|
categories = connection.execute(
|
||||||
|
text("SELECT id, event_id, display_order, is_visible FROM gift_categories")
|
||||||
|
).fetchall()
|
||||||
|
|
||||||
|
# Insert data into the new association table
|
||||||
|
for category in categories:
|
||||||
|
category_id = category[0]
|
||||||
|
event_id = category[1]
|
||||||
|
display_order = category[2] if category[2] is not None else 0
|
||||||
|
is_visible = category[3] if category[3] is not None else True
|
||||||
|
now = datetime.now().isoformat()
|
||||||
|
|
||||||
|
connection.execute(
|
||||||
|
text(f"""
|
||||||
|
INSERT INTO event_gift_categories
|
||||||
|
(event_id, category_id, display_order, is_visible, created_at)
|
||||||
|
VALUES
|
||||||
|
('{event_id}', '{category_id}', {display_order}, {is_visible}, '{now}')
|
||||||
|
""")
|
||||||
|
)
|
||||||
|
|
||||||
|
# Now we can safely modify the gift_categories table
|
||||||
|
op.alter_column('event_themes', 'asset_image_urls',
|
||||||
|
existing_type=postgresql.JSON(astext_type=sa.Text()),
|
||||||
|
nullable=True)
|
||||||
|
op.drop_constraint('uq_event_category_name', 'gift_categories', type_='unique')
|
||||||
|
op.create_unique_constraint(None, 'gift_categories', ['name'])
|
||||||
|
op.drop_constraint('gift_categories_event_id_fkey', 'gift_categories', type_='foreignkey')
|
||||||
|
op.drop_column('gift_categories', 'display_order')
|
||||||
|
op.drop_column('gift_categories', 'event_id')
|
||||||
|
op.drop_column('gift_categories', 'is_visible')
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('gift_categories', sa.Column('is_visible', sa.BOOLEAN(), autoincrement=False, nullable=True))
|
||||||
|
op.add_column('gift_categories', sa.Column('event_id', sa.UUID(), autoincrement=False, nullable=False))
|
||||||
|
op.add_column('gift_categories', sa.Column('display_order', sa.INTEGER(), autoincrement=False, nullable=True))
|
||||||
|
op.create_foreign_key('gift_categories_event_id_fkey', 'gift_categories', 'events', ['event_id'], ['id'])
|
||||||
|
op.drop_constraint(None, 'gift_categories', type_='unique')
|
||||||
|
op.create_unique_constraint('uq_event_category_name', 'gift_categories', ['event_id', 'name'])
|
||||||
|
op.alter_column('event_themes', 'asset_image_urls',
|
||||||
|
existing_type=postgresql.JSON(astext_type=sa.Text()),
|
||||||
|
nullable=False)
|
||||||
|
op.drop_table('event_gift_categories')
|
||||||
|
# ### end Alembic commands ###
|
||||||
@@ -1,19 +1,20 @@
|
|||||||
from typing import List, Optional, Dict, Any
|
from typing import List, Optional, Dict, Any, Union
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Query, Path
|
from fastapi import APIRouter, Depends, HTTPException, Query, Path
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.api.dependencies.auth import get_current_active_user, get_current_user
|
from app.api.dependencies.auth import get_current_active_user, get_current_user
|
||||||
from app.crud.gift import gift_item_crud, gift_category_crud, gift_purchase_crud
|
from app.crud.gift import gift_item_crud, gift_category_crud, gift_purchase_crud, event_gift_category_crud
|
||||||
from app.crud.guest import guest_crud
|
from app.crud.guest import guest_crud
|
||||||
from app.crud.event import event_crud
|
from app.crud.event import event_crud
|
||||||
from app.models.gift import GiftStatus, GiftPriority
|
from app.models.gift import GiftStatus, GiftPriority, EventGiftCategory, GiftItem
|
||||||
from app.models.user import User
|
from app.models.user import User
|
||||||
from app.schemas.gifts import (
|
from app.schemas.gifts import (
|
||||||
GiftItem, GiftItemCreate, GiftItemUpdate,
|
GiftItem, GiftItemCreate, GiftItemUpdate,
|
||||||
GiftCategory, GiftCategoryCreate, GiftCategoryUpdate,
|
GiftCategory, GiftCategoryCreate, GiftCategoryUpdate,
|
||||||
GiftPurchase, GiftPurchaseCreate, GiftPurchaseUpdate
|
GiftPurchase, GiftPurchaseCreate, GiftPurchaseUpdate,
|
||||||
|
EventGiftCategoryCreate, EventGiftCategoryUpdate, EventGiftCategoryInDB
|
||||||
)
|
)
|
||||||
from app.core.database import get_db
|
from app.core.database import get_db
|
||||||
|
|
||||||
@@ -26,13 +27,14 @@ def create_gift_category(
|
|||||||
*,
|
*,
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
category_in: GiftCategoryCreate,
|
category_in: GiftCategoryCreate,
|
||||||
|
event_id: UUID,
|
||||||
current_user: User = Depends(get_current_active_user)
|
current_user: User = Depends(get_current_active_user)
|
||||||
) -> Any:
|
) -> Any:
|
||||||
"""
|
"""
|
||||||
Create new gift category.
|
Create new gift category and associate it with an event.
|
||||||
"""
|
"""
|
||||||
# Check if user has permission to manage this event
|
# Check if user has permission to manage this event
|
||||||
event = event_crud.get(db, category_in.event_id)
|
event = event_crud.get(db, event_id)
|
||||||
if not event:
|
if not event:
|
||||||
raise HTTPException(status_code=404, detail="Event not found")
|
raise HTTPException(status_code=404, detail="Event not found")
|
||||||
|
|
||||||
@@ -41,7 +43,23 @@ def create_gift_category(
|
|||||||
if event.created_by != current_user.id:
|
if event.created_by != current_user.id:
|
||||||
raise HTTPException(status_code=403, detail="Not enough permissions")
|
raise HTTPException(status_code=403, detail="Not enough permissions")
|
||||||
|
|
||||||
return gift_category_crud.create(db, obj_in=category_in)
|
# Create the category
|
||||||
|
category = gift_category_crud.create(db, obj_in=category_in)
|
||||||
|
|
||||||
|
# Create the association between the category and the event
|
||||||
|
association_data = EventGiftCategoryCreate(
|
||||||
|
event_id=event_id,
|
||||||
|
category_id=category.id,
|
||||||
|
display_order=0, # Default display order
|
||||||
|
is_visible=True # Default visibility
|
||||||
|
)
|
||||||
|
event_gift_category_crud.create(db, obj_in=association_data)
|
||||||
|
|
||||||
|
# Set display properties for the response
|
||||||
|
category.display_order = 0
|
||||||
|
category.is_visible = True
|
||||||
|
|
||||||
|
return category
|
||||||
|
|
||||||
|
|
||||||
@router.get("/categories/event/{event_id}", response_model=List[GiftCategory])
|
@router.get("/categories/event/{event_id}", response_model=List[GiftCategory])
|
||||||
@@ -67,11 +85,34 @@ def read_gift_categories(
|
|||||||
if not current_user and not event.is_public:
|
if not current_user and not event.is_public:
|
||||||
raise HTTPException(status_code=403, detail="Not enough permissions")
|
raise HTTPException(status_code=403, detail="Not enough permissions")
|
||||||
|
|
||||||
# Get categories
|
# Get categories for this event using the association table
|
||||||
categories = gift_category_crud.get_multi_by_event(
|
categories = event_gift_category_crud.get_categories_by_event(
|
||||||
db, event_id=event_id, skip=skip, limit=limit, include_hidden=include_hidden
|
db, event_id=event_id, skip=skip, limit=limit, include_hidden=include_hidden
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Enhance categories with display information from the association
|
||||||
|
for category in categories:
|
||||||
|
# Get the association to access display_order and is_visible
|
||||||
|
association = event_gift_category_crud.get(
|
||||||
|
db, event_id=event_id, category_id=category.id
|
||||||
|
)
|
||||||
|
if association:
|
||||||
|
category.display_order = association.display_order
|
||||||
|
category.is_visible = association.is_visible
|
||||||
|
|
||||||
|
# Calculate statistics for this event
|
||||||
|
total_gifts = 0
|
||||||
|
available_gifts = 0
|
||||||
|
if category.gifts:
|
||||||
|
for gift in category.gifts:
|
||||||
|
if gift.event_id == event_id:
|
||||||
|
total_gifts += 1
|
||||||
|
if gift.status == GiftStatus.AVAILABLE and gift.is_visible:
|
||||||
|
available_gifts += 1
|
||||||
|
|
||||||
|
category.total_gifts = total_gifts
|
||||||
|
category.available_gifts = available_gifts
|
||||||
|
|
||||||
# If include_gifts is true, fetch gift items for each category
|
# If include_gifts is true, fetch gift items for each category
|
||||||
if include_gifts:
|
if include_gifts:
|
||||||
for category in categories:
|
for category in categories:
|
||||||
@@ -89,31 +130,79 @@ def read_gift_category(
|
|||||||
*,
|
*,
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
category_id: UUID = Path(...),
|
category_id: UUID = Path(...),
|
||||||
|
event_id: Optional[UUID] = None,
|
||||||
include_gifts: bool = False,
|
include_gifts: bool = False,
|
||||||
current_user: Optional[User] = Depends(get_current_user)
|
current_user: Optional[User] = Depends(get_current_user)
|
||||||
) -> Any:
|
) -> Any:
|
||||||
"""
|
"""
|
||||||
Get gift category by ID.
|
Get gift category by ID. If event_id is provided, includes event-specific display settings.
|
||||||
"""
|
"""
|
||||||
category = gift_category_crud.get(db, id=category_id)
|
category = gift_category_crud.get(db, id=category_id)
|
||||||
if not category:
|
if not category:
|
||||||
raise HTTPException(status_code=404, detail="Gift category not found")
|
raise HTTPException(status_code=404, detail="Gift category not found")
|
||||||
|
|
||||||
# Check if event is public or user is authorized
|
# Initialize event-specific properties
|
||||||
event = event_crud.get(db, category.event_id)
|
category.display_order = None
|
||||||
if not event:
|
category.is_visible = None
|
||||||
raise HTTPException(status_code=404, detail="Event not found")
|
category.total_gifts = None
|
||||||
|
category.available_gifts = None
|
||||||
|
|
||||||
if not event.is_public and not current_user:
|
# If event_id is provided, get event-specific information
|
||||||
raise HTTPException(status_code=403, detail="Not enough permissions")
|
if event_id:
|
||||||
|
# Check if event exists and is accessible
|
||||||
|
event = event_crud.get(db, event_id)
|
||||||
|
if not event:
|
||||||
|
raise HTTPException(status_code=404, detail="Event not found")
|
||||||
|
|
||||||
# If include_gifts is true, fetch gift items for the category
|
# For public access, ensure event is public
|
||||||
if include_gifts:
|
if not event.is_public and not current_user:
|
||||||
gifts = gift_item_crud.get_multi_by_event(
|
raise HTTPException(status_code=403, detail="Not enough permissions")
|
||||||
db, event_id=category.event_id, category_id=category.id,
|
|
||||||
include_hidden=current_user is not None # Only include hidden for logged-in users
|
# Check if this category is associated with the event
|
||||||
)
|
association = event_gift_category_crud.get(db, event_id=event_id, category_id=category_id)
|
||||||
# Set the gifts attribute which is initially None in the model
|
if not association:
|
||||||
|
raise HTTPException(status_code=404, detail="Category not associated with this event")
|
||||||
|
|
||||||
|
# Set event-specific display properties
|
||||||
|
category.display_order = association.display_order
|
||||||
|
category.is_visible = association.is_visible
|
||||||
|
|
||||||
|
# Calculate statistics for this event
|
||||||
|
total_gifts = 0
|
||||||
|
available_gifts = 0
|
||||||
|
|
||||||
|
# If include_gifts is true, fetch gift items for the category in this event
|
||||||
|
if include_gifts:
|
||||||
|
gifts = gift_item_crud.get_multi_by_event(
|
||||||
|
db, event_id=event_id, category_id=category.id,
|
||||||
|
include_hidden=current_user is not None # Only include hidden for logged-in users
|
||||||
|
)
|
||||||
|
# Set the gifts attribute
|
||||||
|
setattr(category, "gifts", gifts)
|
||||||
|
|
||||||
|
# Calculate statistics
|
||||||
|
for gift in gifts:
|
||||||
|
total_gifts += 1
|
||||||
|
if gift.status == GiftStatus.AVAILABLE and gift.is_visible:
|
||||||
|
available_gifts += 1
|
||||||
|
else:
|
||||||
|
# Calculate statistics without fetching all gifts
|
||||||
|
gifts_query = db.query(GiftItem).filter(
|
||||||
|
GiftItem.event_id == event_id,
|
||||||
|
GiftItem.category_id == category_id
|
||||||
|
)
|
||||||
|
total_gifts = gifts_query.count()
|
||||||
|
available_gifts = gifts_query.filter(
|
||||||
|
GiftItem.status == GiftStatus.AVAILABLE,
|
||||||
|
GiftItem.is_visible == True
|
||||||
|
).count()
|
||||||
|
|
||||||
|
category.total_gifts = total_gifts
|
||||||
|
category.available_gifts = available_gifts
|
||||||
|
elif include_gifts:
|
||||||
|
# If no event_id but include_gifts is true, just get all gifts for this category
|
||||||
|
# This is less useful without event context but included for completeness
|
||||||
|
gifts = db.query(GiftItem).filter(GiftItem.category_id == category_id).all()
|
||||||
setattr(category, "gifts", gifts)
|
setattr(category, "gifts", gifts)
|
||||||
|
|
||||||
return category
|
return category
|
||||||
@@ -125,25 +214,76 @@ def update_gift_category(
|
|||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
category_id: UUID = Path(...),
|
category_id: UUID = Path(...),
|
||||||
category_in: GiftCategoryUpdate,
|
category_in: GiftCategoryUpdate,
|
||||||
|
event_id: Optional[UUID] = None,
|
||||||
current_user: User = Depends(get_current_active_user)
|
current_user: User = Depends(get_current_active_user)
|
||||||
) -> Any:
|
) -> Any:
|
||||||
"""
|
"""
|
||||||
Update a gift category.
|
Update a gift category. If event_id is provided, also updates the event-specific settings.
|
||||||
"""
|
"""
|
||||||
category = gift_category_crud.get(db, id=category_id)
|
category = gift_category_crud.get(db, id=category_id)
|
||||||
if not category:
|
if not category:
|
||||||
raise HTTPException(status_code=404, detail="Gift category not found")
|
raise HTTPException(status_code=404, detail="Gift category not found")
|
||||||
|
|
||||||
# Check if user has permission to manage this event
|
# Update the category itself
|
||||||
event = event_crud.get(db, category.event_id)
|
updated_category = gift_category_crud.update(db, db_obj=category, obj_in=category_in)
|
||||||
if not event:
|
|
||||||
raise HTTPException(status_code=404, detail="Event not found")
|
|
||||||
|
|
||||||
# Check permissions (basic implementation)
|
# Initialize event-specific properties for the response
|
||||||
if event.created_by != current_user.id:
|
updated_category.display_order = None
|
||||||
raise HTTPException(status_code=403, detail="Not enough permissions")
|
updated_category.is_visible = None
|
||||||
|
updated_category.total_gifts = None
|
||||||
|
updated_category.available_gifts = None
|
||||||
|
|
||||||
return gift_category_crud.update(db, db_obj=category, obj_in=category_in)
|
# If event_id is provided, update the event-specific settings
|
||||||
|
if event_id:
|
||||||
|
# Check if event exists
|
||||||
|
event = event_crud.get(db, event_id)
|
||||||
|
if not event:
|
||||||
|
raise HTTPException(status_code=404, detail="Event not found")
|
||||||
|
|
||||||
|
# Check permissions (basic implementation)
|
||||||
|
if event.created_by != current_user.id:
|
||||||
|
raise HTTPException(status_code=403, detail="Not enough permissions")
|
||||||
|
|
||||||
|
# Check if this category is associated with the event
|
||||||
|
association = event_gift_category_crud.get(db, event_id=event_id, category_id=category_id)
|
||||||
|
if not association:
|
||||||
|
# If not associated, create the association
|
||||||
|
association_data = EventGiftCategoryCreate(
|
||||||
|
event_id=event_id,
|
||||||
|
category_id=category_id,
|
||||||
|
display_order=0, # Default display order
|
||||||
|
is_visible=True # Default visibility
|
||||||
|
)
|
||||||
|
association = event_gift_category_crud.create(db, obj_in=association_data)
|
||||||
|
else:
|
||||||
|
# If display_order or is_visible are in the update data, update the association
|
||||||
|
association_update = {}
|
||||||
|
if hasattr(category_in, 'display_order') and category_in.display_order is not None:
|
||||||
|
association_update['display_order'] = category_in.display_order
|
||||||
|
if hasattr(category_in, 'is_visible') and category_in.is_visible is not None:
|
||||||
|
association_update['is_visible'] = category_in.is_visible
|
||||||
|
|
||||||
|
if association_update:
|
||||||
|
association = event_gift_category_crud.update(
|
||||||
|
db, db_obj=association, obj_in=association_update
|
||||||
|
)
|
||||||
|
|
||||||
|
# Set event-specific properties for the response
|
||||||
|
updated_category.display_order = association.display_order
|
||||||
|
updated_category.is_visible = association.is_visible
|
||||||
|
|
||||||
|
# Calculate statistics for this event
|
||||||
|
gifts_query = db.query(GiftItem).filter(
|
||||||
|
GiftItem.event_id == event_id,
|
||||||
|
GiftItem.category_id == category_id
|
||||||
|
)
|
||||||
|
updated_category.total_gifts = gifts_query.count()
|
||||||
|
updated_category.available_gifts = gifts_query.filter(
|
||||||
|
GiftItem.status == GiftStatus.AVAILABLE,
|
||||||
|
GiftItem.is_visible == True
|
||||||
|
).count()
|
||||||
|
|
||||||
|
return updated_category
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/categories/{category_id}", response_model=GiftCategory)
|
@router.delete("/categories/{category_id}", response_model=GiftCategory)
|
||||||
@@ -151,25 +291,242 @@ def delete_gift_category(
|
|||||||
*,
|
*,
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
category_id: UUID = Path(...),
|
category_id: UUID = Path(...),
|
||||||
|
event_id: Optional[UUID] = None,
|
||||||
|
force: bool = False,
|
||||||
current_user: User = Depends(get_current_active_user)
|
current_user: User = Depends(get_current_active_user)
|
||||||
) -> Any:
|
) -> Any:
|
||||||
"""
|
"""
|
||||||
Delete a gift category.
|
Delete a gift category. If event_id is provided, only removes the association with that event.
|
||||||
|
If force=True and no event_id is provided, deletes the category completely.
|
||||||
"""
|
"""
|
||||||
category = gift_category_crud.get(db, id=category_id)
|
category = gift_category_crud.get(db, id=category_id)
|
||||||
if not category:
|
if not category:
|
||||||
raise HTTPException(status_code=404, detail="Gift category not found")
|
raise HTTPException(status_code=404, detail="Gift category not found")
|
||||||
|
|
||||||
# Check if user has permission to manage this event
|
# Make a copy of the category for the response
|
||||||
event = event_crud.get(db, category.event_id)
|
category_copy = GiftCategory.model_validate(category)
|
||||||
|
|
||||||
|
if event_id:
|
||||||
|
# Check if event exists
|
||||||
|
event = event_crud.get(db, event_id)
|
||||||
|
if not event:
|
||||||
|
raise HTTPException(status_code=404, detail="Event not found")
|
||||||
|
|
||||||
|
# Check permissions (basic implementation)
|
||||||
|
if event.created_by != current_user.id:
|
||||||
|
raise HTTPException(status_code=403, detail="Not enough permissions")
|
||||||
|
|
||||||
|
# Check if this category is associated with the event
|
||||||
|
association = event_gift_category_crud.get(db, event_id=event_id, category_id=category_id)
|
||||||
|
if not association:
|
||||||
|
raise HTTPException(status_code=404, detail="Category not associated with this event")
|
||||||
|
|
||||||
|
# Remove the association
|
||||||
|
event_gift_category_crud.remove(db, event_id=event_id, category_id=category_id)
|
||||||
|
|
||||||
|
# Return the category with event-specific properties set to None
|
||||||
|
category_copy.display_order = None
|
||||||
|
category_copy.is_visible = None
|
||||||
|
category_copy.total_gifts = None
|
||||||
|
category_copy.available_gifts = None
|
||||||
|
|
||||||
|
return category_copy
|
||||||
|
elif force:
|
||||||
|
# Check if the user has permission to delete the category
|
||||||
|
# This is a more restrictive operation, so we might want to add additional checks
|
||||||
|
|
||||||
|
# Check if the category is used by any events
|
||||||
|
# Get all associations for this category
|
||||||
|
associations = db.query(EventGiftCategory).filter(
|
||||||
|
EventGiftCategory.category_id == category_id
|
||||||
|
).all()
|
||||||
|
|
||||||
|
if associations and len(associations) > 0:
|
||||||
|
# If there are associations, we need to check if the user has permission to manage all events
|
||||||
|
for assoc in associations:
|
||||||
|
event = event_crud.get(db, assoc.event_id)
|
||||||
|
if not event:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# If the user doesn't have permission for any of the events, deny the operation
|
||||||
|
if event.created_by != current_user.id:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=403,
|
||||||
|
detail="Not enough permissions. Category is used by events you don't manage."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Remove all associations
|
||||||
|
for assoc in associations:
|
||||||
|
event_gift_category_crud.remove(db, event_id=assoc.event_id, category_id=category_id)
|
||||||
|
|
||||||
|
# Now delete the category itself
|
||||||
|
return gift_category_crud.remove(db, id=category_id)
|
||||||
|
else:
|
||||||
|
# If no event_id and not force, raise an error
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail="Must provide event_id to remove association or set force=True to delete category completely"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ===== EVENT-CATEGORY ASSOCIATIONS ===== #
|
||||||
|
|
||||||
|
@router.post("/events/{event_id}/categories/{category_id}", response_model=GiftCategory)
|
||||||
|
def associate_category_with_event(
|
||||||
|
*,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
event_id: UUID = Path(...),
|
||||||
|
category_id: UUID = Path(...),
|
||||||
|
display_order: int = 0,
|
||||||
|
is_visible: bool = True,
|
||||||
|
current_user: User = Depends(get_current_active_user)
|
||||||
|
) -> Any:
|
||||||
|
"""
|
||||||
|
Associate an existing category with an event.
|
||||||
|
"""
|
||||||
|
# Check if event exists
|
||||||
|
event = event_crud.get(db, event_id)
|
||||||
if not event:
|
if not event:
|
||||||
raise HTTPException(status_code=404, detail="Event not found")
|
raise HTTPException(status_code=404, detail="Event not found")
|
||||||
|
|
||||||
# Check permissions (basic implementation)
|
# Check permissions
|
||||||
if event.created_by != current_user.id:
|
if event.created_by != current_user.id:
|
||||||
raise HTTPException(status_code=403, detail="Not enough permissions")
|
raise HTTPException(status_code=403, detail="Not enough permissions")
|
||||||
|
|
||||||
return gift_category_crud.remove(db, id=category_id)
|
# Check if category exists
|
||||||
|
category = gift_category_crud.get(db, id=category_id)
|
||||||
|
if not category:
|
||||||
|
raise HTTPException(status_code=404, detail="Gift category not found")
|
||||||
|
|
||||||
|
# Check if association already exists
|
||||||
|
existing_association = event_gift_category_crud.get(db, event_id=event_id, category_id=category_id)
|
||||||
|
if existing_association:
|
||||||
|
raise HTTPException(status_code=400, detail="Category already associated with this event")
|
||||||
|
|
||||||
|
# Create the association
|
||||||
|
association_data = EventGiftCategoryCreate(
|
||||||
|
event_id=event_id,
|
||||||
|
category_id=category_id,
|
||||||
|
display_order=display_order,
|
||||||
|
is_visible=is_visible
|
||||||
|
)
|
||||||
|
event_gift_category_crud.create(db, obj_in=association_data)
|
||||||
|
|
||||||
|
# Set display properties for the response
|
||||||
|
category.display_order = display_order
|
||||||
|
category.is_visible = is_visible
|
||||||
|
|
||||||
|
# Calculate statistics for this event
|
||||||
|
gifts_query = db.query(GiftItem).filter(
|
||||||
|
GiftItem.event_id == event_id,
|
||||||
|
GiftItem.category_id == category_id
|
||||||
|
)
|
||||||
|
category.total_gifts = gifts_query.count()
|
||||||
|
category.available_gifts = gifts_query.filter(
|
||||||
|
GiftItem.status == GiftStatus.AVAILABLE,
|
||||||
|
GiftItem.is_visible == True
|
||||||
|
).count()
|
||||||
|
|
||||||
|
return category
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("/events/{event_id}/categories/{category_id}", response_model=GiftCategory)
|
||||||
|
def update_category_event_settings(
|
||||||
|
*,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
event_id: UUID = Path(...),
|
||||||
|
category_id: UUID = Path(...),
|
||||||
|
display_order: Optional[int] = None,
|
||||||
|
is_visible: Optional[bool] = None,
|
||||||
|
current_user: User = Depends(get_current_active_user)
|
||||||
|
) -> Any:
|
||||||
|
"""
|
||||||
|
Update the display settings for a category in an event.
|
||||||
|
"""
|
||||||
|
# Check if event exists
|
||||||
|
event = event_crud.get(db, event_id)
|
||||||
|
if not event:
|
||||||
|
raise HTTPException(status_code=404, detail="Event not found")
|
||||||
|
|
||||||
|
# Check permissions
|
||||||
|
if event.created_by != current_user.id:
|
||||||
|
raise HTTPException(status_code=403, detail="Not enough permissions")
|
||||||
|
|
||||||
|
# Check if category exists
|
||||||
|
category = gift_category_crud.get(db, id=category_id)
|
||||||
|
if not category:
|
||||||
|
raise HTTPException(status_code=404, detail="Gift category not found")
|
||||||
|
|
||||||
|
# Check if association exists
|
||||||
|
association = event_gift_category_crud.get(db, event_id=event_id, category_id=category_id)
|
||||||
|
if not association:
|
||||||
|
raise HTTPException(status_code=404, detail="Category not associated with this event")
|
||||||
|
|
||||||
|
# Update the association
|
||||||
|
update_data = {}
|
||||||
|
if display_order is not None:
|
||||||
|
update_data['display_order'] = display_order
|
||||||
|
if is_visible is not None:
|
||||||
|
update_data['is_visible'] = is_visible
|
||||||
|
|
||||||
|
if update_data:
|
||||||
|
association = event_gift_category_crud.update(db, db_obj=association, obj_in=update_data)
|
||||||
|
|
||||||
|
# Set display properties for the response
|
||||||
|
category.display_order = association.display_order
|
||||||
|
category.is_visible = association.is_visible
|
||||||
|
|
||||||
|
# Calculate statistics for this event
|
||||||
|
gifts_query = db.query(GiftItem).filter(
|
||||||
|
GiftItem.event_id == event_id,
|
||||||
|
GiftItem.category_id == category_id
|
||||||
|
)
|
||||||
|
category.total_gifts = gifts_query.count()
|
||||||
|
category.available_gifts = gifts_query.filter(
|
||||||
|
GiftItem.status == GiftStatus.AVAILABLE,
|
||||||
|
GiftItem.is_visible == True
|
||||||
|
).count()
|
||||||
|
|
||||||
|
return category
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/categories/{category_id}/events", response_model=List[Dict[str, Any]])
|
||||||
|
def get_events_for_category(
|
||||||
|
*,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
category_id: UUID = Path(...),
|
||||||
|
current_user: User = Depends(get_current_active_user)
|
||||||
|
) -> Any:
|
||||||
|
"""
|
||||||
|
Get all events that use a specific category.
|
||||||
|
"""
|
||||||
|
# Check if category exists
|
||||||
|
category = gift_category_crud.get(db, id=category_id)
|
||||||
|
if not category:
|
||||||
|
raise HTTPException(status_code=404, detail="Gift category not found")
|
||||||
|
|
||||||
|
# Get all events for this category
|
||||||
|
events = event_gift_category_crud.get_events_by_category(db, category_id=category_id)
|
||||||
|
|
||||||
|
# Filter events that the user has permission to see
|
||||||
|
result = []
|
||||||
|
for event in events:
|
||||||
|
# Check if user has permission to see this event
|
||||||
|
if event.created_by == current_user.id or event.is_public:
|
||||||
|
# Get the association to access display_order and is_visible
|
||||||
|
association = event_gift_category_crud.get(db, event_id=event.id, category_id=category_id)
|
||||||
|
if association:
|
||||||
|
# Create a response with event details and association settings
|
||||||
|
event_data = {
|
||||||
|
"event_id": event.id,
|
||||||
|
"event_title": event.title,
|
||||||
|
"event_date": event.event_date,
|
||||||
|
"display_order": association.display_order,
|
||||||
|
"is_visible": association.is_visible
|
||||||
|
}
|
||||||
|
result.append(event_data)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
@router.put("/categories/{category_id}/reorder", response_model=GiftCategory)
|
@router.put("/categories/{category_id}/reorder", response_model=GiftCategory)
|
||||||
@@ -404,7 +761,6 @@ def reserve_gift_item(
|
|||||||
Reserve a gift item for a guest.
|
Reserve a gift item for a guest.
|
||||||
"""
|
"""
|
||||||
gift = gift_item_crud.get(db, id=item_id)
|
gift = gift_item_crud.get(db, id=item_id)
|
||||||
print(f"Gift {gift}")
|
|
||||||
if not gift:
|
if not gift:
|
||||||
raise HTTPException(status_code=404, detail="Gift item not found")
|
raise HTTPException(status_code=404, detail="Gift item not found")
|
||||||
|
|
||||||
@@ -581,12 +937,3 @@ def read_gift_purchases_by_guest(
|
|||||||
raise HTTPException(status_code=403, detail="Not enough permissions")
|
raise HTTPException(status_code=403, detail="Not enough permissions")
|
||||||
|
|
||||||
return gift_purchase_crud.get_by_guest(db, guest_id=guest_id)
|
return gift_purchase_crud.get_by_guest(db, guest_id=guest_id)
|
||||||
|
|
||||||
|
|
||||||
# For public users, additional validation
|
|
||||||
# Check if event is public
|
|
||||||
event = event_crud.get(db, gift.event_id)
|
|
||||||
if not event or not event.is_public:
|
|
||||||
raise HTTPException(status_code=403, detail="Not enough permissions")
|
|
||||||
|
|
||||||
return gift_item_crud.cancel_reservation(db, gift_id=item_id, guest_id=guest_id)
|
|
||||||
@@ -23,11 +23,13 @@ from app.schemas.events import (
|
|||||||
|
|
||||||
from app.api.routes.events import guests
|
from app.api.routes.events import guests
|
||||||
from app.api.routes.events import rsvps
|
from app.api.routes.events import rsvps
|
||||||
|
from app.api.routes.events import gifts
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
events_router = APIRouter()
|
events_router = APIRouter()
|
||||||
|
|
||||||
events_router.include_router(guests.router, prefix="/guests", tags=["guests"])
|
events_router.include_router(guests.router, prefix="/guests", tags=["guests"])
|
||||||
events_router.include_router(rsvps.router, prefix="/rsvps", tags=["rsvps"])
|
events_router.include_router(rsvps.router, prefix="/rsvps", tags=["rsvps"])
|
||||||
|
events_router.include_router(gifts.router, prefix="/gifts", tags=["gifts"])
|
||||||
|
|
||||||
def validate_event_access(
|
def validate_event_access(
|
||||||
*,
|
*,
|
||||||
|
|||||||
@@ -6,12 +6,16 @@ from sqlalchemy import asc, desc
|
|||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.crud.base import CRUDBase
|
from app.crud.base import CRUDBase
|
||||||
from app.models.gift import GiftItem, GiftCategory, GiftPurchase, GiftStatus
|
from app.models import gift as gift_models
|
||||||
|
from app.models.gift import GiftItem, GiftCategory, GiftPurchase, GiftStatus, EventGiftCategory
|
||||||
from app.models.guest import Guest
|
from app.models.guest import Guest
|
||||||
|
from app.models.event import Event
|
||||||
|
from app.schemas import gifts as gift_schemas
|
||||||
from app.schemas.gifts import (
|
from app.schemas.gifts import (
|
||||||
GiftItemCreate, GiftItemUpdate,
|
GiftItemCreate, GiftItemUpdate,
|
||||||
GiftCategoryCreate, GiftCategoryUpdate,
|
GiftCategoryCreate, GiftCategoryUpdate,
|
||||||
GiftPurchaseCreate, GiftPurchaseUpdate
|
GiftPurchaseCreate, GiftPurchaseUpdate,
|
||||||
|
EventGiftCategoryCreate, EventGiftCategoryUpdate
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -130,13 +134,110 @@ class CRUDGiftItem(CRUDBase[GiftItem, GiftItemCreate, GiftItemUpdate]):
|
|||||||
return gift
|
return gift
|
||||||
|
|
||||||
|
|
||||||
|
class CRUDEventGiftCategory:
|
||||||
|
def create(self, db: Session, *, obj_in: EventGiftCategoryCreate) -> EventGiftCategory:
|
||||||
|
"""Create a new event-category association"""
|
||||||
|
obj_data = obj_in.model_dump(exclude_unset=True)
|
||||||
|
|
||||||
|
# Ensure UUID fields are properly converted if they're strings
|
||||||
|
for field in ["event_id", "category_id"]:
|
||||||
|
if field in obj_data and obj_data[field] is not None:
|
||||||
|
if isinstance(obj_data[field], str):
|
||||||
|
obj_data[field] = UUID(obj_data[field])
|
||||||
|
|
||||||
|
db_obj = EventGiftCategory(**obj_data)
|
||||||
|
db.add(db_obj)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(db_obj)
|
||||||
|
return db_obj
|
||||||
|
|
||||||
|
def get(self, db: Session, *, event_id: UUID, category_id: UUID) -> Optional[EventGiftCategory]:
|
||||||
|
"""Get a specific event-category association"""
|
||||||
|
return db.query(EventGiftCategory).filter(
|
||||||
|
EventGiftCategory.event_id == event_id,
|
||||||
|
EventGiftCategory.category_id == category_id
|
||||||
|
).first()
|
||||||
|
|
||||||
|
def update(
|
||||||
|
self, db: Session, *, db_obj: EventGiftCategory, obj_in: Union[EventGiftCategoryUpdate, Dict[str, Any]]
|
||||||
|
) -> EventGiftCategory:
|
||||||
|
"""Update an event-category association"""
|
||||||
|
if isinstance(obj_in, dict):
|
||||||
|
update_data = obj_in
|
||||||
|
else:
|
||||||
|
update_data = obj_in.model_dump(exclude_unset=True)
|
||||||
|
|
||||||
|
for field in update_data:
|
||||||
|
setattr(db_obj, field, update_data[field])
|
||||||
|
|
||||||
|
db.add(db_obj)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(db_obj)
|
||||||
|
return db_obj
|
||||||
|
|
||||||
|
def remove(self, db: Session, *, event_id: UUID, category_id: UUID) -> Optional[EventGiftCategory]:
|
||||||
|
"""Remove an event-category association"""
|
||||||
|
obj = self.get(db, event_id=event_id, category_id=category_id)
|
||||||
|
if obj:
|
||||||
|
db.delete(obj)
|
||||||
|
db.commit()
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def get_categories_by_event(
|
||||||
|
self, db: Session, *, event_id: UUID, skip: int = 0, limit: int = 100,
|
||||||
|
include_hidden: bool = False
|
||||||
|
) -> List[GiftCategory]:
|
||||||
|
"""Get categories for a specific event with filtering options"""
|
||||||
|
query = db.query(GiftCategory).join(
|
||||||
|
EventGiftCategory,
|
||||||
|
GiftCategory.id == EventGiftCategory.category_id
|
||||||
|
).filter(EventGiftCategory.event_id == event_id)
|
||||||
|
|
||||||
|
if not include_hidden:
|
||||||
|
query = query.filter(EventGiftCategory.is_visible == True)
|
||||||
|
|
||||||
|
# Order by display_order then name
|
||||||
|
query = query.order_by(
|
||||||
|
asc(EventGiftCategory.display_order),
|
||||||
|
asc(GiftCategory.name)
|
||||||
|
)
|
||||||
|
|
||||||
|
return query.offset(skip).limit(limit).all()
|
||||||
|
|
||||||
|
def get_events_by_category(
|
||||||
|
self, db: Session, *, category_id: UUID, skip: int = 0, limit: int = 100
|
||||||
|
) -> List[Event]:
|
||||||
|
"""Get events for a specific category"""
|
||||||
|
query = db.query(Event).join(
|
||||||
|
EventGiftCategory,
|
||||||
|
Event.id == EventGiftCategory.event_id
|
||||||
|
).filter(EventGiftCategory.category_id == category_id)
|
||||||
|
|
||||||
|
return query.offset(skip).limit(limit).all()
|
||||||
|
|
||||||
|
def reorder_categories(
|
||||||
|
self, db: Session, *, event_id: UUID, category_orders: Dict[UUID, int]
|
||||||
|
) -> List[EventGiftCategory]:
|
||||||
|
"""Update display order of multiple categories for an event"""
|
||||||
|
associations = db.query(EventGiftCategory).filter(
|
||||||
|
EventGiftCategory.event_id == event_id
|
||||||
|
).all()
|
||||||
|
|
||||||
|
for assoc in associations:
|
||||||
|
if assoc.category_id in category_orders:
|
||||||
|
assoc.display_order = category_orders[assoc.category_id]
|
||||||
|
|
||||||
|
db.commit()
|
||||||
|
return associations
|
||||||
|
|
||||||
|
|
||||||
class CRUDGiftCategory(CRUDBase[GiftCategory, GiftCategoryCreate, GiftCategoryUpdate]):
|
class CRUDGiftCategory(CRUDBase[GiftCategory, GiftCategoryCreate, GiftCategoryUpdate]):
|
||||||
def create(self, db: Session, *, obj_in: GiftCategoryCreate) -> GiftCategory:
|
def create(self, db: Session, *, obj_in: GiftCategoryCreate) -> GiftCategory:
|
||||||
"""Create a new gift category with appropriate type conversions"""
|
"""Create a new gift category with appropriate type conversions"""
|
||||||
obj_data = obj_in.model_dump(exclude_unset=True)
|
obj_data = obj_in.model_dump(exclude_unset=True)
|
||||||
|
|
||||||
# Ensure UUID fields are properly converted if they're strings
|
# Ensure UUID fields are properly converted if they're strings
|
||||||
for field in ["event_id", "created_by"]:
|
for field in ["created_by"]:
|
||||||
if field in obj_data and obj_data[field] is not None:
|
if field in obj_data and obj_data[field] is not None:
|
||||||
if isinstance(obj_data[field], str):
|
if isinstance(obj_data[field], str):
|
||||||
obj_data[field] = UUID(obj_data[field])
|
obj_data[field] = UUID(obj_data[field])
|
||||||
@@ -147,34 +248,6 @@ class CRUDGiftCategory(CRUDBase[GiftCategory, GiftCategoryCreate, GiftCategoryUp
|
|||||||
db.refresh(db_obj)
|
db.refresh(db_obj)
|
||||||
return db_obj
|
return db_obj
|
||||||
|
|
||||||
def get_multi_by_event(
|
|
||||||
self, db: Session, event_id: UUID, skip: int = 0, limit: int = 100,
|
|
||||||
include_hidden: bool = False
|
|
||||||
) -> List[GiftCategory]:
|
|
||||||
"""Get categories for a specific event with filtering options"""
|
|
||||||
query = db.query(self.model).filter(GiftCategory.event_id == event_id)
|
|
||||||
|
|
||||||
if not include_hidden:
|
|
||||||
query = query.filter(GiftCategory.is_visible == True)
|
|
||||||
|
|
||||||
# Order by display_order then name
|
|
||||||
query = query.order_by(asc(GiftCategory.display_order), asc(GiftCategory.name))
|
|
||||||
|
|
||||||
return query.offset(skip).limit(limit).all()
|
|
||||||
|
|
||||||
def reorder_categories(
|
|
||||||
self, db: Session, *, event_id: UUID, category_orders: Dict[UUID, int]
|
|
||||||
) -> List[GiftCategory]:
|
|
||||||
"""Update display order of multiple categories at once"""
|
|
||||||
categories = self.get_multi_by_event(db, event_id, include_hidden=True)
|
|
||||||
|
|
||||||
for category in categories:
|
|
||||||
if category.id in category_orders:
|
|
||||||
category.display_order = category_orders[category.id]
|
|
||||||
|
|
||||||
db.commit()
|
|
||||||
return categories
|
|
||||||
|
|
||||||
def reorder_gifts(
|
def reorder_gifts(
|
||||||
self, db: Session, *, category_id: UUID, gift_orders: Dict[UUID, int]
|
self, db: Session, *, category_id: UUID, gift_orders: Dict[UUID, int]
|
||||||
) -> GiftCategory:
|
) -> GiftCategory:
|
||||||
@@ -241,4 +314,5 @@ class CRUDGiftPurchase(CRUDBase[GiftPurchase, GiftPurchaseCreate, GiftPurchaseUp
|
|||||||
# Create CRUD instances
|
# Create CRUD instances
|
||||||
gift_item_crud = CRUDGiftItem(GiftItem)
|
gift_item_crud = CRUDGiftItem(GiftItem)
|
||||||
gift_category_crud = CRUDGiftCategory(GiftCategory)
|
gift_category_crud = CRUDGiftCategory(GiftCategory)
|
||||||
gift_purchase_crud = CRUDGiftPurchase(GiftPurchase)
|
gift_purchase_crud = CRUDGiftPurchase(GiftPurchase)
|
||||||
|
event_gift_category_crud = CRUDEventGiftCategory()
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ class Event(Base, UUIDMixin, TimestampMixin):
|
|||||||
managers = relationship("EventManager", back_populates="event")
|
managers = relationship("EventManager", back_populates="event")
|
||||||
guests = relationship("Guest", back_populates="event")
|
guests = relationship("Guest", back_populates="event")
|
||||||
gifts = relationship("GiftItem", back_populates="event")
|
gifts = relationship("GiftItem", back_populates="event")
|
||||||
gift_categories = relationship("GiftCategory", back_populates="event")
|
category_associations = relationship("EventGiftCategory", back_populates="event")
|
||||||
media = relationship("EventMedia", back_populates="event")
|
media = relationship("EventMedia", back_populates="event")
|
||||||
# updates = relationship("EventUpdate", back_populates="event") # Keep commented out
|
# updates = relationship("EventUpdate", back_populates="event") # Keep commented out
|
||||||
rsvps = relationship("RSVP", back_populates="event")
|
rsvps = relationship("RSVP", back_populates="event")
|
||||||
|
|||||||
@@ -121,38 +121,47 @@ class GiftPurchase(Base, UUIDMixin):
|
|||||||
return f"<GiftPurchase gift_id={self.gift_id} guest_id={self.guest_id}>"
|
return f"<GiftPurchase gift_id={self.gift_id} guest_id={self.guest_id}>"
|
||||||
|
|
||||||
|
|
||||||
|
class EventGiftCategory(Base):
|
||||||
|
__tablename__ = 'event_gift_categories'
|
||||||
|
|
||||||
|
event_id = Column(UUID(as_uuid=True), ForeignKey('events.id'), primary_key=True)
|
||||||
|
category_id = Column(UUID(as_uuid=True), ForeignKey('gift_categories.id'), primary_key=True)
|
||||||
|
display_order = Column(Integer, default=0)
|
||||||
|
is_visible = Column(Boolean, default=True)
|
||||||
|
created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False)
|
||||||
|
|
||||||
|
# Relationships
|
||||||
|
event = relationship("Event", back_populates="category_associations")
|
||||||
|
category = relationship("GiftCategory", back_populates="event_associations")
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<EventGiftCategory event_id={self.event_id} category_id={self.category_id}>"
|
||||||
|
|
||||||
|
|
||||||
class GiftCategory(Base, UUIDMixin, TimestampMixin):
|
class GiftCategory(Base, UUIDMixin, TimestampMixin):
|
||||||
__tablename__ = 'gift_categories'
|
__tablename__ = 'gift_categories'
|
||||||
|
|
||||||
# Foreign Keys
|
# Foreign Keys
|
||||||
event_id = Column(UUID(as_uuid=True), ForeignKey('events.id'), nullable=False)
|
|
||||||
created_by = Column(UUID(as_uuid=True), ForeignKey('users.id'), nullable=False)
|
created_by = Column(UUID(as_uuid=True), ForeignKey('users.id'), nullable=False)
|
||||||
|
|
||||||
# Category Details
|
# Category Details
|
||||||
name = Column(String, nullable=False)
|
name = Column(String, nullable=False, unique=True)
|
||||||
description = Column(String)
|
description = Column(String)
|
||||||
|
|
||||||
# Display
|
# Display
|
||||||
icon = Column(String) # Icon identifier or URL
|
icon = Column(String) # Icon identifier or URL
|
||||||
color = Column(String) # Color code (hex/rgb)
|
color = Column(String) # Color code (hex/rgb)
|
||||||
display_order = Column(Integer, default=0)
|
|
||||||
is_visible = Column(Boolean, default=True)
|
|
||||||
|
|
||||||
# Additional Settings
|
# Additional Settings
|
||||||
custom_fields = Column(JSONB)
|
custom_fields = Column(JSONB)
|
||||||
|
|
||||||
# Relationships
|
# Relationships
|
||||||
event = relationship("Event", back_populates="gift_categories")
|
event_associations = relationship("EventGiftCategory", back_populates="category")
|
||||||
gifts = relationship("GiftItem",
|
gifts = relationship("GiftItem",
|
||||||
back_populates="category",
|
back_populates="category",
|
||||||
order_by="GiftItem.display_order")
|
order_by="GiftItem.display_order")
|
||||||
created_by_user = relationship("User", foreign_keys=[created_by])
|
created_by_user = relationship("User", foreign_keys=[created_by])
|
||||||
|
|
||||||
# Ensure unique category names within an event
|
|
||||||
__table_args__ = (
|
|
||||||
UniqueConstraint('event_id', 'name', name='uq_event_category_name'),
|
|
||||||
)
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<GiftCategory {self.name}>"
|
return f"<GiftCategory {self.name}>"
|
||||||
|
|
||||||
|
|||||||
@@ -123,7 +123,6 @@ class GiftCategoryBase(BaseModel):
|
|||||||
|
|
||||||
# Schema for creating a category
|
# Schema for creating a category
|
||||||
class GiftCategoryCreate(GiftCategoryBase):
|
class GiftCategoryCreate(GiftCategoryBase):
|
||||||
event_id: UUID
|
|
||||||
created_by: UUID
|
created_by: UUID
|
||||||
|
|
||||||
|
|
||||||
@@ -133,15 +132,12 @@ class GiftCategoryUpdate(BaseModel):
|
|||||||
description: Optional[str] = None
|
description: Optional[str] = None
|
||||||
icon: Optional[str] = None
|
icon: Optional[str] = None
|
||||||
color: Optional[str] = None
|
color: Optional[str] = None
|
||||||
display_order: Optional[int] = None
|
|
||||||
is_visible: Optional[bool] = None
|
|
||||||
custom_fields: Optional[Dict[str, Any]] = None
|
custom_fields: Optional[Dict[str, Any]] = None
|
||||||
|
|
||||||
|
|
||||||
# Schema for reading a category
|
# Schema for reading a category
|
||||||
class GiftCategoryInDB(GiftCategoryBase):
|
class GiftCategoryInDB(GiftCategoryBase):
|
||||||
id: UUID
|
id: UUID
|
||||||
event_id: UUID
|
|
||||||
created_by: UUID
|
created_by: UUID
|
||||||
created_at: datetime
|
created_at: datetime
|
||||||
updated_at: datetime
|
updated_at: datetime
|
||||||
@@ -150,11 +146,42 @@ class GiftCategoryInDB(GiftCategoryBase):
|
|||||||
from_attributes = True
|
from_attributes = True
|
||||||
|
|
||||||
|
|
||||||
|
# Event-Category association schemas
|
||||||
|
class EventGiftCategoryBase(BaseModel):
|
||||||
|
event_id: UUID
|
||||||
|
category_id: UUID
|
||||||
|
display_order: int = 0
|
||||||
|
is_visible: bool = True
|
||||||
|
|
||||||
|
|
||||||
|
# Schema for creating an event-category association
|
||||||
|
class EventGiftCategoryCreate(EventGiftCategoryBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# Schema for updating an event-category association
|
||||||
|
class EventGiftCategoryUpdate(BaseModel):
|
||||||
|
display_order: Optional[int] = None
|
||||||
|
is_visible: Optional[bool] = None
|
||||||
|
|
||||||
|
|
||||||
|
# Schema for reading an event-category association
|
||||||
|
class EventGiftCategoryInDB(EventGiftCategoryBase):
|
||||||
|
created_at: datetime
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
|
|
||||||
|
|
||||||
# Public category response with statistics
|
# Public category response with statistics
|
||||||
class GiftCategory(GiftCategoryInDB):
|
class GiftCategory(GiftCategoryInDB):
|
||||||
total_gifts: int
|
# These properties will be calculated for a specific event in the API
|
||||||
available_gifts: int
|
total_gifts: Optional[int] = None
|
||||||
|
available_gifts: Optional[int] = None
|
||||||
gifts: Optional[List[GiftItem]] = None
|
gifts: Optional[List[GiftItem]] = None
|
||||||
|
# For display in a specific event context
|
||||||
|
display_order: Optional[int] = None
|
||||||
|
is_visible: Optional[bool] = None
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
from_attributes = True
|
from_attributes = True
|
||||||
@@ -195,4 +222,4 @@ class GiftPurchaseInDB(GiftPurchaseBase):
|
|||||||
# Public gift purchase response
|
# Public gift purchase response
|
||||||
class GiftPurchase(GiftPurchaseInDB):
|
class GiftPurchase(GiftPurchaseInDB):
|
||||||
class Config:
|
class Config:
|
||||||
from_attributes = True
|
from_attributes = True
|
||||||
|
|||||||
Reference in New Issue
Block a user