tutorial.rst 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568
  1. Your first Wagtail site
  2. =======================
  3. .. note::
  4. This tutorial covers setting up a brand new Wagtail project. If you'd like to add Wagtail to an existing Django project instead, see :doc:`integrating_into_django`.
  5. 1. Install Wagtail and its dependencies::
  6. pip install wagtail
  7. 2. Start your site::
  8. wagtail start mysite
  9. cd mysite
  10. Wagtail provides a ``start`` command similar to
  11. ``django-admin.py startproject``. Running ``wagtail start mysite`` in
  12. your project will generate a new ``mysite`` folder with a few
  13. Wagtail-specific extras, including the required project settings, a
  14. "home" app with a blank ``HomePage`` model and basic templates and a sample
  15. "search" app.
  16. 3. Install project dependencies::
  17. pip install -r requirements.txt
  18. This ensures that you have the relevant version of Django for the project you've just created.
  19. 4. Create the database::
  20. python manage.py migrate
  21. If you haven't updated the project settings, this will be a SQLite
  22. database file in the project directory.
  23. 5. Create an admin user::
  24. python manage.py createsuperuser
  25. 6. ``python manage.py runserver`` If everything worked,
  26. http://127.0.0.1:8000 will show you a welcome page
  27. .. figure:: ../_static/images/tutorial/tutorial_1.png
  28. :alt: Wagtail welcome message
  29. You can now access the administrative area at http://127.0.0.1:8000/admin
  30. .. figure:: ../_static/images/tutorial/tutorial_2.png
  31. :alt: Administrative screen
  32. Extend the HomePage model
  33. -------------------------
  34. Out of the box, the "home" app defines a blank ``HomePage`` model in ``models.py``, along with a migration that creates a homepage and configures Wagtail to use it.
  35. Edit ``home/models.py`` as follows, to add a ``body`` field to the model:
  36. .. code-block:: python
  37. from __future__ import unicode_literals
  38. from django.db import models
  39. from wagtail.wagtailcore.models import Page
  40. from wagtail.wagtailcore.fields import RichTextField
  41. from wagtail.wagtailadmin.edit_handlers import FieldPanel
  42. class HomePage(Page):
  43. body = RichTextField(blank=True)
  44. content_panels = Page.content_panels + [
  45. FieldPanel('body', classname="full")
  46. ]
  47. ``body`` is defined as ``RichTextField``, a special Wagtail field. You
  48. can use any of the `Django core fields <https://docs.djangoproject.com/en/1.8/ref/models/fields/>`__. ``content_panels`` define the
  49. capabilities and the layout of the editing interface. :doc:`More on creating Page models. <../topics/pages>`
  50. Run ``python manage.py makemigrations``, then
  51. ``python manage.py migrate`` to update the database with your model
  52. changes. You must run the above commands each time you make changes to
  53. the model definition.
  54. You can now edit the homepage within the Wagtail admin area (go to Explorer, Homepage, then Edit) to see the new body field. Enter some text into the body field, and publish the page.
  55. The page template now needs to be updated to reflect the changes made
  56. to the model. Wagtail uses normal Django templates to render each page
  57. type. It automatically generates a template filename from the model name
  58. by separating capital letters with underscores (e.g. HomePage becomes
  59. home\_page.html). Edit
  60. ``home/templates/home/home_page.html`` to contain the following:
  61. .. code-block:: html+django
  62. {% extends "base.html" %}
  63. {% load wagtailcore_tags %}
  64. {% block body_class %}template-homepage{% endblock %}
  65. {% block content %}
  66. {{ page.body|richtext }}
  67. {% endblock %}
  68. .. figure:: ../_static/images/tutorial/tutorial_3.png
  69. :alt: Updated homepage
  70. Wagtail template tags
  71. ~~~~~~~~~~~~~~~~~~~~~
  72. Wagtail provides a number of :ref:`template tags & filters <template-tags-and-filters>`
  73. which can be loaded by including ``{% load wagtailcore_tags %}`` at the top of
  74. your template file.
  75. In this tutorial, we use the `richtext` filter to escape and print the contents
  76. of a ``RichTextField``:
  77. .. code-block:: html+django
  78. {% load wagtailcore_tags %}
  79. {{ page.body|richtext }}
  80. Produces:
  81. .. code-block:: html
  82. <div class="rich-text">
  83. <p>
  84. <b>Welcome</b> to our new site!
  85. </p>
  86. </div>
  87. **Note:** You'll need to include ``{% load wagtailcore_tags %}`` in each
  88. template that uses Wagtail's tags. Django will throw a ``TemplateSyntaxError``
  89. if the tags aren't loaded.
  90. A basic blog
  91. ------------
  92. We are now ready to create a blog. To do so, run
  93. ``python manage.py startapp blog`` to create a new app in your Wagtail site.
  94. Add the new ``blog`` app to ``INSTALLED_APPS`` in ``mysite/settings/base.py``.
  95. Blog Index and Posts
  96. ~~~~~~~~~~~~~~~~~~~~
  97. Lets start with a simple index page for our blog. In ``blog/models.py``:
  98. .. code-block:: python
  99. class BlogIndexPage(Page):
  100. intro = RichTextField(blank=True)
  101. content_panels = Page.content_panels + [
  102. FieldPanel('intro', classname="full")
  103. ]
  104. Run ``python manage.py makemigrations`` and ``python manage.py migrate``.
  105. Since the model is called ``BlogIndexPage``, the default template name
  106. (unless we override it) will be ``blog/templates/blog/blog_index_page.html:``
  107. .. code-block:: html+django
  108. {% extends "base.html" %}
  109. {% load wagtailcore_tags %}
  110. {% block body_class %}template-blogindexpage{% endblock %}
  111. {% block content %}
  112. <h1>{{ page.title }}</h1>
  113. <div class="intro">{{ page.intro|richtext }}</div>
  114. {% for post in page.get_children %}
  115. <h2><a href="{% slugurl post.slug %}">{{ post.title }}</a></h2>
  116. {{ post.specific.intro }}
  117. {{ post.specific.body|richtext }}
  118. {% endfor %}
  119. {% endblock %}
  120. Most of this should be familiar, but we'll explain ``get_children`` a bit later.
  121. Note the ``slugurl`` tag, which is similar to Django's ``url`` tag but
  122. intended to take a Wagtail slug as an argument.
  123. In the Wagtail admin, create a ``BlogIndexPage`` under the Homepage,
  124. make sure it has the slug "blog" on the Promote tab, and publish it.
  125. You should now be able to access the url ``/blog`` on your site
  126. (note how the slug from the Promote tab defines the page URL).
  127. Now we need a model and template for our blog posts. In ``blog/models.py``:
  128. .. code-block:: python
  129. from django.db import models
  130. from wagtail.wagtailcore.models import Page
  131. from wagtail.wagtailcore.fields import RichTextField
  132. from wagtail.wagtailadmin.edit_handlers import FieldPanel
  133. from wagtail.wagtailsearch import index
  134. class BlogPage(Page):
  135. date = models.DateField("Post date")
  136. intro = models.CharField(max_length=250)
  137. body = RichTextField(blank=True)
  138. search_fields = Page.search_fields + [
  139. index.SearchField('intro'),
  140. index.SearchField('body'),
  141. ]
  142. content_panels = Page.content_panels + [
  143. FieldPanel('date'),
  144. FieldPanel('intro'),
  145. FieldPanel('body', classname="full")
  146. ]
  147. .. note::
  148. On Wagtail versions before 1.5, ``search_fields`` needs to be defined as a tuple:
  149. .. code-block:: python
  150. search_fields = Page.search_fields + (
  151. index.SearchField('intro'),
  152. index.SearchField('body'),
  153. )
  154. Run ``python manage.py makemigrations`` and ``python manage.py migrate``.
  155. Create a template at ``blog/templates/blog/blog_page.html``:
  156. .. code-block:: html+django
  157. {% extends "base.html" %}
  158. {% load wagtailcore_tags %}
  159. {% block body_class %}template-blogpage{% endblock %}
  160. {% block content %}
  161. <h1>{{ page.title }}</h1>
  162. <p class="meta">{{ page.date }}</p>
  163. <div class="intro">{{ page.intro }}</div>
  164. {{ page.body|richtext }}
  165. <p><a href="{{ page.get_parent.url }}">Return to blog</a></p>
  166. {% endblock %}
  167. Note the use of Wagtail's built-in ``get_parent()`` method to obtain the
  168. URL of the blog this post is a part of.
  169. Now create a few blog posts as children of ``BlogIndexPage.``
  170. Be sure to select type "BlogPage" when creating your posts.
  171. .. figure:: ../_static/images/tutorial/tutorial_4a.png
  172. :alt: Create blog post as child of BlogIndex
  173. .. figure:: ../_static/images/tutorial/tutorial_4b.png
  174. :alt: Choose type BlogPost
  175. Wagtail gives you full control over what kinds of content can be created under
  176. various parent content types, but we won't go into that here. By default,
  177. any page type can be a child of any other page type.
  178. .. figure:: ../_static/images/tutorial/tutorial_5.png
  179. :alt: Page edit screen
  180. You should now have the very beginnings of a working blog.
  181. Access the ``/blog`` URL and you should see something like this:
  182. .. figure:: ../_static/images/tutorial/tutorial_7.png
  183. :alt: Blog basics
  184. Titles should link to post pages, and a link back to the blog's
  185. homepage should appear in the footer of each post page.
  186. Parents and Children
  187. ~~~~~~~~~~~~~~~~~~~~
  188. Much of the work you'll be doing in Wagtail revolves around the concept of hierarchical
  189. "tree" structures consisting of nodes and leaves (see :doc:`../reference/pages/theory`).
  190. In this case, the ``BlogIndexPage`` is a "node" and individual ``BlogPage`` instances
  191. are the "leaves".
  192. Take another look at the guts of ``BlogIndexPage:``
  193. .. code-block:: html+django
  194. {% for post in page.get_children %}
  195. <h2>{{ post.title }}</h2>
  196. {{ post.specific.intro }}
  197. {{ post.specific.body|richtext }}
  198. {% endfor %}
  199. Every "page" in Wagtail can call out to its parent or children
  200. from its own position in the hierarchy. But why do we have to
  201. specify ``post.specific.title`` rather than ``post.title?``
  202. This has to do with the way we defined our model:
  203. ``class BlogPage(Page):``
  204. The ``get_children()`` method got us a list of ``Page`` base classes. When we want to reference
  205. properties of the instances that inherits from the base class, Wagtail provides the ``specific``
  206. method that retrieves the actual ``BlogPage`` record.
  207. To tighten up template code like this, we could use Django's ``with`` tag:
  208. .. code-block:: html+django
  209. {% for post in page.get_children %}
  210. {% with post=post.specific %}
  211. <h2>{{ post.title }}</h2>
  212. {{ post.intro }}
  213. {{ post.body|richtext }}
  214. {% endwith %}
  215. {% endfor %}
  216. Image support
  217. ~~~~~~~~~~~~~
  218. Wagtail provides support for images out of the box. To add them to
  219. your ``BlogPage`` model:
  220. .. code-block:: python
  221. from django.db import models
  222. from wagtail.wagtailcore.models import Page
  223. from wagtail.wagtailcore.fields import RichTextField
  224. from wagtail.wagtailadmin.edit_handlers import FieldPanel
  225. from wagtail.wagtailimages.edit_handlers import ImageChooserPanel
  226. from wagtail.wagtailsearch import index
  227. class BlogPage(Page):
  228. main_image = models.ForeignKey(
  229. 'wagtailimages.Image',
  230. null=True,
  231. blank=True,
  232. on_delete=models.SET_NULL,
  233. related_name='+'
  234. )
  235. date = models.DateField("Post date")
  236. intro = models.CharField(max_length=250)
  237. body = RichTextField(blank=True)
  238. search_fields = Page.search_fields + [
  239. index.SearchField('intro'),
  240. index.SearchField('body'),
  241. ]
  242. content_panels = Page.content_panels + [
  243. FieldPanel('date'),
  244. ImageChooserPanel('main_image'),
  245. FieldPanel('intro'),
  246. FieldPanel('body'),
  247. ]
  248. Run ``python manage.py makemigrations`` and ``python manage.py migrate``.
  249. Adjust your blog page template to include the image:
  250. .. code-block:: html+django
  251. {% extends "base.html" %}
  252. {% load wagtailcore_tags wagtailimages_tags %}
  253. {% block body_class %}template-blogpage{% endblock %}
  254. {% block content %}
  255. <h1>{{ page.title }}</h1>
  256. <p class="meta">{{ page.date }}</p>
  257. {% if page.main_image %}
  258. {% image page.main_image width-400 %}
  259. {% endif %}
  260. <div class="intro">{{ page.intro }}</div>
  261. {{ page.body|richtext }}
  262. {% endblock %}
  263. .. figure:: ../_static/images/tutorial/tutorial_6.png
  264. :alt: A blog post sample
  265. You can read more about using images in templates in the
  266. :doc:`docs <../topics/images>`.
  267. Tagging Posts
  268. ~~~~~~~~~~~~~
  269. Let's say we want to let editors "tag" their posts, so that readers can, e.g.,
  270. view all bicycle-related content together. For this, we'll need to invoke
  271. the tagging system bundled with Wagtail, attach it to the ``BlogPage``
  272. model and content panels, and render linked tags on the blog post template.
  273. Of course, we'll need a working tag-specific URL view as well.
  274. First, alter ``models.py`` once more:
  275. .. code-block:: python
  276. from django.db import models
  277. from modelcluster.tags import ClusterTaggableManager
  278. from modelcluster.fields import ParentalKey
  279. from taggit.models import TaggedItemBase
  280. from wagtail.wagtailcore.models import Page
  281. from wagtail.wagtailcore.fields import RichTextField
  282. from wagtail.wagtailadmin.edit_handlers import FieldPanel, MultiFieldPanel
  283. from wagtail.wagtailimages.edit_handlers import ImageChooserPanel
  284. from wagtail.wagtailsearch import index
  285. class BlogPageTag(TaggedItemBase):
  286. content_object = ParentalKey('BlogPage', related_name='tagged_items')
  287. class BlogPage(Page):
  288. main_image = models.ForeignKey(
  289. 'wagtailimages.Image',
  290. null=True,
  291. blank=True,
  292. on_delete=models.SET_NULL,
  293. related_name='+'
  294. )
  295. date = models.DateField("Post date")
  296. intro = models.CharField(max_length=250)
  297. body = RichTextField(blank=True)
  298. tags = ClusterTaggableManager(through=BlogPageTag, blank=True)
  299. search_fields = Page.search_fields + [
  300. index.SearchField('intro'),
  301. index.SearchField('body'),
  302. ]
  303. content_panels = Page.content_panels + [
  304. FieldPanel('date'),
  305. ImageChooserPanel('main_image'),
  306. FieldPanel('intro'),
  307. FieldPanel('body'),
  308. MultiFieldPanel([
  309. FieldPanel('tags'),
  310. ], heading="Tags"),
  311. ]
  312. class BlogIndexPage(Page):
  313. intro = RichTextField(blank=True)
  314. Note the new ``modelcluster`` and ``taggit`` imports, the addition of a new
  315. ``BlogPageTag`` model, the addition of a ``tags`` field on ``BlogPage``,
  316. and the use of ``MultiFieldPanel`` in ``content_panels`` to let users
  317. select tags.
  318. Edit one of your ``BlogPage`` instances, and you should now be able to tag posts:
  319. .. figure:: ../_static/images/tutorial/tutorial_8.png
  320. :alt: Tagging a post
  321. To render tags on a ``BlogPage``, add this to ``blog_page.html:``
  322. .. code-block:: html+django
  323. {% if page.specific.tags.all.count %}
  324. <div class="tags">
  325. <h3>Tags</h3>
  326. {% for tag in page.specific.tags.all %}
  327. <a href="{% slugurl 'tags' %}?tag={{ tag }}"><button type="button">{{ tag }}</button></a>
  328. {% endfor %}
  329. </div>
  330. {% endif %}
  331. Visiting a blog post with tags should now show a set of linked
  332. buttons at the bottom - one for each tag. However, clicking a button
  333. will get you a 404, since we haven't yet defined a "tags" view, which
  334. is going to require a little extra magic. Add to ``models.py:``
  335. .. code-block:: python
  336. class BlogTagIndexPage(Page):
  337. def get_context(self, request):
  338. # Filter by tag
  339. tag = request.GET.get('tag')
  340. blogpages = BlogPage.objects.filter().filter(tags__name=tag)
  341. # Update template context
  342. context = super(BlogTagIndexPage, self).get_context(request)
  343. context['blogpages'] = blogpages
  344. return context
  345. Note that this Page-based model defines no fields of its own.
  346. Even without fields, subclassing ``Page`` makes it a part of the
  347. Wagtail ecosystem, so that you can give it a title and URL in the
  348. admin, and so that you can manipulate its contents by returning
  349. a queryset from its ``get_context()`` method.
  350. Migrate this in, then create a new ``BlogTagIndexPage`` in the admin.
  351. You'll probably want to create the new page/view under Homepage,
  352. parallel to your Blog index. Give it the slug "tags" on the Promote tab.
  353. Access ``/tags`` and Django will tell you what you probably already knew:
  354. you need to create a template ``blog/blog_tag_index_page.html:``
  355. .. code-block:: html+django
  356. {% extends "base.html" %}
  357. {% load wagtailcore_tags %}
  358. {% block content %}
  359. {% if request.GET.tag|length %}
  360. <h4>Showing pages tagged "{{ request.GET.tag|safe }}"</h4>
  361. {% endif %}
  362. {% for blogpage in blogpages %}
  363. <p>
  364. <strong><a href="{% pageurl blogpage %}">{{ blogpage.title }}</a></strong><br />
  365. <small>Revised: {{ blogpage.latest_revision_created_at }}</small><br />
  366. {% if blogpage.author %}
  367. <p>By {{ blogpage.author.profile }}</p>
  368. {% endif %}
  369. </p>
  370. {% empty %}
  371. No pages found with that tag.
  372. {% endfor %}
  373. {% endblock %}
  374. Unlike in the previous example, we're linking to pages here with the builtin ``pageurl``
  375. tag rather than ``slugurl``. The difference is that ``pageurl`` takes a Page object
  376. as an argument. Use whichever one best suits your purpose.
  377. We're also calling the built-in ``latest_revision_created_at`` field on the ``Page``
  378. model - handy to know this is always available.
  379. We haven't yet added an "author" field to our ``BlogPage`` model, nor do we have
  380. a Profile model for authors - we'll leave those as an exercise for the reader.
  381. Clicking the tag button at the bottom of a BlogPost should now render a page
  382. something like this:
  383. .. figure:: ../_static/images/tutorial/tutorial_9.png
  384. :alt: A simple tag view
  385. Where next
  386. ----------
  387. - Read the Wagtail :doc:`topics <../topics/index>` and :doc:`reference <../reference/index>` documentation
  388. - Learn how to implement :doc:`StreamField <../topics/streamfield>` for freeform page content
  389. - Browse through the :doc:`advanced topics <../advanced_topics/index>` section and read :doc:`third-party tutorials <../advanced_topics/third_party_tutorials>`