models.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. from __future__ import unicode_literals
  2. from django.db import models
  3. from modelcluster.fields import ParentalKey
  4. from modelcluster.models import ClusterableModel
  5. from wagtail.admin.panels import (
  6. FieldPanel,
  7. FieldRowPanel,
  8. InlinePanel,
  9. MultiFieldPanel,
  10. PublishingPanel,
  11. )
  12. from wagtail.contrib.forms.models import AbstractEmailForm, AbstractFormField
  13. from wagtail.fields import RichTextField, StreamField
  14. from wagtail.models import Collection, DraftStateMixin, Page, RevisionMixin
  15. from wagtail.search import index
  16. from wagtail.snippets.models import register_snippet
  17. from .blocks import BaseStreamBlock
  18. @register_snippet
  19. class Person(index.Indexed, ClusterableModel):
  20. """
  21. A Django model to store Person objects.
  22. It uses the `@register_snippet` decorator to allow it to be accessible
  23. via the Snippets UI (e.g. /admin/snippets/base/person/)
  24. `Person` uses the `ClusterableModel`, which allows the relationship with
  25. another model to be stored locally to the 'parent' model (e.g. a PageModel)
  26. until the parent is explicitly saved. This allows the editor to use the
  27. 'Preview' button, to preview the content, without saving the relationships
  28. to the database.
  29. https://github.com/wagtail/django-modelcluster
  30. """
  31. first_name = models.CharField("First name", max_length=254)
  32. last_name = models.CharField("Last name", max_length=254)
  33. job_title = models.CharField("Job title", max_length=254)
  34. image = models.ForeignKey(
  35. "wagtailimages.Image",
  36. null=True,
  37. blank=True,
  38. on_delete=models.SET_NULL,
  39. related_name="+",
  40. )
  41. panels = [
  42. MultiFieldPanel(
  43. [
  44. FieldRowPanel(
  45. [
  46. FieldPanel("first_name", classname="col6"),
  47. FieldPanel("last_name", classname="col6"),
  48. ]
  49. )
  50. ],
  51. "Name",
  52. ),
  53. FieldPanel("job_title"),
  54. FieldPanel("image"),
  55. ]
  56. search_fields = [
  57. index.SearchField("first_name"),
  58. index.SearchField("last_name"),
  59. ]
  60. @property
  61. def thumb_image(self):
  62. # Returns an empty string if there is no profile pic or the rendition
  63. # file can't be found.
  64. try:
  65. return self.image.get_rendition("fill-50x50").img_tag()
  66. except: # noqa: E722 FIXME: remove bare 'except:'
  67. return ""
  68. def __str__(self):
  69. return "{} {}".format(self.first_name, self.last_name)
  70. class Meta:
  71. verbose_name = "Person"
  72. verbose_name_plural = "People"
  73. @register_snippet
  74. class FooterText(DraftStateMixin, RevisionMixin, models.Model):
  75. """
  76. This provides editable text for the site footer. Again it uses the decorator
  77. `register_snippet` to allow it to be accessible via the admin. It is made
  78. accessible on the template via a template tag defined in base/templatetags/
  79. navigation_tags.py
  80. """
  81. body = RichTextField()
  82. panels = [
  83. FieldPanel("body"),
  84. PublishingPanel(),
  85. ]
  86. def __str__(self):
  87. return "Footer text"
  88. class Meta:
  89. verbose_name_plural = "Footer Text"
  90. class StandardPage(Page):
  91. """
  92. A generic content page. On this demo site we use it for an about page but
  93. it could be used for any type of page content that only needs a title,
  94. image, introduction and body field
  95. """
  96. introduction = models.TextField(help_text="Text to describe the page", blank=True)
  97. image = models.ForeignKey(
  98. "wagtailimages.Image",
  99. null=True,
  100. blank=True,
  101. on_delete=models.SET_NULL,
  102. related_name="+",
  103. help_text="Landscape mode only; horizontal width between 1000px and 3000px.",
  104. )
  105. body = StreamField(
  106. BaseStreamBlock(), verbose_name="Page body", blank=True, use_json_field=True
  107. )
  108. content_panels = Page.content_panels + [
  109. FieldPanel("introduction", classname="full"),
  110. FieldPanel("body"),
  111. FieldPanel("image"),
  112. ]
  113. class HomePage(Page):
  114. """
  115. The Home Page. This looks slightly more complicated than it is. You can
  116. see if you visit your site and edit the homepage that it is split between
  117. a:
  118. - Hero area
  119. - Body area
  120. - A promotional area
  121. - Moveable featured site sections
  122. """
  123. # Hero section of HomePage
  124. image = models.ForeignKey(
  125. "wagtailimages.Image",
  126. null=True,
  127. blank=True,
  128. on_delete=models.SET_NULL,
  129. related_name="+",
  130. help_text="Homepage image",
  131. )
  132. hero_text = models.CharField(
  133. max_length=255, help_text="Write an introduction for the bakery"
  134. )
  135. hero_cta = models.CharField(
  136. verbose_name="Hero CTA",
  137. max_length=255,
  138. help_text="Text to display on Call to Action",
  139. )
  140. hero_cta_link = models.ForeignKey(
  141. "wagtailcore.Page",
  142. null=True,
  143. blank=True,
  144. on_delete=models.SET_NULL,
  145. related_name="+",
  146. verbose_name="Hero CTA link",
  147. help_text="Choose a page to link to for the Call to Action",
  148. )
  149. # Body section of the HomePage
  150. body = StreamField(
  151. BaseStreamBlock(),
  152. verbose_name="Home content block",
  153. blank=True,
  154. use_json_field=True,
  155. )
  156. # Promo section of the HomePage
  157. promo_image = models.ForeignKey(
  158. "wagtailimages.Image",
  159. null=True,
  160. blank=True,
  161. on_delete=models.SET_NULL,
  162. related_name="+",
  163. help_text="Promo image",
  164. )
  165. promo_title = models.CharField(
  166. blank=True, max_length=255, help_text="Title to display above the promo copy"
  167. )
  168. promo_text = RichTextField(
  169. null=True, blank=True, max_length=1000, help_text="Write some promotional copy"
  170. )
  171. # Featured sections on the HomePage
  172. # You will see on templates/base/home_page.html that these are treated
  173. # in different ways, and displayed in different areas of the page.
  174. # Each list their children items that we access via the children function
  175. # that we define on the individual Page models e.g. BlogIndexPage
  176. featured_section_1_title = models.CharField(
  177. blank=True, max_length=255, help_text="Title to display above the promo copy"
  178. )
  179. featured_section_1 = models.ForeignKey(
  180. "wagtailcore.Page",
  181. null=True,
  182. blank=True,
  183. on_delete=models.SET_NULL,
  184. related_name="+",
  185. help_text="First featured section for the homepage. Will display up to "
  186. "three child items.",
  187. verbose_name="Featured section 1",
  188. )
  189. featured_section_2_title = models.CharField(
  190. blank=True, max_length=255, help_text="Title to display above the promo copy"
  191. )
  192. featured_section_2 = models.ForeignKey(
  193. "wagtailcore.Page",
  194. null=True,
  195. blank=True,
  196. on_delete=models.SET_NULL,
  197. related_name="+",
  198. help_text="Second featured section for the homepage. Will display up to "
  199. "three child items.",
  200. verbose_name="Featured section 2",
  201. )
  202. featured_section_3_title = models.CharField(
  203. blank=True, max_length=255, help_text="Title to display above the promo copy"
  204. )
  205. featured_section_3 = models.ForeignKey(
  206. "wagtailcore.Page",
  207. null=True,
  208. blank=True,
  209. on_delete=models.SET_NULL,
  210. related_name="+",
  211. help_text="Third featured section for the homepage. Will display up to "
  212. "six child items.",
  213. verbose_name="Featured section 3",
  214. )
  215. content_panels = Page.content_panels + [
  216. MultiFieldPanel(
  217. [
  218. FieldPanel("image"),
  219. FieldPanel("hero_text", classname="full"),
  220. MultiFieldPanel(
  221. [
  222. FieldPanel("hero_cta"),
  223. FieldPanel("hero_cta_link"),
  224. ]
  225. ),
  226. ],
  227. heading="Hero section",
  228. ),
  229. MultiFieldPanel(
  230. [
  231. FieldPanel("promo_image"),
  232. FieldPanel("promo_title"),
  233. FieldPanel("promo_text"),
  234. ],
  235. heading="Promo section",
  236. ),
  237. FieldPanel("body"),
  238. MultiFieldPanel(
  239. [
  240. MultiFieldPanel(
  241. [
  242. FieldPanel("featured_section_1_title"),
  243. FieldPanel("featured_section_1"),
  244. ]
  245. ),
  246. MultiFieldPanel(
  247. [
  248. FieldPanel("featured_section_2_title"),
  249. FieldPanel("featured_section_2"),
  250. ]
  251. ),
  252. MultiFieldPanel(
  253. [
  254. FieldPanel("featured_section_3_title"),
  255. FieldPanel("featured_section_3"),
  256. ]
  257. ),
  258. ],
  259. heading="Featured homepage sections",
  260. classname="collapsible",
  261. ),
  262. ]
  263. def __str__(self):
  264. return self.title
  265. class GalleryPage(Page):
  266. """
  267. This is a page to list locations from the selected Collection. We use a Q
  268. object to list any Collection created (/admin/collections/) even if they
  269. contain no items. In this demo we use it for a GalleryPage,
  270. and is intended to show the extensibility of this aspect of Wagtail
  271. """
  272. introduction = models.TextField(help_text="Text to describe the page", blank=True)
  273. image = models.ForeignKey(
  274. "wagtailimages.Image",
  275. null=True,
  276. blank=True,
  277. on_delete=models.SET_NULL,
  278. related_name="+",
  279. help_text="Landscape mode only; horizontal width between 1000px and " "3000px.",
  280. )
  281. body = StreamField(
  282. BaseStreamBlock(), verbose_name="Page body", blank=True, use_json_field=True
  283. )
  284. collection = models.ForeignKey(
  285. Collection,
  286. limit_choices_to=~models.Q(name__in=["Root"]),
  287. null=True,
  288. blank=True,
  289. on_delete=models.SET_NULL,
  290. help_text="Select the image collection for this gallery.",
  291. )
  292. content_panels = Page.content_panels + [
  293. FieldPanel("introduction", classname="full"),
  294. FieldPanel("body"),
  295. FieldPanel("image"),
  296. FieldPanel("collection"),
  297. ]
  298. # Defining what content type can sit under the parent. Since it's a blank
  299. # array no subpage can be added
  300. subpage_types = []
  301. class FormField(AbstractFormField):
  302. """
  303. Wagtailforms is a module to introduce simple forms on a Wagtail site. It
  304. isn't intended as a replacement to Django's form support but as a quick way
  305. to generate a general purpose data-collection form or contact form
  306. without having to write code. We use it on the site for a contact form. You
  307. can read more about Wagtail forms at:
  308. https://docs.wagtail.org/en/stable/reference/contrib/forms/index.html
  309. """
  310. page = ParentalKey("FormPage", related_name="form_fields", on_delete=models.CASCADE)
  311. class FormPage(AbstractEmailForm):
  312. image = models.ForeignKey(
  313. "wagtailimages.Image",
  314. null=True,
  315. blank=True,
  316. on_delete=models.SET_NULL,
  317. related_name="+",
  318. )
  319. body = StreamField(BaseStreamBlock(), use_json_field=True)
  320. thank_you_text = RichTextField(blank=True)
  321. # Note how we include the FormField object via an InlinePanel using the
  322. # related_name value
  323. content_panels = AbstractEmailForm.content_panels + [
  324. FieldPanel("image"),
  325. FieldPanel("body"),
  326. InlinePanel("form_fields", label="Form fields"),
  327. FieldPanel("thank_you_text", classname="full"),
  328. MultiFieldPanel(
  329. [
  330. FieldRowPanel(
  331. [
  332. FieldPanel("from_address", classname="col6"),
  333. FieldPanel("to_address", classname="col6"),
  334. ]
  335. ),
  336. FieldPanel("subject"),
  337. ],
  338. "Email",
  339. ),
  340. ]