2
0

page_editing_interface.rst 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. Customising the editing interface
  2. =================================
  3. .. _customising_the_tabbed_interface:
  4. Customising the tabbed interface
  5. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  6. As standard, Wagtail organises panels for pages into three tabs: 'Content', 'Promote' and 'Settings'. For snippets Wagtail puts all panels into one page. Depending on the requirements of your site, you may wish to customise this for specific page types or snippets - for example, adding an additional tab for sidebar content. This can be done by specifying an ``edit_handler`` attribute on the page or snippet model. For example:
  7. .. code-block:: python
  8. from wagtail.admin.edit_handlers import TabbedInterface, ObjectList
  9. class BlogPage(Page):
  10. # field definitions omitted
  11. content_panels = [
  12. FieldPanel('title', classname="full title"),
  13. FieldPanel('date'),
  14. FieldPanel('body', classname="full"),
  15. ]
  16. sidebar_content_panels = [
  17. SnippetChooserPanel('advert'),
  18. InlinePanel('related_links', label="Related links"),
  19. ]
  20. edit_handler = TabbedInterface([
  21. ObjectList(content_panels, heading='Content'),
  22. ObjectList(sidebar_content_panels, heading='Sidebar content'),
  23. ObjectList(Page.promote_panels, heading='Promote'),
  24. ObjectList(Page.settings_panels, heading='Settings', classname="settings"),
  25. ])
  26. .. _rich-text:
  27. Rich Text (HTML)
  28. ~~~~~~~~~~~~~~~~
  29. Wagtail provides a general-purpose WYSIWYG editor for creating rich text content (HTML) and embedding media such as images, video, and documents. To include this in your models, use the :class:`~wagtail.core.fields.RichTextField` function when defining a model field:
  30. .. code-block:: python
  31. from wagtail.core.fields import RichTextField
  32. from wagtail.admin.edit_handlers import FieldPanel
  33. class BookPage(Page):
  34. body = RichTextField()
  35. content_panels = Page.content_panels + [
  36. FieldPanel('body', classname="full"),
  37. ]
  38. :class:`~wagtail.core.fields.RichTextField` inherits from Django's basic ``TextField`` field, so you can pass any field parameters into :class:`~wagtail.core.fields.RichTextField` as if using a normal Django field. This field does not need a special panel and can be defined with ``FieldPanel``.
  39. However, template output from :class:`~wagtail.core.fields.RichTextField` is special and needs to be filtered in order to preserve embedded content. See :ref:`rich-text-filter`.
  40. .. _rich_text_features:
  41. Limiting features in a rich text field
  42. --------------------------------------
  43. By default, the rich text editor provides users with a wide variety of options for text formatting and inserting embedded content such as images. However, we may wish to restrict a rich text field to a more limited set of features - for example:
  44. * The field might be intended for a short text snippet, such as a summary to be pulled out on index pages, where embedded images or videos would be inappropriate;
  45. * When page content is defined using :ref:`StreamField <streamfield>`, elements such as headings, images and videos are usually given their own block types, alongside a rich text block type used for ordinary paragraph text; in this case, allowing headings and images to also exist within the rich text content is redundant (and liable to result in inconsistent designs).
  46. This can be achieved by passing a ``features`` keyword argument to ``RichTextField``, with a list of identifiers for the features you wish to allow:
  47. .. code-block:: python
  48. body = RichTextField(features=['h2', 'h3', 'bold', 'italic', 'link'])
  49. The feature identifiers provided on a default Wagtail installation are as follows:
  50. * ``h1``, ``h2``, ``h3``, ``h4``, ``h5``, ``h6`` - heading elements
  51. * ``bold``, ``italic`` - bold / italic text
  52. * ``ol``, ``ul`` - ordered / unordered lists
  53. * ``hr`` - horizontal rules
  54. * ``link`` - page, external and email links
  55. * ``document-link`` - links to documents
  56. * ``image`` - embedded images
  57. * ``embed`` - embedded media (see :ref:`embedded_content`)
  58. We have few additional feature identifiers as well. They are not enabled by default, but you can use them in your list of identifiers. These are as follows:
  59. * ``code`` - inline code
  60. * ``superscript``, ``subscript``, ``strikethrough`` - text formatting
  61. * ``blockquote`` - blockquote
  62. The process for creating new features is described in the following pages:
  63. * :doc:`../../extending/rich_text_internals`
  64. * :doc:`../../extending/extending_draftail`
  65. * :doc:`../../extending/extending_hallo`
  66. .. _rich_text_image_formats:
  67. Image Formats in the Rich Text Editor
  68. -------------------------------------
  69. On loading, Wagtail will search for any app with the file ``image_formats.py`` and execute the contents. This provides a way to customise the formatting options shown to the editor when inserting images in the :class:`~wagtail.core.fields.RichTextField` editor.
  70. As an example, add a "thumbnail" format:
  71. .. code-block:: python
  72. # image_formats.py
  73. from wagtail.images.formats import Format, register_image_format
  74. register_image_format(Format('thumbnail', 'Thumbnail', 'richtext-image thumbnail', 'max-120x120'))
  75. To begin, import the ``Format`` class, ``register_image_format`` function, and optionally ``unregister_image_format`` function. To register a new ``Format``, call the ``register_image_format`` with the ``Format`` object as the argument. The ``Format`` class takes the following constructor arguments:
  76. ``name``
  77. The unique key used to identify the format. To unregister this format, call ``unregister_image_format`` with this string as the only argument.
  78. ``label``
  79. The label used in the chooser form when inserting the image into the :class:`~wagtail.core.fields.RichTextField`.
  80. ``classnames``
  81. The string to assign to the ``class`` attribute of the generated ``<img>`` tag.
  82. .. note::
  83. Any class names you provide must have CSS rules matching them written separately, as part of the front end CSS code. Specifying a ``classnames`` value of ``left`` will only ensure that class is output in the generated markup, it won't cause the image to align itself left.
  84. ``filter_spec``
  85. The string specification to create the image rendition. For more, see the :ref:`image_tag`.
  86. To unregister, call ``unregister_image_format`` with the string of the ``name`` of the ``Format`` as the only argument.
  87. .. warning::
  88. Unregistering ``Format`` objects will cause errors viewing or editing pages that reference them.
  89. .. _custom_edit_handler_forms:
  90. Customising generated forms
  91. ~~~~~~~~~~~~~~~~~~~~~~~~~~~
  92. .. class:: wagtail.admin.forms.WagtailAdminModelForm
  93. .. class:: wagtail.admin.forms.WagtailAdminPageForm
  94. Wagtail automatically generates forms using the panels configured on the model.
  95. By default, this form subclasses :class:`~wagtail.admin.forms.WagtailAdminModelForm`,
  96. or :class:`~wagtail.admin.forms.WagtailAdminPageForm` for pages.
  97. A custom base form class can be configured by setting the :attr:`base_form_class` attribute on any model.
  98. Custom forms for snippets must subclass :class:`~wagtail.admin.forms.WagtailAdminModelForm`,
  99. and custom forms for pages must subclass :class:`~wagtail.admin.forms.WagtailAdminPageForm`.
  100. This can be used to add non-model fields to the form, to automatically generate field content,
  101. or to add custom validation logic for your models:
  102. .. code-block:: python
  103. from django import forms
  104. from django.db import models
  105. import geocoder # not in Wagtail, for example only - https://geocoder.readthedocs.io/
  106. from wagtail.admin.edit_handlers import FieldPanel
  107. from wagtail.admin.forms import WagtailAdminPageForm
  108. from wagtail.core.models import Page
  109. class EventPageForm(WagtailAdminPageForm):
  110. address = forms.CharField()
  111. def clean(self):
  112. cleaned_data = super().clean()
  113. # Make sure that the event starts before it ends
  114. start_date = cleaned_data['start_date']
  115. end_date = cleaned_data['end_date']
  116. if start_date and end_date and start_date > end_date:
  117. self.add_error('end_date', 'The end date must be after the start date')
  118. return cleaned_data
  119. def save(self, commit=True):
  120. page = super().save(commit=False)
  121. # Update the duration field from the submitted dates
  122. page.duration = (page.end_date - page.start_date).days
  123. # Fetch the location by geocoding the address
  124. page.location = geocoder.arcgis(self.cleaned_data['address'])
  125. if commit:
  126. page.save()
  127. return page
  128. class EventPage(Page):
  129. start_date = models.DateField()
  130. end_date = models.DateField()
  131. duration = models.IntegerField()
  132. location = models.CharField(max_length=255)
  133. content_panels = [
  134. FieldPanel('title'),
  135. FieldPanel('start_date'),
  136. FieldPanel('end_date'),
  137. FieldPanel('address'),
  138. ]
  139. base_form_class = EventPageForm
  140. Wagtail will generate a new subclass of this form for the model,
  141. adding any fields defined in ``panels`` or ``content_panels``.
  142. Any fields already defined on the model will not be overridden by these automatically added fields,
  143. so the form field for a model field can be overridden by adding it to the custom form.