168 lines
9.2 KiB
Python
168 lines
9.2 KiB
Python
from django import forms
|
|
|
|
from .models import ABOUT_KEYS, HOME_KEYS, MediaAsset
|
|
|
|
|
|
ROUTE_CHOICES = (("", "—"), ("about", "About"), ("events", "Events"), ("blog", "Blog"))
|
|
CONTACT_KINDS = ("email", "phone", "telegram", "instagram", "website")
|
|
|
|
|
|
class PageDraftForm(forms.Form):
|
|
title = forms.CharField(max_length=180)
|
|
intro = forms.CharField(max_length=600, required=False, widget=forms.Textarea(attrs={"rows": 3}))
|
|
seo_title = forms.CharField(max_length=120)
|
|
seo_description = forms.CharField(max_length=300, widget=forms.Textarea(attrs={"rows": 2}))
|
|
seo_image = forms.ModelChoiceField(queryset=MediaAsset.objects.all(), required=False)
|
|
|
|
def payload(self):
|
|
return {field: self.cleaned_data[field] for field in ("title", "intro", "seo_title", "seo_description", "seo_image")}
|
|
|
|
|
|
class SiteDraftForm(forms.Form):
|
|
official_name = forms.CharField(max_length=180)
|
|
short_name = forms.CharField(max_length=80)
|
|
faculty_subline = forms.CharField(max_length=140, required=False)
|
|
description = forms.CharField(max_length=500, required=False, widget=forms.Textarea(attrs={"rows": 3}))
|
|
logo = forms.ModelChoiceField(queryset=MediaAsset.objects.all(), required=False)
|
|
logo_alt = forms.CharField(max_length=180, required=False)
|
|
footer_copy = forms.CharField(max_length=300, required=False)
|
|
seo_title = forms.CharField(max_length=120, required=False)
|
|
seo_description = forms.CharField(max_length=300, required=False, widget=forms.Textarea(attrs={"rows": 2}))
|
|
seo_image = forms.ModelChoiceField(queryset=MediaAsset.objects.all(), required=False)
|
|
|
|
def __init__(self, *args, revision=None, **kwargs):
|
|
initial = kwargs.setdefault("initial", {})
|
|
if revision:
|
|
for field in ("official_name", "short_name", "faculty_subline", "description", "logo", "logo_alt", "footer_copy", "seo_title", "seo_description", "seo_image"):
|
|
initial[field] = getattr(revision, field)
|
|
super().__init__(*args, **kwargs)
|
|
navigation = {item["route_id"]: item for item in revision.navigation} if revision else {}
|
|
contacts = {item["kind"]: item for item in revision.contacts} if revision else {}
|
|
for position, route in enumerate(("home", "about", "events", "blog")):
|
|
item = navigation.get(route, {})
|
|
self.fields[f"nav_{route}_label"] = forms.CharField(max_length=80, initial=item.get("label", route.title()))
|
|
self.fields[f"nav_{route}_position"] = forms.IntegerField(min_value=0, initial=item.get("position", position))
|
|
self.fields[f"nav_{route}_visible"] = forms.BooleanField(required=False, initial=item.get("visible", True))
|
|
for kind in CONTACT_KINDS:
|
|
item = contacts.get(kind, {})
|
|
self.fields[f"contact_{kind}_label"] = forms.CharField(max_length=80, required=False, initial=item.get("label", ""))
|
|
self.fields[f"contact_{kind}_url"] = forms.CharField(max_length=500, required=False, initial=item.get("url", ""))
|
|
self.fields[f"contact_{kind}_position"] = forms.IntegerField(min_value=0, required=False, initial=item.get("position"))
|
|
self.fields[f"contact_{kind}_visible"] = forms.BooleanField(required=False, initial=item.get("visible", True))
|
|
|
|
def clean(self):
|
|
cleaned = super().clean()
|
|
for kind in CONTACT_KINDS:
|
|
label = cleaned.get(f"contact_{kind}_label")
|
|
url = cleaned.get(f"contact_{kind}_url")
|
|
if bool(label) != bool(url):
|
|
self.add_error(f"contact_{kind}_url", "Contact label and URL must be filled together.")
|
|
return cleaned
|
|
|
|
def payload(self):
|
|
data = {field: self.cleaned_data[field] for field in ("official_name", "short_name", "faculty_subline", "description", "logo", "logo_alt", "footer_copy", "seo_title", "seo_description", "seo_image")}
|
|
data["navigation"] = [
|
|
{"route_id": route, "label": self.cleaned_data[f"nav_{route}_label"], "position": self.cleaned_data[f"nav_{route}_position"], "visible": self.cleaned_data[f"nav_{route}_visible"]}
|
|
for route in ("home", "about", "events", "blog")
|
|
]
|
|
data["contacts"] = []
|
|
for kind in CONTACT_KINDS:
|
|
url = self.cleaned_data[f"contact_{kind}_url"]
|
|
if url:
|
|
data["contacts"].append({
|
|
"kind": kind, "label": self.cleaned_data[f"contact_{kind}_label"], "url": url,
|
|
"position": self.cleaned_data[f"contact_{kind}_position"] if self.cleaned_data[f"contact_{kind}_position"] is not None else len(data["contacts"]),
|
|
"visible": self.cleaned_data[f"contact_{kind}_visible"],
|
|
})
|
|
return data
|
|
|
|
|
|
class EntryForm(forms.Form):
|
|
title = forms.CharField(max_length=120, required=False)
|
|
text = forms.CharField(max_length=600, required=False, widget=forms.Textarea(attrs={"rows": 2}))
|
|
|
|
def clean(self):
|
|
cleaned = super().clean()
|
|
if bool(cleaned.get("title")) != bool(cleaned.get("text")):
|
|
raise forms.ValidationError("Each entry needs both a title and text.")
|
|
return cleaned
|
|
|
|
|
|
EntryFormSet = forms.formset_factory(EntryForm, extra=1, max_num=20, validate_max=True, can_delete=True)
|
|
|
|
|
|
class SectionForm(forms.Form):
|
|
heading = forms.CharField(max_length=120, required=False)
|
|
body = forms.CharField(max_length=1200, required=False, widget=forms.Textarea(attrs={"rows": 3}))
|
|
intro = forms.CharField(max_length=300, required=False, widget=forms.Textarea(attrs={"rows": 2}))
|
|
all_link_label = forms.CharField(max_length=40, required=False)
|
|
position = forms.IntegerField(min_value=0, required=False)
|
|
visible = forms.BooleanField(required=False)
|
|
image = forms.ModelChoiceField(queryset=MediaAsset.objects.all(), required=False)
|
|
image_alt = forms.CharField(max_length=180, required=False)
|
|
primary_label = forms.CharField(max_length=40, required=False)
|
|
primary_route = forms.ChoiceField(choices=ROUTE_CHOICES, required=False)
|
|
secondary_label = forms.CharField(max_length=40, required=False)
|
|
secondary_route = forms.ChoiceField(choices=ROUTE_CHOICES, required=False)
|
|
|
|
def __init__(self, *args, key, slug, section=None, **kwargs):
|
|
initial = kwargs.setdefault("initial", {})
|
|
if section:
|
|
for field in ("heading", "body", "intro", "all_link_label", "position", "visible", "image", "image_alt"):
|
|
initial[field] = getattr(section, field)
|
|
for side in ("primary", "secondary"):
|
|
action = getattr(section, f"{side}_action") or {}
|
|
initial[f"{side}_label"] = action.get("label", "")
|
|
initial[f"{side}_route"] = action.get("route_id", "")
|
|
else:
|
|
initial["visible"] = True
|
|
if slug == "home":
|
|
initial["position"] = HOME_KEYS.index(key)
|
|
elif key == "identity":
|
|
initial["position"] = 0
|
|
elif slug == "about":
|
|
initial["position"] = ABOUT_KEYS.index(key)
|
|
super().__init__(*args, **kwargs)
|
|
self.key = key
|
|
self.slug = slug
|
|
self.title = key.replace("_", " ").title()
|
|
if key in {"hero", "identity", "contact"} or slug == "home":
|
|
self.fields["heading"].required = True
|
|
if slug == "home" or key in {"identity", "contact"}:
|
|
self.fields["position"].disabled = True
|
|
if key in {"hero", "identity", "contact"} or slug == "home":
|
|
self.fields["visible"].disabled = True
|
|
allowed = {"heading", "position", "visible"}
|
|
if key in {"hero", "participation"}:
|
|
allowed |= {"body", "image", "image_alt", "primary_label", "primary_route", "secondary_label", "secondary_route"}
|
|
self.fields["body"].max_length = 600 if key == "participation" else 500
|
|
elif key in {"featured_events", "featured_posts"}:
|
|
allowed |= {"intro", "all_link_label"}
|
|
elif key != "contact":
|
|
allowed.add("body")
|
|
self.fields["body"].max_length = 600 if key == "identity" else 1200
|
|
for field in tuple(self.fields):
|
|
if field not in allowed:
|
|
del self.fields[field]
|
|
|
|
def clean(self):
|
|
cleaned = super().clean()
|
|
for side in ("primary", "secondary"):
|
|
if bool(cleaned.get(f"{side}_label")) != bool(cleaned.get(f"{side}_route")):
|
|
self.add_error(f"{side}_label", "Action label and route must be filled together.")
|
|
return cleaned
|
|
|
|
def payload(self, entries=(), *, contact_position=None):
|
|
data = self.cleaned_data
|
|
position = contact_position if contact_position is not None else data["position"]
|
|
section = {
|
|
"key": self.key, "section_type": self.key, "position": position,
|
|
"visible": True if self.slug == "home" or self.key in {"identity", "contact"} else data["visible"],
|
|
"heading": data["heading"], "body": data.get("body", ""), "intro": data.get("intro", ""),
|
|
"all_link_label": data.get("all_link_label", ""), "image": data.get("image"), "image_alt": data.get("image_alt", ""),
|
|
"entries": list(entries),
|
|
}
|
|
for side in ("primary", "secondary"):
|
|
section[f"{side}_action"] = {"label": data[f"{side}_label"], "route_id": data[f"{side}_route"]} if data.get(f"{side}_label") else None
|
|
return section
|