2
0

models.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. from django import forms
  2. from django.db import models
  3. from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
  4. from modelcluster.fields import ParentalManyToManyField
  5. from wagtail.admin.panels import (
  6. FieldPanel, MultiFieldPanel
  7. )
  8. from wagtail.fields import StreamField
  9. from wagtail.models import Page
  10. from wagtail.search import index
  11. from wagtail.snippets.models import register_snippet
  12. from bakerydemo.base.blocks import BaseStreamBlock
  13. @register_snippet
  14. class Country(models.Model):
  15. """
  16. A Django model to store set of countries of origin.
  17. It uses the `@register_snippet` decorator to allow it to be accessible
  18. via the Snippets UI (e.g. /admin/snippets/breads/country/) In the BreadPage
  19. model you'll see we use a ForeignKey to create the relationship between
  20. Country and BreadPage. This allows a single relationship (e.g only one
  21. Country can be added) that is one-way (e.g. Country will have no way to
  22. access related BreadPage objects).
  23. """
  24. title = models.CharField(max_length=100)
  25. def __str__(self):
  26. return self.title
  27. class Meta:
  28. verbose_name_plural = "Countries of Origin"
  29. @register_snippet
  30. class BreadIngredient(models.Model):
  31. """
  32. Standard Django model that is displayed as a snippet within the admin due
  33. to the `@register_snippet` decorator. We use a new piece of functionality
  34. available to Wagtail called the ParentalManyToManyField on the BreadPage
  35. model to display this. The Wagtail Docs give a slightly more detailed example
  36. https://docs.wagtail.org/en/stable/getting_started/tutorial.html#categories
  37. """
  38. name = models.CharField(max_length=255)
  39. panels = [
  40. FieldPanel('name'),
  41. ]
  42. def __str__(self):
  43. return self.name
  44. class Meta:
  45. verbose_name_plural = 'Bread ingredients'
  46. @register_snippet
  47. class BreadType(models.Model):
  48. """
  49. A Django model to define the bread type
  50. It uses the `@register_snippet` decorator to allow it to be accessible
  51. via the Snippets UI. In the BreadPage model you'll see we use a ForeignKey
  52. to create the relationship between BreadType and BreadPage. This allows a
  53. single relationship (e.g only one BreadType can be added) that is one-way
  54. (e.g. BreadType will have no way to access related BreadPage objects)
  55. """
  56. title = models.CharField(max_length=255)
  57. panels = [
  58. FieldPanel('title'),
  59. ]
  60. def __str__(self):
  61. return self.title
  62. class Meta:
  63. verbose_name_plural = "Bread types"
  64. class BreadPage(Page):
  65. """
  66. Detail view for a specific bread
  67. """
  68. introduction = models.TextField(
  69. help_text='Text to describe the page',
  70. blank=True)
  71. image = models.ForeignKey(
  72. 'wagtailimages.Image',
  73. null=True,
  74. blank=True,
  75. on_delete=models.SET_NULL,
  76. related_name='+',
  77. help_text='Landscape mode only; horizontal width between 1000px and 3000px.'
  78. )
  79. body = StreamField(
  80. BaseStreamBlock(), verbose_name="Page body", blank=True
  81. )
  82. origin = models.ForeignKey(
  83. Country,
  84. on_delete=models.SET_NULL,
  85. null=True,
  86. blank=True,
  87. )
  88. # We include related_name='+' to avoid name collisions on relationships.
  89. # e.g. there are two FooPage models in two different apps,
  90. # and they both have a FK to bread_type, they'll both try to create a
  91. # relationship called `foopage_objects` that will throw a valueError on
  92. # collision.
  93. bread_type = models.ForeignKey(
  94. 'breads.BreadType',
  95. null=True,
  96. blank=True,
  97. on_delete=models.SET_NULL,
  98. related_name='+'
  99. )
  100. ingredients = ParentalManyToManyField('BreadIngredient', blank=True)
  101. content_panels = Page.content_panels + [
  102. FieldPanel('introduction', classname="full"),
  103. FieldPanel('image'),
  104. FieldPanel('body'),
  105. FieldPanel('origin'),
  106. FieldPanel('bread_type'),
  107. MultiFieldPanel(
  108. [
  109. FieldPanel(
  110. 'ingredients',
  111. widget=forms.CheckboxSelectMultiple,
  112. ),
  113. ],
  114. heading="Additional Metadata",
  115. classname="collapsible collapsed"
  116. ),
  117. ]
  118. search_fields = Page.search_fields + [
  119. index.SearchField('body'),
  120. ]
  121. parent_page_types = ['BreadsIndexPage']
  122. class BreadsIndexPage(Page):
  123. """
  124. Index page for breads.
  125. This is more complex than other index pages on the bakery demo site as we've
  126. included pagination. We've separated the different aspects of the index page
  127. to be discrete functions to make it easier to follow
  128. """
  129. introduction = models.TextField(
  130. help_text='Text to describe the page',
  131. blank=True)
  132. image = models.ForeignKey(
  133. 'wagtailimages.Image',
  134. null=True,
  135. blank=True,
  136. on_delete=models.SET_NULL,
  137. related_name='+',
  138. help_text='Landscape mode only; horizontal width between 1000px and '
  139. '3000px.'
  140. )
  141. content_panels = Page.content_panels + [
  142. FieldPanel('introduction', classname="full"),
  143. FieldPanel('image'),
  144. ]
  145. # Can only have BreadPage children
  146. subpage_types = ['BreadPage']
  147. # Returns a queryset of BreadPage objects that are live, that are direct
  148. # descendants of this index page with most recent first
  149. def get_breads(self):
  150. return BreadPage.objects.live().descendant_of(
  151. self).order_by('-first_published_at')
  152. # Allows child objects (e.g. BreadPage objects) to be accessible via the
  153. # template. We use this on the HomePage to display child items of featured
  154. # content
  155. def children(self):
  156. return self.get_children().specific().live()
  157. # Pagination for the index page. We use the `django.core.paginator` as any
  158. # standard Django app would, but the difference here being we have it as a
  159. # method on the model rather than within a view function
  160. def paginate(self, request, *args):
  161. page = request.GET.get('page')
  162. paginator = Paginator(self.get_breads(), 12)
  163. try:
  164. pages = paginator.page(page)
  165. except PageNotAnInteger:
  166. pages = paginator.page(1)
  167. except EmptyPage:
  168. pages = paginator.page(paginator.num_pages)
  169. return pages
  170. # Returns the above to the get_context method that is used to populate the
  171. # template
  172. def get_context(self, request):
  173. context = super(BreadsIndexPage, self).get_context(request)
  174. # BreadPage objects (get_breads) are passed through pagination
  175. breads = self.paginate(request, self.get_breads())
  176. context['breads'] = breads
  177. return context