angrybeanie_wagtail/env/lib/python3.12/site-packages/wagtail/test/demosite/models.py

695 lines
18 KiB
Python
Raw Normal View History

2025-07-25 21:32:16 +10:00
from datetime import date
from django.core.exceptions import ValidationError
from django.core.paginator import Paginator
from django.db import models
from modelcluster.contrib.taggit import ClusterTaggableManager
from modelcluster.fields import ParentalKey
from taggit.models import TaggedItemBase
from wagtail.admin.panels import FieldPanel, InlinePanel, MultiFieldPanel
from wagtail.api import APIField
from wagtail.contrib.forms.models import AbstractForm, AbstractFormField
from wagtail.fields import RichTextField
from wagtail.images.api.fields import ImageRenditionField
from wagtail.models import Orderable, Page
from wagtail.search import index
# ABSTRACT MODELS
# =============================
class AbstractLinkFields(models.Model):
link_external = models.URLField("External link", blank=True)
link_page = models.ForeignKey(
"wagtailcore.Page",
null=True,
blank=True,
related_name="+",
on_delete=models.CASCADE,
)
link_document = models.ForeignKey(
"wagtaildocs.Document",
null=True,
blank=True,
related_name="+",
on_delete=models.CASCADE,
)
@property
def link(self):
if self.link_page:
return self.link_page.url
elif self.link_document:
return self.link_document.url
else:
return self.link_external
def clean(self):
if (
self.link_page is None
and self.link_document is None
and not self.link_external
):
raise ValidationError(
"You must provide a related page, related document or an external URL"
)
api_fields = ("link",)
panels = [
FieldPanel("link_external"),
FieldPanel("link_page"),
FieldPanel("link_document"),
]
class Meta:
abstract = True
class AbstractRelatedLink(AbstractLinkFields):
title = models.CharField(max_length=255, help_text="Link title")
api_fields = ("title",) + AbstractLinkFields.api_fields
panels = [
FieldPanel("title"),
MultiFieldPanel(AbstractLinkFields.panels, "Link"),
]
class Meta:
abstract = True
class AbstractCarouselItem(AbstractLinkFields):
image = models.ForeignKey(
"wagtailimages.Image",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="+",
)
embed_url = models.URLField("Embed URL", blank=True)
caption = models.CharField(max_length=255, blank=True)
api_fields = (
"image",
"embed_url",
"caption",
) + AbstractLinkFields.api_fields
panels = [
FieldPanel("image"),
FieldPanel("embed_url"),
FieldPanel("caption"),
MultiFieldPanel(AbstractLinkFields.panels, "Link"),
]
class Meta:
abstract = True
class ContactFieldsMixin(models.Model):
telephone = models.CharField(max_length=20, blank=True)
email = models.EmailField(blank=True)
address_1 = models.CharField(max_length=255, blank=True)
address_2 = models.CharField(max_length=255, blank=True)
city = models.CharField(max_length=255, blank=True)
country = models.CharField(max_length=255, blank=True)
post_code = models.CharField(max_length=10, blank=True)
api_fields = (
"telephone",
"email",
"address_1",
"address_2",
"city",
"country",
"post_code",
)
panels = [
FieldPanel("telephone"),
FieldPanel("email"),
FieldPanel("address_1"),
FieldPanel("address_2"),
FieldPanel("city"),
FieldPanel("country"),
FieldPanel("post_code"),
]
class Meta:
abstract = True
# PAGE MODELS
# =============================
# Home page
class HomePage(Page):
page_ptr = models.OneToOneField(
Page, parent_link=True, related_name="+", on_delete=models.CASCADE
)
body = RichTextField(blank=True)
api_fields = (
"body",
"carousel_items",
"related_links",
)
search_fields = Page.search_fields + [
index.SearchField("body"),
]
class Meta:
verbose_name = "homepage"
class HomePageCarouselItem(Orderable, AbstractCarouselItem):
page = ParentalKey(
"HomePage", related_name="carousel_items", on_delete=models.CASCADE
)
class HomePageRelatedLink(Orderable, AbstractRelatedLink):
page = ParentalKey(
"HomePage", related_name="related_links", on_delete=models.CASCADE
)
HomePage.content_panels = Page.content_panels + [
FieldPanel("body"),
InlinePanel("carousel_items", label="Carousel items"),
InlinePanel("related_links", label="Related links"),
]
# Standard pages
class StandardPage(Page):
page_ptr = models.OneToOneField(
Page, parent_link=True, related_name="+", on_delete=models.CASCADE
)
intro = RichTextField(blank=True)
body = RichTextField(blank=True)
feed_image = models.ForeignKey(
"wagtailimages.Image",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="+",
)
api_fields = (
"intro",
"body",
"feed_image",
"carousel_items",
"related_links",
)
search_fields = Page.search_fields + [
index.SearchField("intro"),
index.SearchField("body"),
]
class StandardPageCarouselItem(Orderable, AbstractCarouselItem):
page = ParentalKey(
"StandardPage", related_name="carousel_items", on_delete=models.CASCADE
)
class StandardPageRelatedLink(Orderable, AbstractRelatedLink):
page = ParentalKey(
"StandardPage", related_name="related_links", on_delete=models.CASCADE
)
StandardPage.content_panels = Page.content_panels + [
FieldPanel("intro"),
InlinePanel("carousel_items", heading="Carousel items", label="Carousel item"),
FieldPanel("body"),
InlinePanel("related_links", heading="Related links", label="Related link"),
]
StandardPage.promote_panels = [
MultiFieldPanel(Page.promote_panels, "Common page configuration"),
FieldPanel("feed_image"),
]
class StandardIndexPage(Page):
page_ptr = models.OneToOneField(
Page, parent_link=True, related_name="+", on_delete=models.CASCADE
)
intro = RichTextField(blank=True)
feed_image = models.ForeignKey(
"wagtailimages.Image",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="+",
)
api_fields = (
"intro",
"feed_image",
"related_links",
)
search_fields = Page.search_fields + [
index.SearchField("intro"),
]
class StandardIndexPageRelatedLink(Orderable, AbstractRelatedLink):
page = ParentalKey(
"StandardIndexPage", related_name="related_links", on_delete=models.CASCADE
)
StandardIndexPage.content_panels = Page.content_panels + [
FieldPanel("intro"),
InlinePanel("related_links", label="Related links"),
]
StandardIndexPage.promote_panels = [
MultiFieldPanel(Page.promote_panels, "Common page configuration"),
FieldPanel("feed_image"),
]
# Blog pages
class BlogEntryPage(Page):
page_ptr = models.OneToOneField(
Page, parent_link=True, related_name="+", on_delete=models.CASCADE
)
body = RichTextField()
tags = ClusterTaggableManager(through="BlogEntryPageTag", blank=True)
date = models.DateField("Post date")
feed_image = models.ForeignKey(
"wagtailimages.Image",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="+",
)
api_fields = (
APIField("body"),
APIField("tags"),
APIField("date"),
APIField("feed_image"),
APIField(
"feed_image_thumbnail",
serializer=ImageRenditionField("fill-300x300", source="feed_image"),
),
APIField("carousel_items"),
APIField("related_links"),
)
search_fields = Page.search_fields + [
index.SearchField("body"),
]
def get_blog_index(self):
# Find closest ancestor which is a blog index
return BlogIndexPage.ancestor_of(self).last()
class BlogEntryPageCarouselItem(Orderable, AbstractCarouselItem):
page = ParentalKey(
"BlogEntryPage", related_name="carousel_items", on_delete=models.CASCADE
)
class BlogEntryPageRelatedLink(Orderable, AbstractRelatedLink):
page = ParentalKey(
"BlogEntryPage", related_name="related_links", on_delete=models.CASCADE
)
class BlogEntryPageTag(TaggedItemBase):
content_object = ParentalKey(
"BlogEntryPage", related_name="tagged_items", on_delete=models.CASCADE
)
BlogEntryPage.content_panels = Page.content_panels + [
FieldPanel("date"),
FieldPanel("body"),
InlinePanel("carousel_items", label="Carousel items"),
InlinePanel("related_links", label="Related links"),
]
BlogEntryPage.promote_panels = [
MultiFieldPanel(Page.promote_panels, "Common page configuration"),
FieldPanel("feed_image"),
FieldPanel("tags"),
]
class BlogIndexPage(Page):
page_ptr = models.OneToOneField(
Page, parent_link=True, related_name="+", on_delete=models.CASCADE
)
intro = RichTextField(blank=True)
api_fields = (
"intro",
"related_links",
)
search_fields = Page.search_fields + [
index.SearchField("intro"),
]
def get_blog_entries(self):
# Get list of live blog pages that are descendants of this page
entries = BlogEntryPage.objects.descendant_of(self).live()
# Order by most recent date first
entries = entries.order_by("-date")
return entries
def get_context(self, request):
# Get blog entries
entries = self.get_blog_entries()
# Filter by tag
tag = request.GET.get("tag")
if tag:
entries = entries.filter(tags__name=tag)
paginator = Paginator(entries, per_page=10)
entries = paginator.get_page(request.GET.get("page"))
# Update template context
context = super().get_context(request)
context["entries"] = entries
return context
class BlogIndexPageRelatedLink(Orderable, AbstractRelatedLink):
page = ParentalKey(
"BlogIndexPage", related_name="related_links", on_delete=models.CASCADE
)
BlogIndexPage.content_panels = Page.content_panels + [
FieldPanel("intro"),
InlinePanel("related_links", label="Related links"),
]
# Events pages
class EventPage(Page):
page_ptr = models.OneToOneField(
Page, parent_link=True, related_name="+", on_delete=models.CASCADE
)
AUDIENCE_CHOICES = (
("public", "Public"),
("private", "Private"),
)
date_from = models.DateField("Start date")
date_to = models.DateField(
"End date",
null=True,
blank=True,
help_text="Not required if event is on a single day",
)
time_from = models.TimeField("Start time", null=True, blank=True)
time_to = models.TimeField("End time", null=True, blank=True)
audience = models.CharField(max_length=255, choices=AUDIENCE_CHOICES)
location = models.CharField(max_length=255)
body = RichTextField(blank=True)
cost = models.CharField(max_length=255)
signup_link = models.URLField(blank=True)
feed_image = models.ForeignKey(
"wagtailimages.Image",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="+",
)
api_fields = (
"date_from",
"date_to",
"time_from",
"time_to",
"audience",
"location",
"body",
"cost",
"signup_link",
"feed_image",
"carousel_items",
"related_links",
"speakers",
)
search_fields = Page.search_fields + [
index.SearchField("get_audience_display"),
index.SearchField("location"),
index.SearchField("body"),
]
def get_event_index(self):
# Find closest ancestor which is an event index
return EventIndexPage.objects.ancestor_of(self).last()
class EventPageCarouselItem(Orderable, AbstractCarouselItem):
page = ParentalKey(
"EventPage", related_name="carousel_items", on_delete=models.CASCADE
)
class EventPageRelatedLink(Orderable, AbstractRelatedLink):
page = ParentalKey(
"EventPage", related_name="related_links", on_delete=models.CASCADE
)
class EventPageSpeaker(Orderable, AbstractLinkFields):
page = ParentalKey("EventPage", related_name="speakers", on_delete=models.CASCADE)
first_name = models.CharField("Name", max_length=255, blank=True)
last_name = models.CharField("Surname", max_length=255, blank=True)
image = models.ForeignKey(
"wagtailimages.Image",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="+",
)
api_fields = (
"first_name",
"last_name",
"image",
)
panels = [
FieldPanel("first_name"),
FieldPanel("last_name"),
FieldPanel("image"),
MultiFieldPanel(AbstractLinkFields.panels, "Link"),
]
EventPage.content_panels = Page.content_panels + [
FieldPanel("date_from"),
FieldPanel("date_to"),
FieldPanel("time_from"),
FieldPanel("time_to"),
FieldPanel("location"),
FieldPanel("audience"),
FieldPanel("cost"),
FieldPanel("signup_link"),
InlinePanel("carousel_items", label="Carousel items"),
FieldPanel("body"),
InlinePanel("speakers", label="Speakers"),
InlinePanel("related_links", label="Related links"),
]
EventPage.promote_panels = [
MultiFieldPanel(Page.promote_panels, "Common page configuration"),
FieldPanel("feed_image"),
]
class EventIndexPage(Page):
page_ptr = models.OneToOneField(
Page, parent_link=True, related_name="+", on_delete=models.CASCADE
)
intro = RichTextField(blank=True)
api_fields = (
"intro",
"related_links",
)
search_fields = Page.search_fields + [
index.SearchField("intro"),
]
def get_events(self):
# Get list of live event pages that are descendants of this page
events = EventPage.objects.descendant_of(self).live()
# Filter events list to get ones that are either
# running now or start in the future
events = events.filter(date_from__gte=date.today())
# Order by date
events = events.order_by("date_from")
return events
class EventIndexPageRelatedLink(Orderable, AbstractRelatedLink):
page = ParentalKey(
"EventIndexPage", related_name="related_links", on_delete=models.CASCADE
)
EventIndexPage.content_panels = Page.content_panels + [
FieldPanel("intro"),
InlinePanel("related_links", label="Related links"),
]
# Person page
class PersonPage(Page, ContactFieldsMixin):
page_ptr = models.OneToOneField(
Page, parent_link=True, related_name="+", on_delete=models.CASCADE
)
first_name = models.CharField(max_length=255)
last_name = models.CharField(max_length=255)
intro = RichTextField(blank=True)
biography = RichTextField(blank=True)
image = models.ForeignKey(
"wagtailimages.Image",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="+",
)
feed_image = models.ForeignKey(
"wagtailimages.Image",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="+",
)
api_fields = (
"first_name",
"last_name",
"intro",
"biography",
"image",
"feed_image",
"related_links",
) + ContactFieldsMixin.api_fields
search_fields = Page.search_fields + [
index.SearchField("first_name"),
index.SearchField("last_name"),
index.SearchField("intro"),
index.SearchField("biography"),
]
class PersonPageRelatedLink(Orderable, AbstractRelatedLink):
page = ParentalKey(
"PersonPage", related_name="related_links", on_delete=models.CASCADE
)
PersonPage.content_panels = Page.content_panels + [
FieldPanel("first_name"),
FieldPanel("last_name"),
FieldPanel("intro"),
FieldPanel("biography"),
FieldPanel("image"),
MultiFieldPanel(ContactFieldsMixin.panels, "Contact"),
InlinePanel("related_links", label="Related links"),
]
PersonPage.promote_panels = [
MultiFieldPanel(Page.promote_panels, "Common page configuration"),
FieldPanel("feed_image"),
]
# Contact page
class ContactPage(Page, ContactFieldsMixin):
page_ptr = models.OneToOneField(
Page, parent_link=True, related_name="+", on_delete=models.CASCADE
)
body = RichTextField(blank=True)
feed_image = models.ForeignKey(
"wagtailimages.Image",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="+",
)
api_fields = (
"body",
"feed_image",
) + ContactFieldsMixin.api_fields
search_fields = Page.search_fields + [
index.SearchField("body"),
]
ContactPage.content_panels = Page.content_panels + [
FieldPanel("body"),
MultiFieldPanel(ContactFieldsMixin.panels, "Contact"),
]
ContactPage.promote_panels = [
MultiFieldPanel(Page.promote_panels, "Common page configuration"),
FieldPanel("feed_image"),
]
class FormField(AbstractFormField):
page = ParentalKey("FormPage", related_name="form_fields", on_delete=models.CASCADE)
class FormPage(AbstractForm):
page_ptr = models.OneToOneField(
Page, parent_link=True, related_name="+", on_delete=models.CASCADE
)
api_fields = [APIField("form_fields")]
content_panels = AbstractForm.content_panels + [
InlinePanel("form_fields", label="Form fields")
]