Customization
=============
Newsletter templates
--------------------
Wagtail-newsletter will look for an attribute named ``newsletter_template`` (or
a method named ``get_newsletter_template``) to get the name of the page
template to use for newsletters. Typically this will be a dedicated template
that produces email-compatible HTML.
The ``{% mrml %}`` template tag
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To help generate email-compatible HTML, wagtail-newsletter provides a template
tag that expects MJML_ markup, and runs mrml_ behind the scenes, to transform
it into HTML.
.. code-block:: htmldjango
{% load wagtail_newsletter %}
{% mrml %}
{{ page.title }}
{% endmrml %}
.. _MJML: https://mjml.io
.. _mrml: https://github.com/jdrouet/mrml
The ``{% newsletter_static %}`` template tag
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The ``{% newsletter_static %}`` template tag is used to embed static files in
newsletters. It works like Django's ``{% static %}`` template tag, but it
generates a fully-qualified URL for the static file, by prepending Wagtail's
``WAGTAILADMIN_BASE_URL`` setting to the path.
.. code-block:: htmldjango
{% load wagtail_newsletter %}
...
{% newsletter_static "images/logo.png" %}
The ``newsletter_richtext`` filter
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Wagtail provides a `richtext template filter`_ that expands references to
embedded images and links. It generates path-only URLs, like
``/media/images/bird.jpg`` and ``/about/contact``. In the context of a web
page, they work, because the browser knows to use the domain of the current
page. In the context of emails, the domain is unknown, so those links will
break.
To address this issue, wagtail-newsletter provides a ``newsletter_richtext``
filter, that does the same thing as ``richtext``, but generates full URLs like
``https://example.com/media/images/bird.jpg`` and
``https://example.com/about/contact``.
.. _richtext template filter: https://docs.wagtail.org/en/stable/topics/writing_templates.html#rich-text-filter
.. code-block:: htmldjango
{% load wagtail_newsletter %}
...
{{ page.body|newsletter_richtext }}
Embedding images as links
~~~~~~~~~~~~~~~~~~~~~~~~~
When embedding images, be sure to use their ``full_url`` link:
.. code-block:: htmldjango
{% load wagtailimages_tags %}
...
{% image block.value.image width-800 as image %}
.. warning::
If you include images in your newsletter, the emails will contain links to
image renditions served by Wagtail. Be careful with removing old renditions
as they might break emails that have been sent.
Web-only and email-only content
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You might want to have content that is only included in the web or email
version of a page. This can be accomplished by creating conditional
``StreamField`` blocks that only render their content in the right context.
First let's set a flag in the rendering context so we can use it later to
distinguish between newsletter and web rendering.
.. code-block:: python
from wagtail.fields import StreamField
from wagtail.models import Page
from wagtail_newsletter.models import NewsletterPageMixin
class ArticlePage(NewsletterPageMixin, Page):
body = StreamField(StoryBlock(), blank=True, use_json_field=True)
def get_newsletter_context(self):
context = super().get_newsletter_context()
context["rendering_newsletter"] = True
return context
Then we can define a ``StreamField`` block that only renders its content if the
flag is set (or, for web-only content, if the flag is missing):
.. code-block:: python
from wagtail import blocks
def is_rendering_newsletter(context):
return bool((context or {}).get("rendering_newsletter"))
class EmailOnlyBlock(blocks.RichTextBlock):
def render(self, value, context=None):
if not is_rendering_newsletter(context):
return ""
return super().render(value, context)
class StoryBlock(blocks.StreamBlock):
rich_text = blocks.RichTextBlock()
email_only = EmailOnlyBlock(group="Channel")
Recipients model
----------------
The default recipients model in wagtail-newsletter is intentionally simple: it
has a name and a reference to an audience (and, optionally, a segment) in the
campaign provider's system. If you want to associate more information with a
recipients record, like a custom greeting or footer text, you can define a
custom model:
.. code-block:: python
from wagtail.fields import RichTextField
from wagtail_newsletter.models import NewsletterRecipientsBase
class CustomRecipients(NewsletterRecipientsBase):
greeting = RichTextField(blank=True)
class Meta:
verbose_name_plural = "Custom recipients"
Configure wagtail-newsletter to use the custom model by adding a setting to
Django settings:
.. code-block:: python
WAGTAIL_NEWSLETTER_RECIPIENTS_MODEL = "myapp.CustomRecipients"
Register a viewset, and permissions, for the custom recipients model:
.. code-block:: python
from django.contrib.auth.models import Permission
from wagtail import hooks
from wagtail.admin.panels import FieldPanel
from wagtail_newsletter.viewsets import NewsletterRecipientsViewSet
from .models import CustomRecipients
class CustomRecipientsViewSet(NewsletterRecipientsViewSet):
model = CustomRecipients
panels = NewsletterRecipientsViewSet.panels + [
FieldPanel("greeting"),
]
@hooks.register("register_admin_viewset")
def register_admin_viewset():
return CustomRecipientsViewSet("custom_recipients")
@hooks.register("register_permissions")
def register_permissions():
return Permission.objects.filter(
content_type__app_label="myapp",
codename__in=[
"add_customrecipients",
"change_customrecipients",
"delete_customrecipients",
],
)
Finally, use content from the custom recipients model in the newsletter template:
.. code-block:: htmldjango
{% load wagtail_newsletter %}
{% mrml %}
{{ page.title }}
{% if page.newsletter_recipients.greeting %}
{{ page.newsletter_recipients.greeting|newsletter_richtext }}
{% endif %}
{% endmrml %}
Campaign backends
-----------------
Wagtail-newsletter is designed to work with multiple email campaign providers,
though currently it only supports Mailchimp out of the box. Should you want to
target another provider, or change the behaviour of an existing backend (e.g.
to tweak the configuration of a campaign before it's sent to the API), you can
define your own backend class.
Backends should subclass the
``wagtail_newsletter.campaign_backends.CampaignBackend`` abstract class and
implement its methods.
For example, here is how you would implement a custom backend that disables
tracking for a Mailchimp campaign:
.. code-block:: python
# myapp/campaign_backend.py
from wagtail_newsletter.campaign_backends.mailchimp import MailchimpCampaignBackend
class CustomBackend(MailchimpCampaignBackend):
def get_campaign_request_body(self, **kwargs):
body = super().get_campaign_request_body(**kwargs)
# Add tracking settings to disable all tracking
body["tracking"] = {
"opens": False,
"html_clicks": False,
}
return body
To enable the backend, configure the ``WAGTAIL_NEWSLETTER_CAMPAIGN_BACKEND`` Django setting:
.. code-block:: python
WAGTAIL_NEWSLETTER_CAMPAIGN_BACKEND = "myapp.campaign_backend.CustomBackend"
Permissions
-----------
Out of the box, wagtail-newsletter checks the *Publish* permission to see
whether a user is allowed to perform newsletter actions. If the user doesn't
have the permission, they will not see the corresponding panels in the editor.
Permissions can be customized by implementing the
``has_newsletter_permission(user, action)`` method on the page model. It's
possible to selectively grant permissions to certain actions (a user might be
able to send themselves a test email but not send the campaign). Have a look at
`demo/models.py`_ for an example.
.. _demo/models.py: https://github.com/wagtail/wagtail-newsletter/blob/main/demo/models.py