advanced02.rst 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. Custom Page Types
  2. =================
  3. In many cases, the built-in page types will be exactly what you need. There are, however,
  4. several reasons why you may need a custom page type. This tutorial will show you an example
  5. for creating a custom page type.
  6. Let's say that we need to make a special product page for all of our cupcakes. While our real bakery
  7. may have over 100 different types, we will limit this example to a small handful but enough to show
  8. how this works.
  9. Before we begin, you should have a general understanding of `Django models <https://docs.djangoproject.com/en/stable/topics/db/models/>`_
  10. and some Python skills. You can still follow along for an introduction to these concepts even without this knowledge.
  11. We are also going to be unable to cover every potential use case or scenario in this tutorial, but we hope that it will springboard
  12. any ideas that you have for your own website.
  13. Prep work for custom pages
  14. --------------------------
  15. We need to plan our page ahead of time. What fields will our custom page need, and what will we need our page
  16. to do? Take the time to write down the answer to these questions before you even touch the code. This is what
  17. we are writing down for Simple Sweet Dessert's custom cupcake page:
  18. **Our Prep Notes**
  19. 1. We want our page to page to list the attributes and descriptions of individual cupcakes.
  20. 2. We want to be able to display the cupcakes in cards automatically on a landing page.
  21. **Cupcake Page Fields:**
  22. * Name of cupcake (This could be the title of the page)
  23. * Photo of cupcake
  24. * Description of cupcake
  25. * Days when these cupcakes are made
  26. **Cupcake Landing Page Fields:**
  27. * Needs to be the parent page for the cupcake pages
  28. Setting up the page models
  29. --------------------------
  30. Just like in Django or Wagtail, you will need to set up your page models in the ``models.py`` file of your
  31. project. Navigate to ``mysite\website\models.py`` in your code editor and open up the ``models.py`` file.
  32. You should already see a few page models in there from Wagtail CRX, as well as imports at the top from the
  33. frameworks that we are using.
  34. .. code-block:: python
  35. """
  36. Create or customize your page models here.
  37. """
  38. from modelcluster.fields import ParentalKey
  39. from wagtailcrx.forms import CoderedFormField
  40. from wagtailcrx.models import (
  41. CoderedArticlePage,
  42. CoderedArticleIndexPage,
  43. CoderedEmail,
  44. CoderedFormPage,
  45. CoderedWebPage
  46. )
  47. class ArticlePage(CoderedArticlePage):
  48. """
  49. Article, suitable for news or blog content.
  50. """
  51. class Meta:
  52. verbose_name = 'Article'
  53. ordering = ['-first_published_at']
  54. # Only allow this page to be created beneath an ArticleIndexPage.
  55. parent_page_types = ['website.ArticleIndexPage']
  56. template = 'wagtailcrx/pages/article_page.html'
  57. search_template = 'wagtailcrx/pages/article_page.search.html'
  58. class ArticleIndexPage(CoderedArticleIndexPage):
  59. """
  60. Shows a list of article sub-pages.
  61. """
  62. class Meta:
  63. verbose_name = 'Article Landing Page'
  64. # Override to specify custom index ordering choice/default.
  65. index_query_pagemodel = 'website.ArticlePage'
  66. # Only allow ArticlePages beneath this page.
  67. subpage_types = ['website.ArticlePage']
  68. template = 'wagtailcrx/pages/article_index_page.html'
  69. class FormPage(CoderedFormPage):
  70. """
  71. A page with an html <form>.
  72. """
  73. class Meta:
  74. verbose_name = 'Form'
  75. template = 'wagtailcrx/pages/form_page.html'
  76. class FormPageField(CoderedFormField):
  77. """
  78. A field that links to a FormPage.
  79. """
  80. class Meta:
  81. ordering = ['sort_order']
  82. page = ParentalKey('FormPage', related_name='form_fields')
  83. class FormConfirmEmail(CoderedEmail):
  84. """
  85. Sends a confirmation email after submitting a FormPage.
  86. """
  87. page = ParentalKey('FormPage', related_name='confirmation_emails')
  88. class WebPage(CoderedWebPage):
  89. """
  90. General use page with featureful streamfield and SEO attributes.
  91. Template renders all Navbar and Footer snippets in existence.
  92. """
  93. class Meta:
  94. verbose_name = 'Web Page'
  95. template = 'wagtailcrx/pages/web_page.html'
  96. Before we begin adding our fields for our new page models, we should add the page class, meta class,
  97. and template information for our pages.
  98. * We our extending the ``CoderedWebPage`` model which is why it is wrapped in parentheses after we name our page model.
  99. * We are indicating that Cupcake pages are sub-pages of the Cupcake Landing Page.
  100. * We are specifying the template files that the page models should use, which should also be created in our ``templates\website\pages`` folder.
  101. Add this code below the other page models:
  102. .. code:: python
  103. class CupcakesIndexPage(CoderedWebPage):
  104. """
  105. Landing page for Cupcakes
  106. """
  107. class Meta:
  108. verbose_name = "Cupcakes Landing Page"
  109. # Override to specify custom index ordering choice/default.
  110. index_query_pagemodel = 'website.CupcakesPage'
  111. # Only allow CupcakesPages beneath this page.
  112. subpage_types = ['website.CupcakesPage']
  113. template = 'website/pages/cupcakes_index_page.html'
  114. class CupcakesPage(CoderedWebPage):
  115. """
  116. Custom page for individual cupcakes
  117. """
  118. class Meta:
  119. verbose_name = "Cupcakes Page"
  120. # Only allow this page to be created beneath an CupcakesIndexPage.
  121. parent_page_types = ['website.CupcakesIndexPage']
  122. template = "website/pages/cupcakes_page.html"
  123. At the top of each ``.html`` template page, we want to add these tags so that we have a basic functioning
  124. template prepared:
  125. .. code:: Django
  126. {% extends "wagtailcrx/pages/web_page.html" %}
  127. {% load wagtailcore_tags wagtailimages_tags wagtailcrx_tags %}
  128. Now we can turn our attention back to our page models, specifically the CupcakesPage.
  129. Since the name of the cupcake could just be the title of the page, we don't need to add a custom field
  130. for that information. We do, however, need a few fields.
  131. .. code:: python
  132. # At top of the file add these imports
  133. from django.db import models
  134. from wagtail.admin.edit_handlers import FieldPanel
  135. from wagtail.core.fields import RichTextField
  136. from wagtail.images import get_image_model_string
  137. from wagtail.images.edit_handlers import ImageChooserPanel
  138. class CupcakesPage(CoderedWebPage):
  139. """
  140. Custom page for individual cupcakes
  141. """
  142. class Meta:
  143. verbose_name = "Cupcakes Page"
  144. # Only allow this page to be created beneath an CupcakesIndexPage.
  145. parent_page_types = ['website.CupcakesIndexPage']
  146. template = "website/pages/cupcakes_page.html"
  147. # Cupcakes Page model fields
  148. description = RichTextField(
  149. verbose_name="Cupcake Description",
  150. null=True,
  151. blank=True,
  152. default=""
  153. )
  154. photo = models.ForeignKey(
  155. get_image_model_string(),
  156. null=True,
  157. blank=True,
  158. on_delete=models.SET_NULL,
  159. related_name='+',
  160. verbose_name='Cupcake Photo',
  161. )
  162. DAYS_CHOICES = (
  163. ("Weekends Only", "Weekends Only"),
  164. ("Monday-Friday", "Monday-Friday"),
  165. ("Tuesday/Thursday", "Tuesday/Thursday"),
  166. ("Seasonal", "Seasonal"),
  167. )
  168. days_available = models.CharField(
  169. choices = DAYS_CHOICES,
  170. max_length=20,
  171. default=""
  172. )
  173. # Add custom fields to the body
  174. body_content_panels = CoderedWebPage.body_content_panels + [
  175. FieldPanel("description"),
  176. FieldPanel("photo"),
  177. FieldPanel("days_available"),
  178. ]
  179. **What's happening?**
  180. Okay, we had to add some imports at the top to be able to use these field types in our model.
  181. If we try to makemigrations/migrate without having these imported, it will show an error.
  182. Next, we added the fields we need with the field types that tell it how to function. Our description
  183. will be a RichTextField which is essentially a text box that allows formatting. Then our photo needs to be
  184. able to be associated with the page as well as be uploaded via an ImageChooserPanel -- the popup we get when
  185. we want to add a photo in the CMS.
  186. Finally, we added a field for choosing which days the cupcake is available and we made this a dropdown choice
  187. panel. We had to set the choices first, then include the choices in our field selector.
  188. At the bottom of our model, we are telling it to allow for the standard CMS page builder blocks as well as our custom
  189. fields.
  190. Now we can run ``python manage.py makemigrations website`` and ``python manage.py migrate`` to test our work.
  191. It should migrate successfully. (If not, read what the error says and fix it. A typo can cause huge problems!)
  192. Run the server again with ``python manage.py runserver`` to see how it looks in your CMS admin.
  193. You should now see Cupcake Landing Page as a child page choice under Home page. Choose this, add a title and
  194. publish it. The page does not have a template made; however, it uses the basic CodeRed Web Page so it will display
  195. something.
  196. Now you can add Cupcake Pages, which are sub-pages of the Cupcake Landing Page. While the fields for this page
  197. do not currently show up on the published page, you can add content in the editor mode.
  198. .. note::
  199. We have to create a custom page template to display the custom fields on the published page.
  200. Building our custom page templates
  201. ----------------------------------
  202. Since our models are working and we can add content to the fields, we can begin creating our custom page
  203. template. Navigate to the ``cupcakes_page.html`` file in your project's templates folder. We added the basic
  204. page tags at the top of the page earlier. In case you need to add them, they are:
  205. .. code::
  206. {% extends "wagtailcrx/pages/web_page.html" %}
  207. {% load wagtailcore_tags wagtailimages_tags wagtailcrx_tags %}
  208. Now we want to tell the page to not display the page's title where the cover image would be if there is no cover
  209. image (because we plan to use the page's title aka the cupcake name elsewhere on the page).
  210. The standard CodeRed Web Page template has an ``{% if %} {% else %}`` statement regarding cover images that says to show the page title when a cover image
  211. is not available. We will add that same code to our page but remove the ``else`` statement so that it does nothing when a cover image is not available.
  212. We will also set up the basic layout for our page: a two half-sized columns in a row. To pull in our field data,
  213. we reference the page and then the field, like this ``{{page.title}}`` or ``{{page.description}}``.
  214. For the image, we specify what size it should be and give it a shorter reference name for the variable.
  215. We added a few Bootstrap classes and custom classes to change the padding a little and some text colors, as well
  216. as add a border around the image that is centered within the column.
  217. **Our template code:**
  218. .. code:: Django
  219. {% extends "wagtailcrx/pages/web_page.html" %}
  220. {% load wagtailcore_tags wagtailimages_tags wagtailcrx_tags %}
  221. {% block content_pre_body %}
  222. {% if self.cover_image %}
  223. {% image page.cover_image fill-2000x1000 as cover_image %}
  224. <div class="jumbotron jumotron-fluid" style="height:400px;background-image:url({{cover_image.url}});background-repeat:no-repeat; background-size:cover; background-position:center center;">
  225. </div>
  226. {% endif %}
  227. {% endblock %}
  228. {% block content_body %}
  229. <div class="block-row">
  230. <div class="container-fluid">
  231. <div class="row m-4">
  232. <div class="col-lg-6">
  233. {% if page.photo %}
  234. {% image page.photo fill-300x300 as cupcake %}
  235. <div class="text-center">
  236. <img class="border-cherry" src="{{cupcake.url}}" alt="photo of {{page.title}}">
  237. </div>
  238. {% endif %}
  239. </div>
  240. <div class="col-lg-6">
  241. <div class="py-lg-5">
  242. <h2>{{page.title}}</h2>
  243. <lead class="text-cherry">{{page.days_available}}</lead>
  244. {% if page.description %}
  245. <p>{{page.description|richtext}}</p>
  246. {% endif %}
  247. </div>
  248. </div>
  249. </div>
  250. </div>
  251. </div>
  252. {% endblock %}
  253. We added some content for a cupcake page in the CMS and published it.
  254. Let's take a look.
  255. .. figure:: img/cupcake_page_published.png
  256. :alt: Our customized cupcake page so far
  257. Our customized cupcake page so far
  258. It works! Continue to add cupcake pages until you have a decent amount of them --
  259. five or so would be good.
  260. Building the Cupcake Landing Page
  261. ---------------------------------
  262. While we could simply use the the default "Show Child Pages" option for the page, a list of links
  263. is rather boring. We also want the page to automatically update whenever we add a new cupcake to save us lots of time
  264. and trouble. How can we dynamically update our Cupcake Landing Page?
  265. .. code:: Django
  266. {% extends "wagtailcrx/pages/web_page.html" %}
  267. {% load wagtailcore_tags wagtailimages_tags wagtailcrx_tags %}
  268. {% block index_content %}
  269. <div class="container">
  270. <div class="row d-flex">
  271. {% for cupcake in page.get_children.specific %}
  272. <div class="col m-3">
  273. <div class="card border-cherry" style="width: 18rem;">
  274. {% if cupcake.photo %}
  275. {% image cupcake.photo fill-300x300 as cupcake_photo %}
  276. <a href="{{cupcake.url}}">
  277. <img class="card-img-top w-100" src="{{cupcake_photo.url}}" alt="{{cupcake.title}}">
  278. </a>
  279. {% endif %}
  280. <div class="card-body">
  281. <div class="card-text">
  282. <h3><a class="text-cherry" href="{{cupcake.url}}">{{cupcake.title}}</a></h3>
  283. <p class="lead">{{cupcake.days_available}}</p>
  284. </div>
  285. </div>
  286. </div>
  287. </div>
  288. {% endfor %}
  289. </div>
  290. </div>
  291. {% endblock %}
  292. **What's happening?**
  293. We are using a ``{% block index_content %}`` and a ``{% for cupcake in page.get_children.specific %}`` loop that pulls
  294. in content from the child/sub-pages. Our new variable for the sub-pages is ``cupcake``, so we reference the fields like so:
  295. ``{{cupcake.title}}``. In the CMS we want to make show that "Show Child Pages" is NOT selected because it will just show
  296. the list of page links in addition to our custom cards. This is what our published landing page looks like now:
  297. .. figure:: img/cupcake_landing_published.png
  298. :alt: Our customized landing cupcake page so far
  299. Our customized cupcake landing page dynamically pulling in child pages as cards
  300. Now we can keep customizing our templates until we get the design that we want.