Replace persisted event statuses with publication and cancellation flags, and compute public lifecycle labels from event timing and registration capacity. Keep filters, registration checks, and profile responses aligned with the derived status.
201 lines
8.7 KiB
Python
201 lines
8.7 KiB
Python
from django.http import HttpResponse
|
|
from django.utils import timezone
|
|
from ninja import Router
|
|
from ninja.errors import HttpError
|
|
|
|
from apps.events.models import Event
|
|
from apps.content.current import AboutContent, HomeContent, ListingContent, SiteContent
|
|
from apps.content.api.schemas import AboutPageOut, HomePageOut, ListingPageOut, PageContentOut, SiteContentOut
|
|
|
|
|
|
content_router = Router(tags=["Public content"])
|
|
|
|
|
|
def _media(request, field, alt):
|
|
if not field or not field.name:
|
|
return None
|
|
return {"url": request.build_absolute_uri(field.url), "alt": alt or ""}
|
|
|
|
|
|
def _seo(request, obj):
|
|
return {
|
|
"title": obj.seo_title,
|
|
"description": obj.seo_description,
|
|
"image": _media(request, obj.seo_image, obj.seo_image_alt),
|
|
}
|
|
|
|
|
|
def _about_member_out(request, member, year_label=None):
|
|
return {
|
|
"name": member.name,
|
|
"role": member.role,
|
|
"entry_year": year_label or member.entry_year,
|
|
"bio": member.bio,
|
|
"photo": _media(request, member.photo, member.photo_alt),
|
|
"position": member.position,
|
|
}
|
|
|
|
|
|
def _about_event_out(request, event):
|
|
return {
|
|
"id": event.pk,
|
|
"title": event.title,
|
|
"slug": event.slug,
|
|
"start_time": event.start_time,
|
|
"featured_image": _media(request, event.featured_image, f"پوستر {event.title}"),
|
|
}
|
|
|
|
|
|
@content_router.get("/site", response=SiteContentOut)
|
|
def get_site_content(request, response: HttpResponse):
|
|
site = SiteContent.objects.filter(pk=1).first()
|
|
if site is None:
|
|
raise HttpError(404, "Site content not found")
|
|
response["Cache-Control"] = "no-store"
|
|
return {
|
|
"official_name": site.official_name,
|
|
"short_name": site.short_name,
|
|
"faculty_subline": site.faculty_subline,
|
|
"description": site.description,
|
|
"logo": _media(request, site.logo, site.logo_alt),
|
|
"navigation": {
|
|
"home": site.nav_home,
|
|
"about": site.nav_about,
|
|
"events": site.nav_events,
|
|
"blog": site.nav_blog,
|
|
},
|
|
"contacts": {
|
|
"email": site.email,
|
|
"phone": site.phone,
|
|
"telegram": site.telegram_url,
|
|
"instagram": site.instagram_url,
|
|
"website": site.website_url,
|
|
},
|
|
"address": site.address,
|
|
"footer_copy": site.footer_copy,
|
|
"enamad_image": _media(request, site.enamad_image, site.enamad_image_alt),
|
|
"enamad_url": site.enamad_url,
|
|
"seo": _seo(request, site),
|
|
}
|
|
|
|
|
|
@content_router.get("/pages/{slug}", response=PageContentOut)
|
|
def get_page_content(request, response: HttpResponse, slug: str):
|
|
response["Cache-Control"] = "no-store"
|
|
if slug == "home":
|
|
page = HomeContent.objects.filter(pk=1).first()
|
|
if page is None:
|
|
raise HttpError(404, "Page content not found")
|
|
return {
|
|
"slug": "home", "title": page.title, "intro": page.intro, "seo": _seo(request, page),
|
|
"hero_heading": page.hero_heading, "hero_body": page.hero_body,
|
|
"hero_image": _media(request, page.hero_image, page.hero_image_alt),
|
|
"hero_action_label": page.hero_action_label,
|
|
"hero_action_url": page.hero_action_url,
|
|
"hero_secondary_action_label": page.hero_secondary_action_label,
|
|
"hero_secondary_action_url": page.hero_secondary_action_url,
|
|
"featured_events_heading": page.featured_events_heading,
|
|
"featured_events_intro": page.featured_events_intro,
|
|
"featured_events_link_label": page.featured_events_link_label,
|
|
"featured_events_url": page.featured_events_url,
|
|
"featured_event_action_label": page.featured_event_action_label,
|
|
"featured_posts_heading": page.featured_posts_heading,
|
|
"featured_posts_intro": page.featured_posts_intro,
|
|
"featured_posts_link_label": page.featured_posts_link_label,
|
|
"featured_posts_url": page.featured_posts_url,
|
|
"featured_post_action_label": page.featured_post_action_label,
|
|
"participation_heading": page.participation_heading,
|
|
"participation_body": page.participation_body,
|
|
"participation_image": _media(request, page.participation_image, page.participation_image_alt),
|
|
"participation_action_label": page.participation_action_label,
|
|
"participation_action_url": page.participation_action_url,
|
|
"participation_secondary_action_label": page.participation_secondary_action_label,
|
|
"participation_secondary_action_url": page.participation_secondary_action_url,
|
|
}
|
|
|
|
if slug == "about":
|
|
page = AboutContent.objects.filter(pk=1).first()
|
|
if page is None:
|
|
raise HttpError(404, "Page content not found")
|
|
years_by_label = {}
|
|
for year in page.years.prefetch_related("members", "events").all():
|
|
label = year.year_label.strip() or "سال نامشخص"
|
|
events = year.events.filter(
|
|
is_deleted=False,
|
|
is_published=True,
|
|
start_time__lte=timezone.now(),
|
|
).order_by("-start_time", "-pk")
|
|
years_by_label[label] = {
|
|
"year_label": label,
|
|
"position": year.position,
|
|
"_pk": year.pk,
|
|
"members": [
|
|
_about_member_out(request, member, label)
|
|
for member in year.members.all()
|
|
],
|
|
"selected_events": [_about_event_out(request, event) for event in events],
|
|
}
|
|
|
|
legacy_members = page.members.filter(year_group__isnull=True).order_by("entry_year", "position", "pk")
|
|
for member in legacy_members:
|
|
label = member.entry_year.strip() or "سال نامشخص"
|
|
year_data = years_by_label.setdefault(
|
|
label,
|
|
{
|
|
"year_label": label,
|
|
"position": 0,
|
|
"_pk": 0,
|
|
"members": [],
|
|
"selected_events": [],
|
|
},
|
|
)
|
|
year_data["members"].append(_about_member_out(request, member, label))
|
|
|
|
years = sorted(
|
|
years_by_label.values(),
|
|
key=lambda year: (year["position"], year["_pk"], year["year_label"]),
|
|
)
|
|
all_members = [member for year in years for member in year["members"]]
|
|
return {
|
|
"slug": "about", "title": page.title, "intro": page.intro, "seo": _seo(request, page),
|
|
"identity_heading": page.identity_heading, "identity_body": page.identity_body,
|
|
"mission_heading": page.mission_heading, "mission_body": page.mission_body,
|
|
"vision_heading": page.vision_heading, "vision_body": page.vision_body,
|
|
"activities_heading": page.activities_heading, "activities_body": page.activities_body,
|
|
"values_heading": page.values_heading, "values_body": page.values_body,
|
|
"faq_heading": page.faq_heading, "contact_heading": page.contact_heading,
|
|
"members_heading": page.members_heading, "members_intro": page.members_intro,
|
|
"gallery_heading": page.gallery_heading, "gallery_intro": page.gallery_intro,
|
|
"past_events_heading": page.past_events_heading,
|
|
"history_heading": page.history_heading,
|
|
"audience_heading": page.audience_heading,
|
|
"year_events_heading": page.year_events_heading,
|
|
"cta_heading": page.cta_heading,
|
|
"cta_body": page.cta_body,
|
|
"cta_action_label": page.cta_action_label,
|
|
"cta_action_url": page.cta_action_url,
|
|
"items": list(page.items.exclude(kind__in=("committees", "affiliations")).values("kind", "title", "text", "position")),
|
|
"members": all_members,
|
|
"gallery_images": [
|
|
{
|
|
"image": _media(request, item.image, item.image_alt),
|
|
"caption": item.caption, "position": item.position,
|
|
}
|
|
for item in page.gallery_images.all()
|
|
],
|
|
"audiences": list(page.audiences.values("title", "text", "icon", "position")),
|
|
"milestones": list(page.milestones.values("year_label", "title", "description", "position")),
|
|
"years": [
|
|
{key: value for key, value in year.items() if not key.startswith("_")}
|
|
for year in years
|
|
],
|
|
}
|
|
|
|
if slug in ("events", "blog"):
|
|
page = ListingContent.objects.filter(slug=slug).first()
|
|
if page is None:
|
|
raise HttpError(404, "Page content not found")
|
|
return {"slug": page.slug, "title": page.title, "intro": page.intro, "seo": _seo(request, page)}
|
|
|
|
raise HttpError(404, "Page content not found")
|