diff --git a/setup/website_blog_email_template/odoo/addons/website_blog_email_template b/setup/website_blog_email_template/odoo/addons/website_blog_email_template new file mode 120000 index 0000000000..3ed530ce81 --- /dev/null +++ b/setup/website_blog_email_template/odoo/addons/website_blog_email_template @@ -0,0 +1 @@ +../../../../website_blog_email_template \ No newline at end of file diff --git a/setup/website_blog_email_template/setup.py b/setup/website_blog_email_template/setup.py new file mode 100644 index 0000000000..28c57bb640 --- /dev/null +++ b/setup/website_blog_email_template/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/website_blog_email_template/README.rst b/website_blog_email_template/README.rst new file mode 100644 index 0000000000..d9eefbf6df --- /dev/null +++ b/website_blog_email_template/README.rst @@ -0,0 +1,323 @@ +=========================== +Website Blog Email Template +=========================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:be4c40b5d965009ef0b2392c723ccfd5e9b9dfa35a24967c19eb2c5901457d14 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fwebsite-lightgray.png?logo=github + :target: https://github.com/OCA/website/tree/18.0/website_blog_email_template + :alt: OCA/website +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/website-18-0/website-18-0-website_blog_email_template + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/website&target_branch=18.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module extends the website blog functionality by allowing you to +configure custom email templates for new blog post notifications. + +Main Features +------------- + +- **Custom Email Templates**: Configure a custom email template for each + blog to personalize notifications +- **Template Selection**: Choose from existing mail templates or create + new ones specifically for blog notifications +- **Context Access**: Templates have access to blog post information + through context variables +- **Backward Compatible**: If no custom template is configured, the + default template is used +- **Link to Post**: Templates can include direct links to the published + blog post + +Benefits +-------- + +- Personalize email notifications for different blogs +- Create branded email templates matching your company style +- Include additional information in notifications (author, subtitle, + etc.) +- Maintain consistency with your email marketing campaigns +- Better control over notification content and formatting + +Dependencies +------------ + +This module requires: + +- ``website_blog``: Odoo's website blog module + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +This module does not require additional configuration after +installation. It works automatically once installed. + +Installation +------------ + +1. Go to the **Apps** menu +2. Remove the "Apps" filter if necessary +3. Search for "Website Blog Email Template" +4. Click **Install** + +Prerequisites +------------- + +Make sure the following modules are installed: + +- **Website Blog** (base blog module) + +The system will automatically install the necessary dependencies during +installation. + +Default Template +---------------- + +After installation, a default custom template is available: + +- **Template Name**: "Blog: New Post Published (Custom Template)" +- **Location**: Settings > Technical > Email > Templates +- **Model**: Blog (blog.blog) + +You can use this template as-is, customize it, or create new templates +based on it. + +Permissions +----------- + +The module uses the same access permissions as the base modules: + +- Users with access to **Website** and **Blogs** can configure email + templates for blogs +- Users with access to **Settings > Technical > Email > Templates** can + create and edit email templates + +No additional permission configuration is required. + +Template Configuration +---------------------- + +**Template Requirements:** + +1. **Model**: Must be set to "Blog" (blog.blog) +2. **Subject**: Can use ``{{ object.name }}`` for blog name +3. **Body**: HTML content with access to: + + - Blog object: ``object.name``, ``object.subtitle`` + - Post context: ``ctx.get('blog_post')`` for post information + +**Recommended Template Structure:** + +- Include a greeting +- Display blog name +- Show post title and subtitle +- Include a link to the post using ``ctx.get('blog_post').website_url`` +- Add a closing message + +Usage +===== + +This guide explains how to use the Website Blog Email Template module to +configure custom email notifications for new blog posts. + +Configure a Custom Email Template for a Blog +-------------------------------------------- + +**Step 1: Access Blog Settings** + +1. Go to the **Website** module +2. Navigate to **Blogs** > **Blogs** +3. Select the blog you want to configure +4. Open the blog form view + +**Step 2: Select or Create Email Template** + +1. In the blog form, locate the **Email Template for New Posts** field +2. You have two options: + + - **Option A**: Select an existing template from the dropdown + - **Option B**: Create a new template (click the search icon and then + "Create") + +**Step 3: Create a New Template (if needed)** + +1. Click on the **Email Template for New Posts** field +2. Click the search icon (magnifying glass) +3. Click **Create** button +4. Fill in the template form: + + - **Name**: Give your template a descriptive name (e.g., "Blog Post + Notification - Marketing") + - **Model**: Should be set to "Blog" (blog.blog) + - **Subject**: Email subject line (e.g., "New post: {{ object.name + }}") + - **Body**: HTML content of the email + +5. Save the template +6. Select it in the blog's **Email Template for New Posts** field + +**Step 4: Save the Blog Configuration** + +1. Save the blog form +2. The custom template will now be used when new posts are published + +Using Context Variables in Templates +------------------------------------ + +When creating or editing a template, you can use the following context +variables: + +**Blog Information:** + +- ``{{ object.name }}`` - Blog name +- ``{{ object.subtitle }}`` - Blog subtitle + +**Post Information (via context):** + +- ``{{ ctx.get('blog_post').name }}`` - Post title +- ``{{ ctx.get('blog_post').subtitle }}`` - Post subtitle +- ``{{ ctx.get('blog_post').author_name }}`` - Author name +- ``{{ ctx.get('blog_post').website_url }}`` - Direct link to the post +- ``{{ ctx.get('blog_post_id') }}`` - Post ID + +**Example Template Body:** + +.. code:: html + +
+

Hello,

+ A new post has been published on the {{ object.name }} blog:

+ {{ ctx.get('blog_post').name }}

+ Read the full post +

+
+ +Automatic Behavior +------------------ + +**Template Selection Logic** + +When a new blog post is published: + +1. The system checks if the blog has a custom template configured +2. **If a template is configured**: Uses + ``message_post_with_template()`` with the custom template +3. **If no template is configured**: Uses the default template + (``website_blog.blog_post_template_new_post``) + +**Notification Recipients** + +- Only blog followers receive the notification +- The notification uses the subtype "Published Post" + (mt_blog_blog_published) +- Email layout uses "mail.mail_notification_light" for custom templates + +Usage Examples +-------------- + +**Example 1: Simple Custom Template** + +1. Create a blog "Company News" +2. Create a template with subject: "New article on {{ object.name }}" +3. Body includes post title and link +4. Configure the template in the blog +5. When posts are published, followers receive the custom email + +**Example 2: Branded Template** + +1. Create a blog "Product Updates" +2. Create a template matching your company branding +3. Include company logo, colors, and specific formatting +4. Add additional information like author bio or related posts +5. Configure in the blog settings + +**Example 3: Multi-language Support** + +1. Create different templates for different languages +2. Configure language-specific templates per blog +3. Templates can use language-specific formatting and content + +Tips +---- + +- Test your template before publishing posts by using the "Send Test + Email" feature in the template +- Use the default template as a reference when creating custom templates +- Include the post link (``ctx.get('blog_post').website_url``) to make + it easy for readers to access the content +- Consider your email client compatibility when designing HTML templates +- The template is rendered with the blog as ``object``, so use + ``object.name`` for blog name +- Post information is available through context, so use + ``ctx.get('blog_post')`` to access post data + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Escodoo + +Contributors +------------ + +- `ESCODOO `__: + + - Marcel Savegnago + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +.. |maintainer-marcelsavegnago| image:: https://github.com/marcelsavegnago.png?size=40px + :target: https://github.com/marcelsavegnago + :alt: marcelsavegnago + +Current `maintainer `__: + +|maintainer-marcelsavegnago| + +This module is part of the `OCA/website `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/website_blog_email_template/__init__.py b/website_blog_email_template/__init__.py new file mode 100644 index 0000000000..0650744f6b --- /dev/null +++ b/website_blog_email_template/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/website_blog_email_template/__manifest__.py b/website_blog_email_template/__manifest__.py new file mode 100644 index 0000000000..0f94fd08d3 --- /dev/null +++ b/website_blog_email_template/__manifest__.py @@ -0,0 +1,19 @@ +# Copyright 2025 Escodoo +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +{ + "name": "Website Blog Email Template", + "summary": "Allow configuring custom email template for new blog posts", + "version": "18.0.1.0.0", + "category": "Website", + "website": "https://github.com/OCA/website", + "author": "Escodoo, Odoo Community Association (OCA)", + "maintainers": ["marcelsavegnago"], + "license": "AGPL-3", + "application": False, + "installable": True, + "depends": ["website_blog"], + "data": [ + "data/mail_template_data.xml", + "views/website_blog_views.xml", + ], +} diff --git a/website_blog_email_template/data/mail_template_data.xml b/website_blog_email_template/data/mail_template_data.xml new file mode 100644 index 0000000000..2f66c594ae --- /dev/null +++ b/website_blog_email_template/data/mail_template_data.xml @@ -0,0 +1,57 @@ + + + + + Blog: New Post Published (Custom Template) + + New post published on {{ object.name }} + Email template for notifying followers when a new blog post is published. This template can be configured in the blog settings. + +
+

+ Hello,

+ A new post has been published on the Blog blog you are following:

+

+ New Post Title +
+ +

+ +

+
+ +

+ By +

+
+ +

+ Thank you for following our blog!
+ + +
+
+ Blog Team +

+

+
+
+
+
diff --git a/website_blog_email_template/models/__init__.py b/website_blog_email_template/models/__init__.py new file mode 100644 index 0000000000..412086456a --- /dev/null +++ b/website_blog_email_template/models/__init__.py @@ -0,0 +1 @@ +from . import website_blog diff --git a/website_blog_email_template/models/website_blog.py b/website_blog_email_template/models/website_blog.py new file mode 100644 index 0000000000..e48d8fcf1e --- /dev/null +++ b/website_blog_email_template/models/website_blog.py @@ -0,0 +1,86 @@ +# Copyright 2025 Marcel Savegnago - Escodoo +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import fields, models + + +class Blog(models.Model): + _inherit = "blog.blog" + + mail_template_id = fields.Many2one( + comodel_name="mail.template", + string="Email Template for New Posts", + domain=[("model", "=", "blog.blog")], + help="If set, this email template will be used when a new post is published " + "instead of the default template. The template should be configured for " + "the 'blog.blog' model.", + ) + + def message_post_with_view( + self, view_xmlid, subject=None, values=None, subtype_id=None, **kwargs + ): + render_values = values or {} + return self.message_post_with_source( + view_xmlid, + subject=subject, + render_values=render_values, + subtype_id=subtype_id, + **kwargs, + ) + + def message_post_with_template( + self, + template_id, + subject=None, + email_layout_xmlid=None, + subtype_id=None, + **kwargs, + ): + template = self.env["mail.template"].browse(template_id).sudo() + + for blog in self: + subject_map = template._render_field("subject", [blog.id]) + body_map = template._render_field("body_html", [blog.id]) + + final_subject = subject or subject_map.get(blog.id) or "" + body = body_map.get(blog.id) or "" + + blog.message_post( + body=body, + subject=final_subject, + subtype_id=subtype_id, + **kwargs, + ) + + return self.message_ids[:1] + + +class BlogPost(models.Model): + _inherit = "blog.post" + + def _check_for_publication(self, vals): + if vals.get("is_published"): + for post in self.filtered(lambda p: p.active): + if post.blog_id.mail_template_id: + post.blog_id.with_context( + blog_post_id=post.id, + blog_post=post, + ).message_post_with_template( + post.blog_id.mail_template_id.id, + subject=post.name, + email_layout_xmlid="mail.mail_notification_light", + subtype_id=self.env["ir.model.data"]._xmlid_to_res_id( + "website_blog.mt_blog_blog_published" + ), + ) + else: + post.blog_id.message_post_with_view( + "website_blog.blog_post_template_new_post", + subject=post.name, + values={"post": post}, + subtype_id=self.env["ir.model.data"]._xmlid_to_res_id( + "website_blog.mt_blog_blog_published" + ), + ) + return True + return False diff --git a/website_blog_email_template/pyproject.toml b/website_blog_email_template/pyproject.toml new file mode 100644 index 0000000000..4231d0cccb --- /dev/null +++ b/website_blog_email_template/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/website_blog_email_template/readme/CONFIGURE.md b/website_blog_email_template/readme/CONFIGURE.md new file mode 100644 index 0000000000..61ef82956e --- /dev/null +++ b/website_blog_email_template/readme/CONFIGURE.md @@ -0,0 +1,51 @@ +This module does not require additional configuration after installation. It works automatically once installed. + +## Installation + +1. Go to the **Apps** menu +2. Remove the "Apps" filter if necessary +3. Search for "Website Blog Email Template" +4. Click **Install** + +## Prerequisites + +Make sure the following modules are installed: +* **Website Blog** (base blog module) + +The system will automatically install the necessary dependencies during installation. + +## Default Template + +After installation, a default custom template is available: +* **Template Name**: "Blog: New Post Published (Custom Template)" +* **Location**: Settings > Technical > Email > Templates +* **Model**: Blog (blog.blog) + +You can use this template as-is, customize it, or create new templates based on it. + +## Permissions + +The module uses the same access permissions as the base modules: +* Users with access to **Website** and **Blogs** can configure email templates for blogs +* Users with access to **Settings > Technical > Email > Templates** can create and edit email templates + +No additional permission configuration is required. + +## Template Configuration + +**Template Requirements:** + +1. **Model**: Must be set to "Blog" (blog.blog) +2. **Subject**: Can use `{{ object.name }}` for blog name +3. **Body**: HTML content with access to: + - Blog object: `object.name`, `object.subtitle` + - Post context: `ctx.get('blog_post')` for post information + +**Recommended Template Structure:** + +* Include a greeting +* Display blog name +* Show post title and subtitle +* Include a link to the post using `ctx.get('blog_post').website_url` +* Add a closing message + diff --git a/website_blog_email_template/readme/CONTRIBUTORS.md b/website_blog_email_template/readme/CONTRIBUTORS.md new file mode 100644 index 0000000000..e9e5505f00 --- /dev/null +++ b/website_blog_email_template/readme/CONTRIBUTORS.md @@ -0,0 +1,3 @@ +- [ESCODOO](https://escodoo.com.br): + - Marcel Savegnago \ + diff --git a/website_blog_email_template/readme/DESCRIPTION.md b/website_blog_email_template/readme/DESCRIPTION.md new file mode 100644 index 0000000000..bc5d4f0480 --- /dev/null +++ b/website_blog_email_template/readme/DESCRIPTION.md @@ -0,0 +1,23 @@ +This module extends the website blog functionality by allowing you to configure custom email templates for new blog post notifications. + +## Main Features + +* **Custom Email Templates**: Configure a custom email template for each blog to personalize notifications +* **Template Selection**: Choose from existing mail templates or create new ones specifically for blog notifications +* **Context Access**: Templates have access to blog post information through context variables +* **Backward Compatible**: If no custom template is configured, the default template is used +* **Link to Post**: Templates can include direct links to the published blog post + +## Benefits + +* Personalize email notifications for different blogs +* Create branded email templates matching your company style +* Include additional information in notifications (author, subtitle, etc.) +* Maintain consistency with your email marketing campaigns +* Better control over notification content and formatting + +## Dependencies + +This module requires: +* `website_blog`: Odoo's website blog module + diff --git a/website_blog_email_template/readme/USAGE.md b/website_blog_email_template/readme/USAGE.md new file mode 100644 index 0000000000..b34b383b96 --- /dev/null +++ b/website_blog_email_template/readme/USAGE.md @@ -0,0 +1,111 @@ +This guide explains how to use the Website Blog Email Template module to configure custom email notifications for new blog posts. + +## Configure a Custom Email Template for a Blog + +**Step 1: Access Blog Settings** + +1. Go to the **Website** module +2. Navigate to **Blogs** > **Blogs** +3. Select the blog you want to configure +4. Open the blog form view + +**Step 2: Select or Create Email Template** + +1. In the blog form, locate the **Email Template for New Posts** field +2. You have two options: + * **Option A**: Select an existing template from the dropdown + * **Option B**: Create a new template (click the search icon and then "Create") + +**Step 3: Create a New Template (if needed)** + +1. Click on the **Email Template for New Posts** field +2. Click the search icon (magnifying glass) +3. Click **Create** button +4. Fill in the template form: + * **Name**: Give your template a descriptive name (e.g., "Blog Post Notification - Marketing") + * **Model**: Should be set to "Blog" (blog.blog) + * **Subject**: Email subject line (e.g., "New post: {{ object.name }}") + * **Body**: HTML content of the email +5. Save the template +6. Select it in the blog's **Email Template for New Posts** field + +**Step 4: Save the Blog Configuration** + +1. Save the blog form +2. The custom template will now be used when new posts are published + +## Using Context Variables in Templates + +When creating or editing a template, you can use the following context variables: + +**Blog Information:** +* `{{ object.name }}` - Blog name +* `{{ object.subtitle }}` - Blog subtitle + +**Post Information (via context):** +* `{{ ctx.get('blog_post').name }}` - Post title +* `{{ ctx.get('blog_post').subtitle }}` - Post subtitle +* `{{ ctx.get('blog_post').author_name }}` - Author name +* `{{ ctx.get('blog_post').website_url }}` - Direct link to the post +* `{{ ctx.get('blog_post_id') }}` - Post ID + +**Example Template Body:** + +```html +
+

Hello,

+ A new post has been published on the {{ object.name }} blog:

+ {{ ctx.get('blog_post').name }}

+ Read the full post +

+
+``` + +## Automatic Behavior + +**Template Selection Logic** + +When a new blog post is published: +1. The system checks if the blog has a custom template configured +2. **If a template is configured**: Uses `message_post_with_template()` with the custom template +3. **If no template is configured**: Uses the default template (`website_blog.blog_post_template_new_post`) + +**Notification Recipients** + +* Only blog followers receive the notification +* The notification uses the subtype "Published Post" (mt_blog_blog_published) +* Email layout uses "mail.mail_notification_light" for custom templates + +## Usage Examples + +**Example 1: Simple Custom Template** + +1. Create a blog "Company News" +2. Create a template with subject: "New article on {{ object.name }}" +3. Body includes post title and link +4. Configure the template in the blog +5. When posts are published, followers receive the custom email + +**Example 2: Branded Template** + +1. Create a blog "Product Updates" +2. Create a template matching your company branding +3. Include company logo, colors, and specific formatting +4. Add additional information like author bio or related posts +5. Configure in the blog settings + +**Example 3: Multi-language Support** + +1. Create different templates for different languages +2. Configure language-specific templates per blog +3. Templates can use language-specific formatting and content + +## Tips + +* Test your template before publishing posts by using the "Send Test Email" feature in the template +* Use the default template as a reference when creating custom templates +* Include the post link (`ctx.get('blog_post').website_url`) to make it easy for readers to access the content +* Consider your email client compatibility when designing HTML templates +* The template is rendered with the blog as `object`, so use `object.name` for blog name +* Post information is available through context, so use `ctx.get('blog_post')` to access post data + diff --git a/website_blog_email_template/static/description/index.html b/website_blog_email_template/static/description/index.html new file mode 100644 index 0000000000..6155933a58 --- /dev/null +++ b/website_blog_email_template/static/description/index.html @@ -0,0 +1,656 @@ + + + + + +Website Blog Email Template + + + +
+

Website Blog Email Template

+ + +

Beta License: AGPL-3 OCA/website Translate me on Weblate Try me on Runboat

+

This module extends the website blog functionality by allowing you to +configure custom email templates for new blog post notifications.

+
+

Main Features

+
    +
  • Custom Email Templates: Configure a custom email template for each +blog to personalize notifications
  • +
  • Template Selection: Choose from existing mail templates or create +new ones specifically for blog notifications
  • +
  • Context Access: Templates have access to blog post information +through context variables
  • +
  • Backward Compatible: If no custom template is configured, the +default template is used
  • +
  • Link to Post: Templates can include direct links to the published +blog post
  • +
+
+
+

Benefits

+
    +
  • Personalize email notifications for different blogs
  • +
  • Create branded email templates matching your company style
  • +
  • Include additional information in notifications (author, subtitle, +etc.)
  • +
  • Maintain consistency with your email marketing campaigns
  • +
  • Better control over notification content and formatting
  • +
+
+
+

Dependencies

+

This module requires:

+
    +
  • website_blog: Odoo’s website blog module
  • +
+

Table of contents

+ +
+

Configuration

+

This module does not require additional configuration after +installation. It works automatically once installed.

+
+
+
+

Installation

+
    +
  1. Go to the Apps menu
  2. +
  3. Remove the “Apps” filter if necessary
  4. +
  5. Search for “Website Blog Email Template”
  6. +
  7. Click Install
  8. +
+
+
+

Prerequisites

+

Make sure the following modules are installed:

+
    +
  • Website Blog (base blog module)
  • +
+

The system will automatically install the necessary dependencies during +installation.

+
+
+

Default Template

+

After installation, a default custom template is available:

+
    +
  • Template Name: “Blog: New Post Published (Custom Template)”
  • +
  • Location: Settings > Technical > Email > Templates
  • +
  • Model: Blog (blog.blog)
  • +
+

You can use this template as-is, customize it, or create new templates +based on it.

+
+
+

Permissions

+

The module uses the same access permissions as the base modules:

+
    +
  • Users with access to Website and Blogs can configure email +templates for blogs
  • +
  • Users with access to Settings > Technical > Email > Templates can +create and edit email templates
  • +
+

No additional permission configuration is required.

+
+
+

Template Configuration

+

Template Requirements:

+
    +
  1. Model: Must be set to “Blog” (blog.blog)
  2. +
  3. Subject: Can use {{ object.name }} for blog name
  4. +
  5. Body: HTML content with access to:
      +
    • Blog object: object.name, object.subtitle
    • +
    • Post context: ctx.get('blog_post') for post information
    • +
    +
  6. +
+

Recommended Template Structure:

+
    +
  • Include a greeting
  • +
  • Display blog name
  • +
  • Show post title and subtitle
  • +
  • Include a link to the post using ctx.get('blog_post').website_url
  • +
  • Add a closing message
  • +
+
+

Usage

+

This guide explains how to use the Website Blog Email Template module to +configure custom email notifications for new blog posts.

+
+
+
+

Configure a Custom Email Template for a Blog

+

Step 1: Access Blog Settings

+
    +
  1. Go to the Website module
  2. +
  3. Navigate to Blogs > Blogs
  4. +
  5. Select the blog you want to configure
  6. +
  7. Open the blog form view
  8. +
+

Step 2: Select or Create Email Template

+
    +
  1. In the blog form, locate the Email Template for New Posts field
  2. +
  3. You have two options:
      +
    • Option A: Select an existing template from the dropdown
    • +
    • Option B: Create a new template (click the search icon and then +“Create”)
    • +
    +
  4. +
+

Step 3: Create a New Template (if needed)

+
    +
  1. Click on the Email Template for New Posts field
  2. +
  3. Click the search icon (magnifying glass)
  4. +
  5. Click Create button
  6. +
  7. Fill in the template form:
      +
    • Name: Give your template a descriptive name (e.g., “Blog Post +Notification - Marketing”)
    • +
    • Model: Should be set to “Blog” (blog.blog)
    • +
    • Subject: Email subject line (e.g., “New post: {{ object.name +}}”)
    • +
    • Body: HTML content of the email
    • +
    +
  8. +
  9. Save the template
  10. +
  11. Select it in the blog’s Email Template for New Posts field
  12. +
+

Step 4: Save the Blog Configuration

+
    +
  1. Save the blog form
  2. +
  3. The custom template will now be used when new posts are published
  4. +
+
+
+

Using Context Variables in Templates

+

When creating or editing a template, you can use the following context +variables:

+

Blog Information:

+
    +
  • {{ object.name }} - Blog name
  • +
  • {{ object.subtitle }} - Blog subtitle
  • +
+

Post Information (via context):

+
    +
  • {{ ctx.get('blog_post').name }} - Post title
  • +
  • {{ ctx.get('blog_post').subtitle }} - Post subtitle
  • +
  • {{ ctx.get('blog_post').author_name }} - Author name
  • +
  • {{ ctx.get('blog_post').website_url }} - Direct link to the post
  • +
  • {{ ctx.get('blog_post_id') }} - Post ID
  • +
+

Example Template Body:

+
+<div style="margin: 0px; padding: 0px; font-size: 13px;">
+    <p>Hello,<br/><br/>
+    A new post has been published on the <strong>{{ object.name }}</strong> blog:<br/><br/>
+    <strong>{{ ctx.get('blog_post').name }}</strong><br/><br/>
+    <a href="{{ ctx.get('blog_post').website_url }}">Read the full post</a>
+    </p>
+</div>
+
+
+
+

Automatic Behavior

+

Template Selection Logic

+

When a new blog post is published:

+
    +
  1. The system checks if the blog has a custom template configured
  2. +
  3. If a template is configured: Uses +message_post_with_template() with the custom template
  4. +
  5. If no template is configured: Uses the default template +(website_blog.blog_post_template_new_post)
  6. +
+

Notification Recipients

+
    +
  • Only blog followers receive the notification
  • +
  • The notification uses the subtype “Published Post” +(mt_blog_blog_published)
  • +
  • Email layout uses “mail.mail_notification_light” for custom templates
  • +
+
+
+

Usage Examples

+

Example 1: Simple Custom Template

+
    +
  1. Create a blog “Company News”
  2. +
  3. Create a template with subject: “New article on {{ object.name }}”
  4. +
  5. Body includes post title and link
  6. +
  7. Configure the template in the blog
  8. +
  9. When posts are published, followers receive the custom email
  10. +
+

Example 2: Branded Template

+
    +
  1. Create a blog “Product Updates”
  2. +
  3. Create a template matching your company branding
  4. +
  5. Include company logo, colors, and specific formatting
  6. +
  7. Add additional information like author bio or related posts
  8. +
  9. Configure in the blog settings
  10. +
+

Example 3: Multi-language Support

+
    +
  1. Create different templates for different languages
  2. +
  3. Configure language-specific templates per blog
  4. +
  5. Templates can use language-specific formatting and content
  6. +
+
+
+

Tips

+
    +
  • Test your template before publishing posts by using the “Send Test +Email” feature in the template
  • +
  • Use the default template as a reference when creating custom templates
  • +
  • Include the post link (ctx.get('blog_post').website_url) to make +it easy for readers to access the content
  • +
  • Consider your email client compatibility when designing HTML templates
  • +
  • The template is rendered with the blog as object, so use +object.name for blog name
  • +
  • Post information is available through context, so use +ctx.get('blog_post') to access post data
  • +
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+
+
+

Authors

+
    +
  • Escodoo
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

Current maintainer:

+

marcelsavegnago

+

This module is part of the OCA/website project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+ + diff --git a/website_blog_email_template/tests/__init__.py b/website_blog_email_template/tests/__init__.py new file mode 100644 index 0000000000..622bb44ec9 --- /dev/null +++ b/website_blog_email_template/tests/__init__.py @@ -0,0 +1 @@ +from . import test_website_blog_email_template diff --git a/website_blog_email_template/tests/test_website_blog_email_template.py b/website_blog_email_template/tests/test_website_blog_email_template.py new file mode 100644 index 0000000000..2062109297 --- /dev/null +++ b/website_blog_email_template/tests/test_website_blog_email_template.py @@ -0,0 +1,273 @@ +# Copyright 2025 Marcel Savegnago - Escodoo +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from unittest.mock import patch + +from odoo.tests.common import tagged + +from odoo.addons.website_blog.tests.common import TestWebsiteBlogCommon + + +@tagged("post_install", "-at_install") +class TestWebsiteBlogEmailTemplate(TestWebsiteBlogCommon): + def setUp(self): + super().setUp() + self.mail_template = self.env["mail.template"].create( + { + "name": "Test Blog Post Template", + "model_id": self.env["ir.model"]._get("blog.blog").id, + "subject": "New post: {{ object.name }}", + "body_html": ( + "

New post {{ ctx.get('blog_post').name }} " "published!

" + ), + } + ) + + def test_blog_post_publication_without_template(self): + """Test that default template is used when no custom template is configured""" + blog = self.test_blog + blog.mail_template_id = False + + # Subscribe users to the blog + blog.message_subscribe( + [self.user_employee.partner_id.id, self.user_public.partner_id.id] + ) + + # Create a new post + post = ( + self.env["blog.post"] + .with_user(self.user_blogmanager) + .create( + { + "name": "Test Post Without Template", + "blog_id": blog.id, + "content": "

Test content

", + } + ) + ) + + # Mock message_post_with_view to verify it's called + with patch.object( + type(blog), "message_post_with_view" + ) as mock_message_post_with_view: + # Publish the post + post.write({"website_published": True}) + + # Verify message_post_with_view was called with default template + mock_message_post_with_view.assert_called_once() + call_args = mock_message_post_with_view.call_args + self.assertEqual( + call_args[0][0], "website_blog.blog_post_template_new_post" + ) + self.assertEqual(call_args[1]["subject"], "Test Post Without Template") + self.assertIn("post", call_args[1]["values"]) + self.assertEqual(call_args[1]["values"]["post"], post) + + def test_blog_post_publication_with_template(self): + """Test that custom template is used when configured""" + blog = self.test_blog + blog.mail_template_id = self.mail_template + + # Subscribe users to the blog + blog.message_subscribe( + [self.user_employee.partner_id.id, self.user_public.partner_id.id] + ) + + # Create a new post + post = ( + self.env["blog.post"] + .with_user(self.user_blogmanager) + .create( + { + "name": "Test Post With Template", + "blog_id": blog.id, + "content": "

Test content

", + } + ) + ) + + # Mock message_post_with_template to verify it's called + with patch.object( + type(blog), "message_post_with_template" + ) as mock_message_post_with_template: + # Publish the post + post.write({"website_published": True}) + + # Verify message_post_with_template was called with custom template + mock_message_post_with_template.assert_called_once() + call_args = mock_message_post_with_template.call_args + self.assertEqual(call_args[0][0], self.mail_template.id) + self.assertEqual(call_args[1]["subject"], "Test Post With Template") + self.assertEqual( + call_args[1]["email_layout_xmlid"], "mail.mail_notification_light" + ) + + def test_blog_post_publication_context(self): + """Test that blog post is passed in context when using custom template""" + blog = self.test_blog + blog.mail_template_id = self.mail_template + + # Subscribe users to the blog + blog.message_subscribe( + [self.user_employee.partner_id.id, self.user_public.partner_id.id] + ) + + # Create a new post + post = ( + self.env["blog.post"] + .with_user(self.user_blogmanager) + .create( + { + "name": "Test Post Context", + "blog_id": blog.id, + "content": "

Test content

", + } + ) + ) + + # Track context passed to message_post_with_template + captured_context = {} + + # Store original method + original_message_post_with_template = type(blog).message_post_with_template + + def mock_message_post_with_template(self, *args, **kwargs): + # Capture context from the recordset that calls the method + # (self is the recordset returned by with_context) + captured_context["context"] = self.env.context + # Call original method + return original_message_post_with_template(self, *args, **kwargs) + + # Patch message_post_with_template to capture context + with patch.object( + type(blog), "message_post_with_template", mock_message_post_with_template + ): + # Publish the post + post.write({"website_published": True}) + + # Verify context contains blog_post + self.assertIn("blog_post", captured_context["context"]) + self.assertIn("blog_post_id", captured_context["context"]) + self.assertEqual(captured_context["context"]["blog_post"], post) + self.assertEqual(captured_context["context"]["blog_post_id"], post.id) + + def test_blog_post_publication_message_creation(self): + """Test that messages are actually created when publishing posts""" + blog = self.test_blog + blog.mail_template_id = False + + # Subscribe users to the blog + blog.message_subscribe( + [self.user_employee.partner_id.id, self.user_public.partner_id.id] + ) + + # Count initial messages + initial_message_count = len(blog.message_ids) + + # Create and publish a post + post = ( + self.env["blog.post"] + .with_user(self.user_blogmanager) + .create( + { + "name": "Test Post Message", + "blog_id": blog.id, + "content": "

Test content

", + } + ) + ) + post.write({"website_published": True}) + + # Verify a new message was created + self.assertEqual(len(blog.message_ids), initial_message_count + 1) + + # Verify the message has the correct subtype + publish_message = blog.message_ids.filtered( + lambda m: m.subtype_id + == self.env.ref("website_blog.mt_blog_blog_published") + ) + self.assertTrue(publish_message, "Publish message should be created") + + # Verify followers are notified + self.assertIn( + self.user_employee.partner_id, + publish_message.notified_partner_ids, + "Blog followers should be notified", + ) + + def test_blog_post_publication_with_template_message_creation(self): + """Test that messages are created when using custom template""" + blog = self.test_blog + blog.mail_template_id = self.mail_template + + # Subscribe users to the blog + blog.message_subscribe( + [self.user_employee.partner_id.id, self.user_public.partner_id.id] + ) + + # Count initial messages + initial_message_count = len(blog.message_ids) + + # Create and publish a post + post = ( + self.env["blog.post"] + .with_user(self.user_blogmanager) + .create( + { + "name": "Test Post Custom Template", + "blog_id": blog.id, + "content": "

Test content

", + } + ) + ) + post.write({"website_published": True}) + + # Verify a new message was created + self.assertEqual(len(blog.message_ids), initial_message_count + 1) + + # Verify the message has the correct subtype + publish_message = blog.message_ids.filtered( + lambda m: m.subtype_id + == self.env.ref("website_blog.mt_blog_blog_published") + ) + self.assertTrue(publish_message, "Publish message should be created") + + # Verify followers are notified + self.assertIn( + self.user_employee.partner_id, + publish_message.notified_partner_ids, + "Blog followers should be notified", + ) + + def test_blog_post_unpublish_no_notification(self): + """Test that unpublishing a post doesn't trigger notification""" + blog = self.test_blog + blog.mail_template_id = False + + # Subscribe users to the blog + blog.message_subscribe( + [self.user_employee.partner_id.id, self.user_public.partner_id.id] + ) + + # Create and publish a post + post = ( + self.env["blog.post"] + .with_user(self.user_blogmanager) + .create( + { + "name": "Test Post Unpublish", + "blog_id": blog.id, + "content": "

Test content

", + } + ) + ) + post.write({"website_published": True}) + + # Count messages after publishing + messages_after_publish = len(blog.message_ids) + + # Unpublish the post + post.write({"website_published": False}) + + # Verify no new message was created + self.assertEqual(len(blog.message_ids), messages_after_publish) diff --git a/website_blog_email_template/views/website_blog_views.xml b/website_blog_email_template/views/website_blog_views.xml new file mode 100644 index 0000000000..80de488a7e --- /dev/null +++ b/website_blog_email_template/views/website_blog_views.xml @@ -0,0 +1,19 @@ + + + + + blog.blog.form.inherit.email.template + blog.blog + + + + + + + +