From bfa420de663f897246acca4f05ba07f59381843d Mon Sep 17 00:00:00 2001 From: Alex Morega Date: Mon, 24 Jun 2024 17:19:53 +0300 Subject: [PATCH 01/12] Docs: installation and usage --- README.md | 2 +- docs/conf.py | 1 + docs/index.rst | 3 ++ docs/installation.rst | 105 ++++++++++++++++++++++++++++++++++++++++++ docs/usage.rst | 49 ++++++++++++++++++++ 5 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 docs/installation.rst create mode 100644 docs/usage.rst diff --git a/README.md b/README.md index 0462a95..7216361 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Turn Wagtail pages into newsletters. ## Links -- [Documentation](https://github.com/wagtail/wagtail-newsletter/blob/main/README.md) +- [Documentation](https://wagtail-newsletter.readthedocs.io) - [Changelog](https://github.com/wagtail/wagtail-newsletter/blob/main/CHANGELOG.md) - [Contributing](https://github.com/wagtail/wagtail-newsletter/blob/main/CONTRIBUTING.md) - [Discussions](https://github.com/wagtail/wagtail-newsletter/discussions) diff --git a/docs/conf.py b/docs/conf.py index 87caeba..ceb470b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -7,6 +7,7 @@ release = "0.1.0" extensions = [ + "sphinx.ext.autosectionlabel", "sphinx_wagtail_theme", ] diff --git a/docs/index.rst b/docs/index.rst index c0fe5b9..b036b85 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -5,6 +5,9 @@ Welcome to Wagtail Newsletter's documentation! :maxdepth: 2 :caption: Contents: + installation + usage + This is a work in progress. Indices and tables diff --git a/docs/installation.rst b/docs/installation.rst new file mode 100644 index 0000000..1097a4d --- /dev/null +++ b/docs/installation.rst @@ -0,0 +1,105 @@ +Installation +============ + +Here are the steps to install and configure ``wagtail-newsletter`` for your +site. They assume that you'll be sending campaigns through Mailchimp_ using the +default campaign backend. + +.. _Mailchimp: https://mailchimp.com + +Install the package from PyPI +----------------------------- + +.. code-block:: shell + + pip install wagtail-newsletter + +Configure Django settings +------------------------- + +Add the app to ``INSTALLED_APPS``: + +.. code-block:: python + + INSTALLED_APPS = [ + # ... + "wagtail_newsletter", + ] + +For ``WAGTAIL_NEWSLETTER_MAILCHIMP_API_KEY``, `get an API key`_ from Mailchimp. + +The ``WAGTAIL_NEWSLETTER_FROM_NAME`` and ``WAGTAIL_NEWSLETTER_REPLY_TO`` +settings are used to build the `From:` field in the outgoing emails: + +.. _get an API key: https://us1.admin.mailchimp.com/account/api/ + +.. code-block:: python + + WAGTAIL_NEWSLETTER_MAILCHIMP_API_KEY = "the-mailchimp-api-key" + WAGTAIL_NEWSLETTER_FROM_NAME = "Example Newsletter" + WAGTAIL_NEWSLETTER_REPLY_TO = "newsletter@example.com" + +Set up a page model for newsletters +----------------------------------- + +Add ``NewsletterPageMixin`` to a page model: + +.. code-block:: python + + from wagtail.admin.panels import FieldPanel + from wagtail.fields import RichTextField + from wagtail.models import Page + from wagtail_newsletter.models import NewsletterPageMixin + + class ArticlePage(NewsletterPageMixin, Page): + body = RichTextField() + + content_panels = Page.content_panels + [ + FieldPanel("body"), + ] + + newsletter_template = "demo/article_page_newsletter.html" + +Create a page template for the email body +(``templates/demo/article_page_newsletter.html``). The content of the template +is completely up to you, but writing email-compatible HTML is notoriously +difficult, so *wagtail-newsletter* includes a template tag ``{% mrml %}``, that +transforms its contents from MJML_ into email-compatible HTML. Behind the +scenes it uses the mrml_ library. + +.. _MJML: https://mjml.io +.. _mrml: https://github.com/jdrouet/mrml + +.. code-block:: htmldjango + + {% load wagtail_newsletter %} + + {% mrml %} + + +

{{ page.title }}

+ {{ page.body|newsletter_richtext }} +
+
+ {% endmrml %} + +Configure recipients +-------------------- + +Finally, we need to configure recipients for our newsletteres. Go to Wagtail +admin and click on *Settings*, then *Newsletter Recipients*, and create a new +Recipients record. Give it a descriptive name, and select an *audience* from +Mailchimp, and optionally an *audience segment*, and click *Create*. When +preparing a newsletter page, you can select this *Recipients* record to use as +recipients for the campaign. + +Next steps +---------- + +That's it! Now, when editing an ``ArticlePage`` in Wagtail Admin, you can `flip +the preview mode`_ to *Newsletter*, and see the article in email format. When +you're happy with the results, go to the *Newsletter* tab in the editor to +:ref:`send a test email ` and then :ref:`send your campaign +`. + +.. _flip the preview mode: https://docs.wagtail.org/en/stable/reference/pages/model_reference.html#wagtail.models.Page.preview_modes diff --git a/docs/usage.rst b/docs/usage.rst new file mode 100644 index 0000000..66752e6 --- /dev/null +++ b/docs/usage.rst @@ -0,0 +1,49 @@ +Usage +===== + +Edit and preview content +------------------------ + +Campaigns are sent from regular Wagtail pages that include the +``NewsletterPageMixin`` mixin. The content that you create for the Web is the +same content that will be sent in newsletter emails, albeit with different +formatting. + +To help you make sure the content is right for emails, use Wagtail's editor +preview, and change *Preview mode* to *Newsletter*. It will show a preview of +what the email will look like in recipients' inboxes. + +Send test email +--------------- + +Once you are happy with the content, it's a good idea to send yourself a test +email. Go over to the *Newsletter* tab in the editor and click *Send test +email*. This will save a page revision, upload the content to the campaign +provider (and create a new campaign if it hasn't been created already), then +trigger a test email from the campaign provider's system. + +Send campaign +------------- + +When you're ready to unleash the campaign upon your audience, click *Send +campaign* in the *Newsletter* editor tab. This will save a page revision, +upload the content to the campaign provider, and trigger campaign sending. + +Save campaign withouth sending +------------------------------ + +If you want to simply upload the content to the campaign provider (without +sending a test email or triggering the campaign sending), maybe to make tweaks +to the campaign in the provider's app, or use advanced features like scheduled +sending, then click on *Save campaign to {provider}*. This will save a page +revision, and upload the campaign, but take no further action. + +Keep in mind that any changes you make to the campaign will be overridden if +you send a test email, or trigger campaign sending, from Wagtail. + +View report +----------- + +Once a campaign is sent, you can track its performance in the *Newsletter* tab +of the Wagtail admin page editor. It shows sending status, send time, how many +emails were sent, and how many people opened the message and clicked on a link. From eb2ac51cb11c4721d611cb6384add5d4a90a695e Mon Sep 17 00:00:00 2001 From: Alex Morega Date: Mon, 24 Jun 2024 18:54:38 +0300 Subject: [PATCH 02/12] Docs for customization --- docs/customization.rst | 166 +++++++++++++++++++++++++++++++++++++++++ docs/index.rst | 1 + 2 files changed, 167 insertions(+) create mode 100644 docs/customization.rst diff --git a/docs/customization.rst b/docs/customization.rst new file mode 100644 index 0000000..e37680b --- /dev/null +++ b/docs/customization.rst @@ -0,0 +1,166 @@ +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_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 }} + +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. + +To use a different backend, configure the ``WAGTAIL_NEWSLETTER_CAMPAIGN_BACKEND`` Django setting: + +.. code-block:: python + + WAGTAIL_NEWSLETTER_CAMPAIGN_BACKEND = "myapp.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 diff --git a/docs/index.rst b/docs/index.rst index b036b85..cc218ec 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -7,6 +7,7 @@ Welcome to Wagtail Newsletter's documentation! installation usage + customization This is a work in progress. From 48ab21b6ae69602bd85a3fc6064d849b4b7ae318 Mon Sep 17 00:00:00 2001 From: Alex Morega Date: Mon, 24 Jun 2024 18:54:57 +0300 Subject: [PATCH 03/12] Clean up header and homepage --- docs/conf.py | 5 +++++ docs/index.rst | 4 ---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index ceb470b..44b7e90 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -19,4 +19,9 @@ ] html_theme = "sphinx_wagtail_theme" +html_theme_options = { + "project_name": "Wagtail Newsletter documentation", + "github_url": "https://github.com/wagtail/wagtail-newsletter/blob/main/docs/", +} + html_static_path = ["_static"] diff --git a/docs/index.rst b/docs/index.rst index cc218ec..5d7290d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -9,11 +9,7 @@ Welcome to Wagtail Newsletter's documentation! usage customization -This is a work in progress. - Indices and tables ================== -* :ref:`genindex` -* :ref:`modindex` * :ref:`search` From cb2ec3a08fede79370bbab0ee9d2a8490797aa40 Mon Sep 17 00:00:00 2001 From: Alex Morega Date: Mon, 24 Jun 2024 19:00:52 +0300 Subject: [PATCH 04/12] Home page text --- docs/index.rst | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index 5d7290d..35ab9c2 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,9 +1,22 @@ Welcome to Wagtail Newsletter's documentation! ============================================== +*Wagtail Newsletter* is a Wagtail extension that helps you create and send +newsletter campaigns for individual Wagtail content pages. It comes with a +Mailchimp_ backend out of the box, and includes support for MJML_ to render +email-compatible HTML. + +.. image:: https://github.com/wagtail/wagtail-newsletter/assets/27617/35fd29fc-730d-4e69-a886-d0d8fe4ac182 + :width: 600px + +.. _Mailchimp: https://mailchimp.com +.. _MJML: https://mjml.io + +Contents +======== + .. toctree:: :maxdepth: 2 - :caption: Contents: installation usage From 03532870b301b5a4c8582d5accb473020acc6a0d Mon Sep 17 00:00:00 2001 From: Alex Morega Date: Mon, 24 Jun 2024 19:55:06 +0300 Subject: [PATCH 05/12] How to embed images --- docs/customization.rst | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/customization.rst b/docs/customization.rst index e37680b..1266992 100644 --- a/docs/customization.rst +++ b/docs/customization.rst @@ -51,9 +51,29 @@ filter, that does the same thing as ``richtext``, but generates full URLs like .. 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. + Recipients model ---------------- From fe9484a3419674f3e97a7152b6790a7cd918415d Mon Sep 17 00:00:00 2001 From: Alex Morega Date: Mon, 8 Jul 2024 18:49:39 +0300 Subject: [PATCH 06/12] Configure a cache --- docs/installation.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/installation.rst b/docs/installation.rst index 1097a4d..9480c5f 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -93,6 +93,18 @@ Mailchimp, and optionally an *audience segment*, and click *Create*. When preparing a newsletter page, you can select this *Recipients* record to use as recipients for the campaign. +Configure a Django cache +------------------------ + +In Wagtail Admin, when displaying recipients, an audience, or an audience +segment, wagtail-newsletter must perform API calls to retrieve information like +the audience name and number of subscribers. To avoid repeated API calls it's +useful to set up Django's cache_ mechanism. Wagtail-newsletter will use the +`default` cache, and store information for 5 minutes (configurable via the +``WAGTAIL_NEWSLETTER_CACHE_TIMEOUT`` setting). + +.. _cache: https://docs.djangoproject.com/en/stable/topics/cache/#setting-up-the-cache + Next steps ---------- From b7e8ade5c582c6edf9f88f20ca9cb6c2007cbdc6 Mon Sep 17 00:00:00 2001 From: Alex Morega Date: Mon, 8 Jul 2024 19:00:11 +0300 Subject: [PATCH 07/12] Audit log --- docs/usage.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/usage.rst b/docs/usage.rst index 66752e6..a5015ba 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -47,3 +47,16 @@ View report Once a campaign is sent, you can track its performance in the *Newsletter* tab of the Wagtail admin page editor. It shows sending status, send time, how many emails were sent, and how many people opened the message and clicked on a link. + +Audit log +--------- + +Newsletter-related actions performed by users on a newsletter page are `logged +to the page history`_, just like regular save and publish actions. The following +actions are logged: + +- :ref:`Save campaign ` +- :ref:`Send test email` +- :ref:`Send campaign` + +.. _logged to the page history: https://docs.wagtail.org/en/stable/extending/audit_log.html From 94222d4b45aeeef0d8d259b3ca7e53b785f0689f Mon Sep 17 00:00:00 2001 From: Alex Morega Date: Mon, 8 Jul 2024 19:28:15 +0300 Subject: [PATCH 08/12] List of settings --- docs/index.rst | 1 + docs/settings.rst | 77 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 docs/settings.rst diff --git a/docs/index.rst b/docs/index.rst index 35ab9c2..d881983 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -20,6 +20,7 @@ Contents installation usage + settings customization Indices and tables diff --git a/docs/settings.rst b/docs/settings.rst new file mode 100644 index 0000000..799a4fe --- /dev/null +++ b/docs/settings.rst @@ -0,0 +1,77 @@ +Settings +======== + +Wagtail-newsletter can be configured with the following `Django settings`_. + +.. _Django settings: https://docs.djangoproject.com/en/stable/ref/settings/ + +Campaigns +--------- + +``WAGTAIL_NEWSLETTER_FROM_NAME`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + WAGTAIL_NEWSLETTER_FROM_NAME = "Bakery Tips" + +When sending a newsletter, this value will appear as the email sender's name. + +``WAGTAIL_NEWSLETTER_REPLY_TO`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + WAGTAIL_NEWSLETTER_FROM_NAME = "bakerytips@example.com" + +When sending a newsletter, this value will appear as the email sender's +address. Be sure to configure your email domain to allow the newsletter service +to send emails on behalf of this address, otherwise they will likely be marked +as spam. + +Backends +-------- + +``WAGTAIL_NEWSLETTER_CAMPAIGN_BACKEND`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + WAGTAIL_NEWSLETTER_CAMPAIGN_BACKEND = "wagtail_newsletter.campaign_backends.mailchimp.MailchimpCampaignBackend" + +Specifies which :ref:`campaign backend ` to use. + + +``WAGTAIL_NEWSLETTER_MAILCHIMP_API_KEY`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + WAGTAIL_NEWSLETTER_MAILCHIMP_API_KEY = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-us13" + +When using the default Mailchimp backend, this setting specifies the API key. +An API key can be obtained `from the Mailchimp website`_. + +.. _from the Mailchimp website: https://us1.admin.mailchimp.com/account/api/ + +Recipients +---------- + +``WAGTAIL_NEWSLETTER_RECIPIENTS_MODEL`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + WAGTAIL_NEWSLETTER_RECIPIENTS_MODEL = "wagtail_newsletter.NewsletterRecipients" + +Specifies which :ref:`recipients model ` to use. + +``WAGTAIL_NEWSLETTER_CACHE_TIMEOUT`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + WAGTAIL_NEWSLETTER_CACHE_TIMEOUT = 300 # 5 minutes + +Specifies how long, in seconds, to cache information about recipients +(audiences, segments, and subscriber counts). From 0b6b8032ccd40a47bfd3a140f5c3410394d21f09 Mon Sep 17 00:00:00 2001 From: Alex Morega Date: Mon, 8 Jul 2024 19:47:41 +0300 Subject: [PATCH 09/12] Update the README files --- README.md | 5 ++--- demo/README.md | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 7216361..a8b40aa 100644 --- a/README.md +++ b/README.md @@ -18,12 +18,11 @@ Turn Wagtail pages into newsletters. - Python (3.8, 3.9, 3.10, 3.11, 3.12) - Django (4.2, 5.0) -- Wagtail (5.2, 6.0) +- Wagtail (5.2, 6.0, 6.1) ## Installation -- `python -m pip install wagtail-newsletter` -- ... +See https://wagtail-newsletter.readthedocs.io/en/stable/installation.html. ## Sample project diff --git a/demo/README.md b/demo/README.md index b480f0b..5d14a46 100644 --- a/demo/README.md +++ b/demo/README.md @@ -1,12 +1,22 @@ # Wagtail Newsletter demo project -This is a minimal Wagtail project that exemplifies how to integrate `wagtail-newsletter` into a project. +This is a minimal Wagtail project that exemplifies how to integrate `wagtail-newsletter` into a project. It includes: +- A page model, `demo.ArticlePage`, that is configured as a newsletter. +- A custom permission to restrict newsletter actions, `demo.sendnewsletter_articlepage`. +- A moderately complex newsletter template, `templates/demo/article_page_newsletter.html`. +- A custom recipients model, `demo.CustomRecipients`. ## Running the demo project +Get an [API key from Mailchimp](https://us1.admin.mailchimp.com/account/api/). + From the top-level repository directory, run: -``` +```bash +export WAGTAIL_NEWSLETTER_MAILCHIMP_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-us13 +export WAGTAIL_NEWSLETTER_FROM_NAME="My Newsletter" +export WAGTAIL_NEWSLETTER_REPLY_TO=sender@example.com + ./demo/manage.py migrate ./demo/manage.py createcachetable ./demo/manage.py createsuperuser From d79630310a7f90e79645f329b7f01229f57e266e Mon Sep 17 00:00:00 2001 From: Alex Morega Date: Tue, 9 Jul 2024 16:53:07 +0300 Subject: [PATCH 10/12] How to set up web-only and email-only content --- demo/blocks.py | 22 +++++++++++++ demo/models.py | 5 +++ demo/templates/demo/article_page.html | 2 +- docs/customization.rst | 46 +++++++++++++++++++++++++++ 4 files changed, 74 insertions(+), 1 deletion(-) diff --git a/demo/blocks.py b/demo/blocks.py index f0266c1..3aea483 100644 --- a/demo/blocks.py +++ b/demo/blocks.py @@ -10,7 +10,29 @@ class Meta: # type: ignore template = "blocks/image_block.html" +def is_rendering_newsletter(context): + return bool((context or {}).get("rendering_newsletter")) + + +class WebOnlyBlock(blocks.RichTextBlock): + def render(self, value, context=None): + if is_rendering_newsletter(context): + return "" + + return super().render(value, context) + + +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() image = ImageBlock() raw_html = blocks.RawHTMLBlock() + email_only = EmailOnlyBlock(group="Channel") + web_only = WebOnlyBlock(group="Channel") diff --git a/demo/models.py b/demo/models.py index 4f6c6b4..f2f7daf 100644 --- a/demo/models.py +++ b/demo/models.py @@ -41,6 +41,11 @@ def get_newsletter_panels(cls): panel.permission = "demo.sendnewsletter_articlepage" return panels + def get_newsletter_context(self): + context = super().get_newsletter_context() + context["rendering_newsletter"] = True + return context + class CustomRecipients(NewsletterRecipientsBase): greeting = RichTextField(blank=True) diff --git a/demo/templates/demo/article_page.html b/demo/templates/demo/article_page.html index 22a6f6c..504b07d 100644 --- a/demo/templates/demo/article_page.html +++ b/demo/templates/demo/article_page.html @@ -14,6 +14,6 @@

{{ page.title }}

{% endif %} {% for block in page.body %} -
{% include_block block %}
+ {% include_block block %} {% endfor %} {% endblock %} diff --git a/docs/customization.rst b/docs/customization.rst index 1266992..688ab58 100644 --- a/docs/customization.rst +++ b/docs/customization.rst @@ -74,6 +74,52 @@ When embedding images, be sure to use their ``full_url`` link: 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 ---------------- From 55922ab7f06fcdf643a0f7ed1ee27aeef55f21d1 Mon Sep 17 00:00:00 2001 From: Alex Morega Date: Wed, 24 Jul 2024 16:30:43 +0300 Subject: [PATCH 11/12] PR incorporate feedback --- docs/conf.py | 9 +++++++++ docs/customization.rst | 6 +++--- docs/settings.rst | 5 ++--- docs/usage.rst | 8 ++++---- 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 44b7e90..92a94bd 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -8,6 +8,7 @@ extensions = [ "sphinx.ext.autosectionlabel", + "sphinx.ext.intersphinx", "sphinx_wagtail_theme", ] @@ -25,3 +26,11 @@ } html_static_path = ["_static"] + +intersphinx_mapping = { + "django": ( + "https://docs.djangoproject.com/en/stable/", + "https://docs.djangoproject.com/en/stable/_objects/", + ), +} +intersphinx_disabled_reftypes = ["*"] diff --git a/docs/customization.rst b/docs/customization.rst index 688ab58..b5b5cd7 100644 --- a/docs/customization.rst +++ b/docs/customization.rst @@ -78,8 +78,8 @@ 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. +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. @@ -98,7 +98,7 @@ distinguish between newsletter and web rendering. context["rendering_newsletter"] = True return context -Then we can define a Streamfield block that only renders its content if the +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 diff --git a/docs/settings.rst b/docs/settings.rst index 799a4fe..6fde20b 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -1,9 +1,8 @@ Settings ======== -Wagtail-newsletter can be configured with the following `Django settings`_. - -.. _Django settings: https://docs.djangoproject.com/en/stable/ref/settings/ +Wagtail-newsletter can be configured with the following :doc:`Django settings +`. Campaigns --------- diff --git a/docs/usage.rst b/docs/usage.rst index a5015ba..9532b4a 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -29,8 +29,8 @@ When you're ready to unleash the campaign upon your audience, click *Send campaign* in the *Newsletter* editor tab. This will save a page revision, upload the content to the campaign provider, and trigger campaign sending. -Save campaign withouth sending ------------------------------- +Save campaign without sending +----------------------------- If you want to simply upload the content to the campaign provider (without sending a test email or triggering the campaign sending), maybe to make tweaks @@ -38,7 +38,7 @@ to the campaign in the provider's app, or use advanced features like scheduled sending, then click on *Save campaign to {provider}*. This will save a page revision, and upload the campaign, but take no further action. -Keep in mind that any changes you make to the campaign will be overridden if +Keep in mind that any changes you make to the campaign will be overwritten if you send a test email, or trigger campaign sending, from Wagtail. View report @@ -55,7 +55,7 @@ Newsletter-related actions performed by users on a newsletter page are `logged to the page history`_, just like regular save and publish actions. The following actions are logged: -- :ref:`Save campaign ` +- :ref:`Save campaign ` - :ref:`Send test email` - :ref:`Send campaign` From e1bb2b268aa5534e5ac243a3585c91f3f3a408d7 Mon Sep 17 00:00:00 2001 From: Alex Morega Date: Tue, 30 Jul 2024 18:06:47 +0300 Subject: [PATCH 12/12] PR feedback --- CONTRIBUTING.md | 6 +++--- demo/README.md | 1 + demo/fields.py | 4 ++-- docs/customization.rst | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7804e4d..794e408 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,7 +15,7 @@ With your preferred virtualenv activated, install testing dependencies: ```sh python -m pip install --upgrade 'pip>=21.3' -python -m pip install -e '.[testing]' -U +python -m pip install -e '.[testing,ci,mailchimp,mrml]' -U ``` ### Using flit @@ -48,8 +48,8 @@ Now you can run tests as shown below: tox ``` -or, you can run them for a specific environment `tox -e python3.11-django4.2-wagtail5.1` or specific test -`tox -e python3.11-django4.2-wagtail5.1-sqlite wagtail-newsletter.tests.test_file.TestClass.test_method` +or, you can run them for a specific environment `tox -e python3.12-django5.0-wagtail6.1` or specific test +`tox -e python3.12-django5.0-wagtail6.1-sqlite wagtail-newsletter.tests.test_file.TestClass.test_method` To run the test app interactively, use `tox -e interactive`, visit `http://127.0.0.1:8020/admin/` and log in with `admin`/`changeme`. diff --git a/demo/README.md b/demo/README.md index 5d14a46..af04fed 100644 --- a/demo/README.md +++ b/demo/README.md @@ -17,6 +17,7 @@ export WAGTAIL_NEWSLETTER_MAILCHIMP_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-us1 export WAGTAIL_NEWSLETTER_FROM_NAME="My Newsletter" export WAGTAIL_NEWSLETTER_REPLY_TO=sender@example.com +python -m pip install --editable='.[testing,mailchimp,mrml]' ./demo/manage.py migrate ./demo/manage.py createcachetable ./demo/manage.py createsuperuser diff --git a/demo/fields.py b/demo/fields.py index 895cba3..d280751 100644 --- a/demo/fields.py +++ b/demo/fields.py @@ -1,9 +1,9 @@ import json -from wagtail.fields import StreamField as WagtailStreamfield +from wagtail.fields import StreamField as WagtailStreamField -class StreamField(WagtailStreamfield): +class StreamField(WagtailStreamField): def __init__(self, *args, **kwargs): """ Overrides StreamField.__init__() to account for `block_types` no longer diff --git a/docs/customization.rst b/docs/customization.rst index b5b5cd7..dcf1f97 100644 --- a/docs/customization.rst +++ b/docs/customization.rst @@ -98,7 +98,7 @@ distinguish between newsletter and web rendering. context["rendering_newsletter"] = True return context -Then we can define a ``Streamfield`` block that only renders its content if the +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