248 lines
7.4 KiB
Python
248 lines
7.4 KiB
Python
"""Event and gallery API schemas."""
|
|
|
|
from uuid import UUID
|
|
from ninja import ModelSchema, Schema
|
|
from pydantic import field_validator
|
|
from typing import Literal, Optional, List
|
|
from datetime import datetime
|
|
|
|
from apps.blog.api.schemas import AuthorSchema
|
|
from apps.events.models import Event, Registration
|
|
from apps.gallery.models import Gallery
|
|
from apps.payments.models import Payment
|
|
|
|
|
|
class EventGallerySchema(ModelSchema):
|
|
"""Schema representing gallery items associated with an event."""
|
|
uploaded_by: AuthorSchema
|
|
file_size_mb: float
|
|
markdown_url: str
|
|
absolute_image_url: Optional[str] = None
|
|
|
|
class Config:
|
|
model = Gallery
|
|
model_fields = ['id', 'title', 'description', 'image', 'alt_text',
|
|
'width', 'height', 'is_public', 'created_at']
|
|
|
|
@staticmethod
|
|
def resolve_absolute_image_url(obj, context):
|
|
request = context['request']
|
|
if obj.image and hasattr(obj.image, 'url'):
|
|
return request.build_absolute_uri(obj.image.url)
|
|
return None
|
|
|
|
class EventSchema(ModelSchema):
|
|
"""Schema providing full event details for API responses."""
|
|
gallery_images: List[EventGallerySchema]
|
|
description_html: str
|
|
registration_count: int
|
|
absolute_featured_image_url: Optional[str] = None
|
|
|
|
class Config:
|
|
model = Event
|
|
model_fields = [
|
|
'id', 'title', 'slug', 'description', 'featured_image', 'event_type',
|
|
'address', 'location', 'online_link', 'start_time', 'end_time',
|
|
'registration_start_date', 'registration_end_date', 'registration_success_markdown',
|
|
'capacity', 'price', 'status', 'created_at', 'updated_at'
|
|
]
|
|
|
|
@staticmethod
|
|
def resolve_absolute_featured_image_url(obj, context):
|
|
request = context['request']
|
|
if obj.featured_image and hasattr(obj.featured_image, 'url'):
|
|
return request.build_absolute_uri(obj.featured_image.url)
|
|
return None
|
|
|
|
@staticmethod
|
|
def resolve_registration_count(obj):
|
|
return obj.registrations.filter(status__in=[Registration.StatusChoices.CONFIRMED, Registration.StatusChoices.ATTENDED]).count()
|
|
|
|
@staticmethod
|
|
def resolve_description_html(obj):
|
|
return obj.description_html
|
|
|
|
|
|
class EventListSchema(Schema):
|
|
"""Condensed event representation for list endpoints."""
|
|
id: int
|
|
title: str
|
|
slug: str
|
|
featured_image: Optional[str] = None
|
|
absolute_featured_image_url: Optional[str] = None
|
|
event_type: str
|
|
start_time: datetime
|
|
end_time: datetime
|
|
registration_start_date: Optional[datetime] = None
|
|
registration_end_date: Optional[datetime] = None
|
|
capacity: Optional[int] = None
|
|
price: Optional[float] = None
|
|
status: str
|
|
registration_count: int
|
|
created_at: datetime
|
|
|
|
@staticmethod
|
|
def resolve_absolute_featured_image_url(obj, context):
|
|
request = context['request']
|
|
if obj.featured_image and hasattr(obj.featured_image, 'url'):
|
|
return request.build_absolute_uri(obj.featured_image.url)
|
|
return None
|
|
|
|
@staticmethod
|
|
def resolve_registration_count(obj):
|
|
return obj.registrations.filter(status__in=[Registration.StatusChoices.CONFIRMED, Registration.StatusChoices.ATTENDED]).count()
|
|
|
|
class EventCreateSchema(Schema):
|
|
"""Payload for creating events via the API."""
|
|
title: str
|
|
description: str
|
|
event_type: str
|
|
address: Optional[str] = None
|
|
location: Optional[str] = None
|
|
online_link: Optional[str] = None
|
|
start_time: datetime
|
|
end_time: datetime
|
|
registration_start_date: Optional[datetime] = None
|
|
registration_end_date: Optional[datetime] = None
|
|
capacity: Optional[int] = None
|
|
price: Optional[float] = None
|
|
status: str = "draft"
|
|
gallery_image_ids: Optional[List[int]] = []
|
|
|
|
class EventUpdateSchema(Schema):
|
|
"""Payload for updating events via the API."""
|
|
title: Optional[str] = None
|
|
description: Optional[str] = None
|
|
event_type: Optional[str] = None
|
|
address: Optional[str] = None
|
|
location: Optional[str] = None
|
|
online_link: Optional[str] = None
|
|
start_time: Optional[datetime] = None
|
|
end_time: Optional[datetime] = None
|
|
registration_start_date: Optional[datetime] = None
|
|
registration_end_date: Optional[datetime] = None
|
|
capacity: Optional[int] = None
|
|
price: Optional[float] = None
|
|
status: Optional[str] = None
|
|
gallery_image_ids: Optional[List[int]] = None
|
|
|
|
class RegistrationSchema(ModelSchema):
|
|
"""Schema describing a registration entry with event context."""
|
|
user: AuthorSchema
|
|
event: EventListSchema
|
|
discount_code: str | None = None
|
|
|
|
class Config:
|
|
model = Registration
|
|
model_fields = [
|
|
'id',
|
|
'status',
|
|
'registered_at',
|
|
'ticket_id',
|
|
'discount_amount',
|
|
'final_price',
|
|
'created_at',
|
|
'updated_at',
|
|
]
|
|
|
|
@staticmethod
|
|
def resolve_discount_code(obj):
|
|
return obj.discount_code.code if obj.discount_code else None
|
|
|
|
|
|
class AdminUserSchema(Schema):
|
|
id: int
|
|
username: str
|
|
first_name: str
|
|
last_name: str
|
|
email: str
|
|
|
|
|
|
class PaymentAdminSchema(Schema):
|
|
id: int
|
|
authority: Optional[str]
|
|
ref_id: Optional[str]
|
|
status: int
|
|
status_label: str
|
|
base_amount: int
|
|
discount_amount: int
|
|
amount: int
|
|
verified_at: Optional[datetime]
|
|
created_at: datetime
|
|
discount_code: Optional[str]
|
|
user: AdminUserSchema
|
|
|
|
@field_validator("discount_code", mode="before")
|
|
def normalize_discount_code(cls, value):
|
|
if value is None:
|
|
return None
|
|
if hasattr(value, "code"):
|
|
return value.code
|
|
return str(value)
|
|
|
|
|
|
class RegistrationAdminSchema(Schema):
|
|
id: int
|
|
ticket_id: UUID
|
|
status: str
|
|
status_label: str
|
|
registered_at: datetime
|
|
final_price: Optional[int]
|
|
discount_amount: Optional[int]
|
|
user: AdminUserSchema
|
|
payments: List[PaymentAdminSchema]
|
|
|
|
|
|
class EventAdminDetailSchema(EventSchema):
|
|
registrations: List[RegistrationAdminSchema] = []
|
|
|
|
@staticmethod
|
|
def resolve_registrations(obj):
|
|
return obj.registrations.select_related("user").prefetch_related(
|
|
"payments__discount_code"
|
|
).order_by("-registered_at")
|
|
|
|
class PaginatedRegistrationSchema(Schema):
|
|
count: int
|
|
next: Optional[str] = None
|
|
previous: Optional[str] = None
|
|
results: List[RegistrationAdminSchema]
|
|
|
|
class RegistrationStatusUpdateSchema(Schema):
|
|
status: str
|
|
|
|
class RegisterationDetailSchema(Schema):
|
|
"""Detailed registration information with associated event metadata."""
|
|
event_image: Optional[str]
|
|
event_title: str
|
|
event_type: str
|
|
ticket_id: UUID
|
|
status: str
|
|
registered_at: datetime
|
|
success_markdown: Optional[str]
|
|
|
|
class EventBriefSchema(Schema):
|
|
"""Minimal event representation used for nested responses."""
|
|
id: int
|
|
title: str
|
|
slug: str
|
|
start_date: datetime
|
|
end_date: Optional[datetime] = None
|
|
location: Optional[str] = None
|
|
price: int
|
|
absolute_image_url: Optional[str] = None
|
|
|
|
class MyEventRegistrationOut(Schema):
|
|
"""Registration information as returned to authenticated users."""
|
|
id: int
|
|
created_at: datetime
|
|
status: Literal["pending", "confirmed", "cancelled", "attended"]
|
|
event: EventBriefSchema
|
|
|
|
class RegistrationStatusOut(Schema):
|
|
is_registered: bool
|
|
|
|
|
|
class RegistrationCreateSchema(Schema):
|
|
discount_code: Optional[str] = None
|