feat: add direct-save CMS and About archive
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
from datetime import datetime
|
||||
from typing import Literal, Optional, Union
|
||||
|
||||
from ninja import Schema
|
||||
@@ -24,6 +25,8 @@ class SiteContentOut(Schema):
|
||||
contacts: dict[str, str]
|
||||
address: str
|
||||
footer_copy: str
|
||||
enamad_image: Optional[MediaOut] = None
|
||||
enamad_url: str
|
||||
seo: SeoOut
|
||||
|
||||
|
||||
@@ -40,16 +43,26 @@ class HomePageOut(PageBaseOut):
|
||||
hero_body: str
|
||||
hero_image: Optional[MediaOut] = None
|
||||
hero_action_label: str
|
||||
hero_action_url: str
|
||||
hero_secondary_action_label: str
|
||||
hero_secondary_action_url: str
|
||||
featured_events_heading: str
|
||||
featured_events_intro: str
|
||||
featured_events_link_label: str
|
||||
featured_events_url: str
|
||||
featured_event_action_label: str
|
||||
featured_posts_heading: str
|
||||
featured_posts_intro: str
|
||||
featured_posts_link_label: str
|
||||
featured_posts_url: str
|
||||
featured_post_action_label: str
|
||||
participation_heading: str
|
||||
participation_body: str
|
||||
participation_image: Optional[MediaOut] = None
|
||||
participation_action_label: str
|
||||
participation_action_url: str
|
||||
participation_secondary_action_label: str
|
||||
participation_secondary_action_url: str
|
||||
|
||||
|
||||
class AboutItemOut(Schema):
|
||||
@@ -59,23 +72,82 @@ class AboutItemOut(Schema):
|
||||
position: int
|
||||
|
||||
|
||||
class AboutMemberOut(Schema):
|
||||
name: str
|
||||
role: str
|
||||
entry_year: str
|
||||
bio: str
|
||||
photo: Optional[MediaOut] = None
|
||||
position: int
|
||||
|
||||
|
||||
class AboutGalleryImageOut(Schema):
|
||||
image: MediaOut
|
||||
caption: str
|
||||
position: int
|
||||
|
||||
|
||||
class AboutAudienceOut(Schema):
|
||||
title: str
|
||||
text: str
|
||||
icon: str
|
||||
position: int
|
||||
|
||||
|
||||
class AboutMilestoneOut(Schema):
|
||||
year_label: str
|
||||
title: str
|
||||
description: str
|
||||
position: int
|
||||
|
||||
|
||||
class AboutSelectedEventOut(Schema):
|
||||
id: int
|
||||
title: str
|
||||
slug: str
|
||||
start_time: datetime
|
||||
featured_image: Optional[MediaOut] = None
|
||||
|
||||
|
||||
class AboutYearOut(Schema):
|
||||
year_label: str
|
||||
position: int
|
||||
members: list[AboutMemberOut]
|
||||
selected_events: list[AboutSelectedEventOut]
|
||||
|
||||
|
||||
class AboutPageOut(PageBaseOut):
|
||||
slug: Literal["about"]
|
||||
identity_heading: str
|
||||
identity_body: str
|
||||
mission_heading: str
|
||||
mission_body: str
|
||||
vision_heading: str
|
||||
vision_body: str
|
||||
activities_heading: str
|
||||
activities_body: str
|
||||
values_heading: str
|
||||
values_body: str
|
||||
committees_heading: str
|
||||
committees_body: str
|
||||
affiliations_heading: str
|
||||
affiliations_body: str
|
||||
faq_heading: str
|
||||
contact_heading: str
|
||||
members_heading: str
|
||||
members_intro: str
|
||||
gallery_heading: str
|
||||
gallery_intro: str
|
||||
past_events_heading: str
|
||||
history_heading: str
|
||||
audience_heading: str
|
||||
year_events_heading: str
|
||||
cta_heading: str
|
||||
cta_body: str
|
||||
cta_action_label: str
|
||||
cta_action_url: str
|
||||
items: list[AboutItemOut]
|
||||
members: list[AboutMemberOut]
|
||||
gallery_images: list[AboutGalleryImageOut]
|
||||
audiences: list[AboutAudienceOut]
|
||||
milestones: list[AboutMilestoneOut]
|
||||
years: list[AboutYearOut]
|
||||
|
||||
|
||||
class ListingPageOut(PageBaseOut):
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
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
|
||||
|
||||
@@ -23,6 +25,27 @@ def _seo(request, obj):
|
||||
}
|
||||
|
||||
|
||||
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()
|
||||
@@ -50,6 +73,8 @@ def get_site_content(request, response: HttpResponse):
|
||||
},
|
||||
"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),
|
||||
}
|
||||
|
||||
@@ -66,32 +91,105 @@ def get_page_content(request, response: HttpResponse, slug: str):
|
||||
"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")
|
||||
event_statuses = (Event.StatusChoices.PUBLISHED, Event.StatusChoices.COMPLETED)
|
||||
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,
|
||||
status__in=event_statuses,
|
||||
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,
|
||||
"committees_heading": page.committees_heading, "committees_body": page.committees_body,
|
||||
"affiliations_heading": page.affiliations_heading, "affiliations_body": page.affiliations_body,
|
||||
"faq_heading": page.faq_heading, "contact_heading": page.contact_heading,
|
||||
"items": list(page.items.values("kind", "title", "text", "position")),
|
||||
"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"):
|
||||
|
||||
Reference in New Issue
Block a user