models.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. from django.db import models
  2. from modelcluster.fields import ParentalKey
  3. from wagtail.admin.panels import (
  4. FieldPanel,
  5. HelpPanel,
  6. MultiFieldPanel,
  7. MultipleChooserPanel,
  8. )
  9. from wagtail.api import APIField
  10. from wagtail.fields import RichTextField, StreamField
  11. from wagtail.models import Orderable, Page
  12. from wagtail.search import index
  13. from bakerydemo.base.blocks import BaseStreamBlock
  14. from bakerydemo.headless import CustomHeadlessMixin
  15. from bakerydemo.serializers import RichTextSerializer
  16. from .blocks import RecipeStreamBlock
  17. class RecipePersonRelationship(Orderable, models.Model):
  18. """
  19. This defines the relationship between the `Person` within the `base`
  20. app and the RecipePage below. This allows people to be added to a RecipePage.
  21. We have created a two way relationship between RecipePage and Person using
  22. the ParentalKey and ForeignKey
  23. """
  24. page = ParentalKey(
  25. "RecipePage",
  26. related_name="recipe_person_relationship",
  27. on_delete=models.CASCADE,
  28. )
  29. person = models.ForeignKey(
  30. "base.Person",
  31. related_name="person_recipe_relationship",
  32. on_delete=models.CASCADE,
  33. )
  34. panels = [FieldPanel("person")]
  35. api_fields = [
  36. APIField("page"),
  37. APIField("person"),
  38. ]
  39. class RecipePage(CustomHeadlessMixin, Page):
  40. """
  41. Recipe pages are more complex than blog pages, demonstrating more advanced StreamField patterns.
  42. """
  43. date_published = models.DateField("Date article published", blank=True, null=True)
  44. subtitle = models.CharField(blank=True, max_length=255)
  45. introduction = models.TextField(blank=True, max_length=500)
  46. backstory = StreamField(
  47. BaseStreamBlock(),
  48. # Demonstrate block_counts to keep the backstory concise.
  49. block_counts={
  50. "heading_block": {"max_num": 1},
  51. "image_block": {"max_num": 1},
  52. "embed_block": {"max_num": 1},
  53. },
  54. blank=True,
  55. use_json_field=True,
  56. help_text="Use only a minimum number of headings and large blocks.",
  57. )
  58. # An example of using rich text for single-line content.
  59. recipe_headline = RichTextField(
  60. blank=True,
  61. max_length=120,
  62. features=["bold", "italic", "link"],
  63. help_text="Keep to a single line",
  64. )
  65. body = StreamField(
  66. RecipeStreamBlock(),
  67. blank=True,
  68. use_json_field=True,
  69. help_text="The recipe’s step-by-step instructions and any other relevant information.",
  70. )
  71. content_panels = Page.content_panels + [
  72. FieldPanel("date_published"),
  73. # Using `title` to make a field larger.
  74. FieldPanel("subtitle", classname="title"),
  75. MultiFieldPanel(
  76. [
  77. # Example use case for HelpPanel.
  78. HelpPanel(
  79. "Refer to keywords analysis and correct international ingredients names to craft the best introduction backstory, and headline."
  80. ),
  81. FieldPanel("introduction"),
  82. # StreamField inside a MultiFieldPanel.
  83. FieldPanel("backstory"),
  84. FieldPanel("recipe_headline"),
  85. ],
  86. heading="Preface",
  87. ),
  88. FieldPanel("body"),
  89. MultipleChooserPanel(
  90. "recipe_person_relationship",
  91. chooser_field_name="person",
  92. heading="Authors",
  93. label="Author",
  94. help_text="Select between one and three authors",
  95. panels=None,
  96. min_num=1,
  97. max_num=3,
  98. ),
  99. ]
  100. search_fields = Page.search_fields + [
  101. index.SearchField("backstory"),
  102. index.SearchField("body"),
  103. ]
  104. api_fields = [
  105. APIField("date_published"),
  106. APIField("subtitle"),
  107. APIField("introduction"),
  108. APIField("backstory"),
  109. APIField("recipe_headline", serializer=RichTextSerializer()),
  110. APIField("body"),
  111. APIField("recipe_person_relationship"),
  112. ]
  113. def authors(self):
  114. """
  115. Returns the RecipePage's related people. Again note that we are using
  116. the ParentalKey's related_name from the RecipePersonRelationship model
  117. to access these objects. This allows us to access the Person objects
  118. with a loop on the template. If we tried to access the recipe_person_
  119. relationship directly we'd print `recipe.RecipePersonRelationship.None`
  120. """
  121. # Only return authors that are not in draft
  122. return [
  123. n.person
  124. for n in self.recipe_person_relationship.filter(
  125. person__live=True
  126. ).select_related("person")
  127. ]
  128. # Specifies parent to Recipe as being RecipeIndexPages
  129. parent_page_types = ["RecipeIndexPage"]
  130. # Specifies what content types can exist as children of RecipePage.
  131. # Empty list means that no child content types are allowed.
  132. subpage_types = []
  133. class RecipeIndexPage(CustomHeadlessMixin, Page):
  134. """
  135. Index page for recipe.
  136. We need to alter the page model's context to return the child page objects,
  137. the RecipePage objects, so that it works as an index page
  138. """
  139. introduction = models.TextField(help_text="Text to describe the page", blank=True)
  140. content_panels = Page.content_panels + [
  141. FieldPanel("introduction"),
  142. ]
  143. api_fields = [
  144. APIField("introduction"),
  145. ]
  146. # Specifies that only RecipePage objects can live under this index page
  147. subpage_types = ["RecipePage"]
  148. # Defines a method to access the children of the page (e.g. RecipePage
  149. # objects).
  150. def children(self):
  151. return self.get_children().specific().live()
  152. # Overrides the context to list all child items, that are live, by the
  153. # date that they were published
  154. # https://docs.wagtail.org/en/stable/getting_started/tutorial.html#overriding-context
  155. def get_context(self, request):
  156. context = super(RecipeIndexPage, self).get_context(request)
  157. context["recipes"] = (
  158. RecipePage.objects.descendant_of(self).live().order_by("-date_published")
  159. )
  160. return context