Django中的电子邮件模板化

7

众所周知(或应该知道),您可以使用Django的模板系统来渲染电子邮件正文:

def email(email, subject, template, context):
    from django.core.mail import send_mail
    from django.template import loader, Context

    send_mail(subject, loader.get_template(template).render(Context(context)), 'from@domain.com', [email,])

在我看来,这有一个缺陷:要编辑电子邮件的主题和内容,您必须同时编辑视图和模板。虽然我可以证明向管理员用户授予对模板的访问权限,但我不会给他们访问原始Python的权限!

真正酷的是,如果您可以指定电子邮件中的块并在发送电子邮件时将它们分别提取出来:

{% block subject %}This is my subject{% endblock %}
{% block plaintext %}My body{% endblock%}
{% block html %}My HTML body{% endblock%}

但是你该如何做到呢?你将如何逐个渲染块?
3个回答

11

这是我的第三个工作版本。它假设您有一个类似于以下的电子邮件模板:

{% block subject %}{% endblock %}
{% block plain %}{% endblock %}
{% block html %}{% endblock %}

我已经重构了代码,现在默认情况下通过列表进行电子邮件发送,并具有向单个电子邮件和 django.contrib.auth User(单个和多个)发送的实用程序方法。 我涵盖可能比我需要的更多,但就是这样。

我可能对Python过于热爱了。

def email_list(to_list, template_path, context_dict):
    from django.core.mail import send_mail
    from django.template import loader, Context

    nodes = dict((n.name, n) for n in loader.get_template(template_path).nodelist if n.__class__.__name__ == 'BlockNode')
    con = Context(context_dict)
    r = lambda n: nodes[n].render(con)

    for address in to_list:
        send_mail(r('subject'), r('plain'), 'from@domain.com', [address,])

def email(to, template_path, context_dict):
    return email_list([to,], template_path, context_dict)

def email_user(user, template_path, context_dict):
    return email_list([user.email,], template_path, context_dict)

def email_users(user_list, template_path, context_dict):
    return email_list([user.email for user in user_list], template_path, context_dict)

如果您能够改进它,请务必这样做。


哇,它可用了。考虑添加更多字段到基础设置中,以允许设置发件人/发件人名称/回复地址设置。 - Oli
我认为如果你使用.get_nodes_by_type(loader_tags.BlockNode)而不是n.class.name == 'BlockNode',那么模板继承将会起作用。 - Tom Christie
这个票 https://code.djangoproject.com/ticket/17193 基本上做了同样的事情。更冗长,但也处理了HTML、文件附件和自动生成纯文本。(这是差异,还有文档...https://code.djangoproject.com/attachment/ticket/17193/send_templated_mail.3.diff) 可能值得在django-dev列表上讨论将类似功能集成到核心中。 - Tom Christie
1
当我升级到Django 1.8时,这个出了问题。但这很容易解决。将loader.get_template(template_path).nodelist更改为loader.get_template(template_path).template。我通过https://dev59.com/a3E85IYBdhLWcg3wnU-p#29633563找到了这个解决方案。 - Phil Gyford
现在使用Django 4.1时,我遇到了像“TypeError: 'BlockNode' object is not iterable”这样的错误。这次的解决方案是将上面的行从loader.get_template(template_path).template更改为loader.get_template(template_path).template.nodelist - Phil Gyford
显示剩余3条评论

0
只需使用两个模板:一个用于正文,一个用于主题。

两个文件意味着基本上是同一件事的两个文件名。只需保持跟踪那么多模板就很麻烦。 - Oli

0

我无法使用{% body %}标签使模板继承正常工作,所以我转而使用了这样的模板:

{% extends "base.txt" %}

{% if subject %}Subject{% endif %}
{% if body %}Email body{% endif %}
{% if html %}<p>HTML body</p>{% endif %}

现在我们需要渲染模板三次,但继承功能正常工作。
c = Context(context, autoescape = False)
subject = render_to_string(template_name, {'subject': True}, c).strip()
body = render_to_string(template_name, {'body': True}, c).strip()
c = Context(context, autoescape = True)
html = render_to_string(template_name, {'html': True}, c).strip()

我还发现在渲染非HTML文本时关闭自动转义是必要的,以避免在电子邮件中出现转义文本。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接