在 post 中发送电子邮件时出现问题,在 Django 中保存信号
Problems sending emails in post save signal in Django
我试图在 post_save
信号中保存一些模型后发送电子邮件,但是当我第一次保存模型时,电子邮件没有发送,如果我第二次保存模型,电子邮件已发送。
备注
该模型有一个 ManyToMany
字段,所以我不能使用 pre_save
信号,因为它会引发错误:<mymodel: mymodel_name object (None)>" needs to have a value for field "id" before this many-to-many relationship can be used.
我有什么
型号
class Message(TimeStampedModel, models.Model):
"""Representation of a Message."""
recipients = models.ManyToManyField(
to=settings.AUTH_USER_MODEL, verbose_name=_("Recipients")
)
subject = models.CharField(
verbose_name=_("Subject"),
help_text=_(
"150 numbers / letters or fewer. Only letters and numbers are allowed."
),
max_length=150,
validators=[alphabets_accents_and_numbers],
)
content = models.TextField(verbose_name=_("Message"))
信号
@receiver(signal=post_save, sender=Message)
def send_message(sender, instance, **kwargs):
"""Send a email or Whatsapp if a new message is created."""
recipient_emails = [recipient.email for recipient in instance.recipients.all()]
attachments = []
if instance.messagefile_set:
for message_file in instance.messagefile_set.all():
attachments.append((message_file.file.name, message_file.file.read()))
send_mails(
subject=instance.subject,
message=instance.content,
recipient_list=recipient_emails,
attachments=attachments,
)
发送邮件包装器以发送电子邮件
这是信号中用来发送邮件的函数。
def send_mails(
subject: str,
message: str,
recipient_list: List[str],
from_email: Optional[str] = None,
**kwargs,
) -> int:
"""Wrapper around Django's EmailMessage done in send_mail().
Custom from_email handling and special Auto-Submitted header.
"""
if not from_email:
if hasattr(settings, "DEFAULT_FROM_EMAIL"):
from_email = settings.DEFAULT_FROM_EMAIL
else:
from_email = "webmaster@localhost"
connection = kwargs.get("connection", False) or get_connection(
username=kwargs.get("auth_user", None),
password=kwargs.get("auth_password", None),
fail_silently=kwargs.get("fail_silently", None),
)
multi_alt_kwargs = {
"connection": connection,
"headers": {"Auto-Submitted": "auto-generated"},
}
mail = EmailMessage(
subject=subject,
body=message,
from_email=from_email,
to=recipient_list,
**multi_alt_kwargs,
)
attachments = kwargs.get("attachments", None)
if attachments:
for attachment in attachments:
if isinstance(attachment, MIMEBase):
mail.attach(attachment)
else:
mail.attach(*attachment)
return mail.send()
在您创建 Message
时,在您 创建 (而不是更新)Message
之后立即(所以当 post_save
信号被调用),所有 ManyToManyField
都是空的,以及所有 ForeignKey
的反向)。这是有道理的,因为 before 你可以在两条记录之间创建多对多关系,这些记录需要保存到数据库中,否则这些记录没有主键,并且因此这些不能链接。
我建议不要为此使用信号。严格来说,您可以尝试使用 m2m_changed
signal [Django-doc],例如:
from django.contrib.auth import get_user_model
from django.db.models.signals import m2m_changed
def recipient_added(sender, instance, action, pk_set, **kwargs):
if action == 'post_add':
recipient_emails = list(get_user_model().objects.filter(pk__in=pk_set))
'm2m_changed'.connect(recipient_added, sender=Message.recipients.through)
不过,现在有可能messagefile_set
是后来保存的,所以还是有问题。
我建议将发送电子邮件的逻辑封装在一个函数中,然后在创建对象之后在视图中调用该函数。
例如,如果您有一个带有 MessageForm
的视图:
def send_message(request):
if request.method == 'POST':
form = MessageForm(request.POST, request.FILES)
if form.is_valid():
message = form.save()
send_messages(message)
这里的form
不仅会保存对象,还会保存它处理的多对多字段。所以在 form.save()
之后对象的保存 完全 完成。
在ModelAdmin
中你可以使用save_related
method [Django-doc]:
from django.contrib import admin
class MessageAdmin(admin.ModelAdmin):
def save_related(self, request, form, formsets, change):
super().save_related(request, form, formsets, change)
send_mail(form.instance)
你当然可以改变它。 首先调用.save_related()
超级方法很重要,因为这将调用form.save()
,后者将再次处理多对多关系等
我试图在 post_save
信号中保存一些模型后发送电子邮件,但是当我第一次保存模型时,电子邮件没有发送,如果我第二次保存模型,电子邮件已发送。
备注
该模型有一个 ManyToMany
字段,所以我不能使用 pre_save
信号,因为它会引发错误:<mymodel: mymodel_name object (None)>" needs to have a value for field "id" before this many-to-many relationship can be used.
我有什么
型号
class Message(TimeStampedModel, models.Model):
"""Representation of a Message."""
recipients = models.ManyToManyField(
to=settings.AUTH_USER_MODEL, verbose_name=_("Recipients")
)
subject = models.CharField(
verbose_name=_("Subject"),
help_text=_(
"150 numbers / letters or fewer. Only letters and numbers are allowed."
),
max_length=150,
validators=[alphabets_accents_and_numbers],
)
content = models.TextField(verbose_name=_("Message"))
信号
@receiver(signal=post_save, sender=Message)
def send_message(sender, instance, **kwargs):
"""Send a email or Whatsapp if a new message is created."""
recipient_emails = [recipient.email for recipient in instance.recipients.all()]
attachments = []
if instance.messagefile_set:
for message_file in instance.messagefile_set.all():
attachments.append((message_file.file.name, message_file.file.read()))
send_mails(
subject=instance.subject,
message=instance.content,
recipient_list=recipient_emails,
attachments=attachments,
)
发送邮件包装器以发送电子邮件
这是信号中用来发送邮件的函数。
def send_mails(
subject: str,
message: str,
recipient_list: List[str],
from_email: Optional[str] = None,
**kwargs,
) -> int:
"""Wrapper around Django's EmailMessage done in send_mail().
Custom from_email handling and special Auto-Submitted header.
"""
if not from_email:
if hasattr(settings, "DEFAULT_FROM_EMAIL"):
from_email = settings.DEFAULT_FROM_EMAIL
else:
from_email = "webmaster@localhost"
connection = kwargs.get("connection", False) or get_connection(
username=kwargs.get("auth_user", None),
password=kwargs.get("auth_password", None),
fail_silently=kwargs.get("fail_silently", None),
)
multi_alt_kwargs = {
"connection": connection,
"headers": {"Auto-Submitted": "auto-generated"},
}
mail = EmailMessage(
subject=subject,
body=message,
from_email=from_email,
to=recipient_list,
**multi_alt_kwargs,
)
attachments = kwargs.get("attachments", None)
if attachments:
for attachment in attachments:
if isinstance(attachment, MIMEBase):
mail.attach(attachment)
else:
mail.attach(*attachment)
return mail.send()
在您创建 Message
时,在您 创建 (而不是更新)Message
之后立即(所以当 post_save
信号被调用),所有 ManyToManyField
都是空的,以及所有 ForeignKey
的反向)。这是有道理的,因为 before 你可以在两条记录之间创建多对多关系,这些记录需要保存到数据库中,否则这些记录没有主键,并且因此这些不能链接。
我建议不要为此使用信号。严格来说,您可以尝试使用 m2m_changed
signal [Django-doc],例如:
from django.contrib.auth import get_user_model
from django.db.models.signals import m2m_changed
def recipient_added(sender, instance, action, pk_set, **kwargs):
if action == 'post_add':
recipient_emails = list(get_user_model().objects.filter(pk__in=pk_set))
'm2m_changed'.connect(recipient_added, sender=Message.recipients.through)
不过,现在有可能messagefile_set
是后来保存的,所以还是有问题。
我建议将发送电子邮件的逻辑封装在一个函数中,然后在创建对象之后在视图中调用该函数。
例如,如果您有一个带有 MessageForm
的视图:
def send_message(request):
if request.method == 'POST':
form = MessageForm(request.POST, request.FILES)
if form.is_valid():
message = form.save()
send_messages(message)
这里的form
不仅会保存对象,还会保存它处理的多对多字段。所以在 form.save()
之后对象的保存 完全 完成。
在ModelAdmin
中你可以使用save_related
method [Django-doc]:
from django.contrib import admin
class MessageAdmin(admin.ModelAdmin):
def save_related(self, request, form, formsets, change):
super().save_related(request, form, formsets, change)
send_mail(form.instance)
你当然可以改变它。 首先调用.save_related()
超级方法很重要,因为这将调用form.save()
,后者将再次处理多对多关系等