【问题标题】:How to send email when save method is triggered in django admin panel在 django 管理面板中触发保存方法时如何发送电子邮件
【发布时间】:2021-09-21 13:41:56
【问题描述】:

我使用默认的 Django 管理面板作为我的后端。我有一个 Blogpost 模型。我想做的是每当管理员用户在 Django 管理员上保存 blogpost 对象时,我需要向新闻通讯订阅者发送一封电子邮件,通知他们网站上有一个新博客。

由于django admin是通过调用save函数自动保存博客的,所以不知道在哪里写send email api逻辑。希望我已经解释清楚了。

我的博文:

class BlogPost(models.Model):

    author = models.CharField(max_length=64, default='Admin')
    CATEGORY_CHOICES = (
        ('travel_news', 'Travel News',),
        ('travel_tips', 'Travel Tips',),
        ('things_to_do', 'Things to Do',),
        ('places_to_go', 'Places to Go'),
    )
    image = models.ImageField(blank=True, null=True)
    title = models.CharField(max_length=255)
    categories = models.CharField(max_length=64, choices=CATEGORY_CHOICES, default='travel_news')
    caption = models.CharField(max_length=500)
    content = RichTextUploadingField()

    # todo support for tags
    # tags = models.CharField(max_length=255, default='travel') #todo
    tag = models.ManyToManyField(Tag)
    date_created = models.DateField()

我已经用这样的模型表单覆盖了 Django 管理表单。

class BlogForm(forms.ModelForm):
    CATEGORY_CHOICES = (
        ('travel_news', 'Travel News',),
        ('travel_tips', 'Travel Tips',),
        ('things_to_do', 'Things to Do',),
        ('places_to_go', 'Places to Go'),
    )
    # categories = forms.MultipleChoiceField(choices = CATEGORY_CHOICES)

    class Meta:
        model = BlogPost
        fields = ['author','image', 'title','categories',
                  'caption','content','tag','date_created']

@register(BlogPost)
class BlogPostAdmin(ModelAdmin):


    # autocomplete_fields = ['author']
    def edit(self, obj):
        return format_html('<a class="btn-btn" href="/admin/blogs/blogpost/{}/change/">Change</a>', obj.id)

    def delete(self, obj):
        return format_html('<a class="btn-btn" href="/admin/blogs/blogpost/{}/delete/">Delete</a>', obj.id)

    list_display = ('categories', 'title', 'date_created','edit', 'delete')
    icon_name = 'chrome_reader_mode'
    # inlines = [TagInline]
    form = BlogForm

我写了一个视图,但它没有任何意义,因为没有任何 url 调用它,因为从 django 管理本身保存时我们不需要任何视图和 url。

from apps.blogs.admin import BlogForm
def blogform(request):
    if request.method == 'POST':
        form = BlogForm(request.POST)
        if form.is_valid():
            form.save() 

如果没有这个逻辑,表单也可以正常工作。

我的订阅者模型:

class Subscribers(models.Model):
    email = models.EmailField(unique=True)
    date_subscribed = models.DateField(auto_now_add=True)

    def __str__(self):
        return self.email

    class Meta:
        verbose_name_plural = "Newsletter Subscribers"

【问题讨论】:

  • override save博客方法呢?或者只是给bind to post_save signal
  • 好的,但是在哪里写保存后的信号调用??还有信号发出后逻辑在哪里写??
  • 如果可以解决您的问题,我可以根据信号编写答案。
  • 当然。我将尝试实施它。 @daniherrera
  • 我刚刚发布了一个包含所有元素的示例:博客、博客文章、信号、send_mail、订阅者……

标签: django api django-rest-framework django-forms django-admin


【解决方案1】:

创建新的Post 后,您可以通过以下方式向订阅者发送电子邮件:

from django.db.models.signals import post_save
from django.dispatch import receiver

class BlogPost(models.Model):
    subscribers = models.ManyToManyField(User) # <-- subscribers
    title = models.CharField(max_length=255)

class BlogPost(models.Model):
    blog = models.ForeignKey(Blog, on_delete= models.CASCADE) #<-- blog
    title = models.CharField(max_length=255)
    # ....

# binding sinal:
@receiver(post_save, sender=BlogPost)
def send_mail_to_subs(sender, instance, created, **kwargs):
    if Not created:
        return
        
    for mysub in instance.blog.subscribers.all():
        send_mail(
            f'New Post {instance.title}',
            'Hi .... ',
            'from@example.com',
            [sub.email],
        )

可以根据您的自定义场景随意调整此示例。例如,也许你有其他关系来获得博客或订阅者。

关于性能。

如果您有很多订阅者,这不是一个真正的解决方案。对于大量邮件,您是否需要后台进程来发送邮件。

例如,如果帖子被通知给订阅者,您可以添加一个字段来存储:

class BlogPost(models.Model):
    blog = models.ForeignKey(Blog, on_delete= models.CASCADE) #<-- blog
    title = models.CharField(max_length=255)
    notified = models.BooleanField(editable=False, default=False)

然后每 X 分钟执行一个流程,以获取未通知的帖子并向所有订阅者发送邮件。

你有两个选择:

【讨论】:

  • 如果有 1000 封电子邮件,如果使用 for 循环发送电子邮件会不会很慢? @dani
  • 另外,我有一个名为 Subscribers 的单独模型,其中包含字段 email 和 date_subscribed。
  • 这两个 cmets 包含我在您最初的问题中错过的相关信息。我修改了我的答案以匹配这个新的约束。
  • 您好@dani,最新消息功能已添加到后端。 Blogpost 模型很久以前就已经创建了,现在上面有很多客户数据。我已经用订阅者模型更新了上面的代码。现在我需要从该模型中挑选用户。如何做到这一点而不是将用户字段添加到 Blogpost 模型??
  • 嗨@Django-Rocks,对我来说,这个问题已经得到解答。如果您有新的要求,可以免费发布新的 Q :) 问候!
【解决方案2】:

您可以覆盖模型中的save 方法。


class BlogPost(models.Model):
    author = models.CharField(max_length=64, default='Admin')
    .....
    
    def save(self, *args, **kwargs):
         if self.pk:
             # send mail here
              send_mail()
         return super().save(*args, **kwargs)

或者你也可以写信号。


@receiver(post_save, sender=BlogPost)
def send_mail_to_user(sender, instance, created, **kwargs):
    if created:
       # send mail
         send_mail()

【讨论】:

【解决方案3】:

最好使用信号。如果您需要将其发送给所有订阅者,您需要在信号函数中创建一个循环。

您可以像这样在博客文章视图中创建信号:

@receiver(post_save, sender=BlogPost)
def send_mail_to_subs(sender, instance, created, **kwargs):
    if created:
        for subs in instance.author.subscribed.all():
            send_mail(
                f'New Post from {instance.author}',
                f'Title: {instance.post_title}',
                'youremail',
                [subs.email],
            )

良好的编码:)

【讨论】:

    猜你喜欢
    • 2013-06-14
    • 1970-01-01
    • 2019-01-09
    • 2010-11-27
    • 1970-01-01
    • 2013-04-13
    • 2011-10-02
    • 2012-11-08
    • 2020-05-03
    相关资源
    最近更新 更多