在 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(),后者将再次处理多对多关系等