Add task to notify commenters once their comment has been moderated.
This commit is contained in:
parent
23985a3263
commit
f6d6d7b850
7 changed files with 132 additions and 2 deletions
|
@ -116,7 +116,15 @@ class PageAdmin(ArticleAdmin):
|
||||||
|
|
||||||
@register(Comment)
|
@register(Comment)
|
||||||
class CommentAdmin(admin.ModelAdmin):
|
class CommentAdmin(admin.ModelAdmin):
|
||||||
list_display = ("username", "email", "content", "article", "created_at", "status")
|
list_display = (
|
||||||
|
"username",
|
||||||
|
"email",
|
||||||
|
"content",
|
||||||
|
"article",
|
||||||
|
"created_at",
|
||||||
|
"status",
|
||||||
|
"user_notified",
|
||||||
|
)
|
||||||
list_filter = ("status",)
|
list_filter = ("status",)
|
||||||
search_fields = ("username", "email", "content")
|
search_fields = ("username", "email", "content")
|
||||||
actions = ["approve_comments", "reject_comments"]
|
actions = ["approve_comments", "reject_comments"]
|
||||||
|
|
54
articles/management/commands/notify_commenters.py
Normal file
54
articles/management/commands/notify_commenters.py
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.mail import mail_admins, send_mass_mail
|
||||||
|
from django.core.management import BaseCommand
|
||||||
|
from django.db.models import Q
|
||||||
|
from django.template import Context, Engine
|
||||||
|
from django.template.loader import render_to_string
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils.translation import ngettext
|
||||||
|
|
||||||
|
from articles.models import Comment
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = "Check for pending comments and send an email to the admin."
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
to_notify = (
|
||||||
|
Comment.objects.filter(
|
||||||
|
Q(status=Comment.APPROVED) | Q(status=Comment.REJECTED),
|
||||||
|
user_notified=False,
|
||||||
|
)
|
||||||
|
.exclude(email=None)
|
||||||
|
.exclude(email="")
|
||||||
|
)
|
||||||
|
by_email = {}
|
||||||
|
for comment in to_notify:
|
||||||
|
if comment.email not in by_email:
|
||||||
|
by_email[comment.email] = {"approved": [], "rejected": []}
|
||||||
|
if comment.status == Comment.APPROVED:
|
||||||
|
by_email[comment.email]["approved"].append(comment)
|
||||||
|
elif comment.status == Comment.REJECTED:
|
||||||
|
by_email[comment.email]["rejected"].append(comment)
|
||||||
|
|
||||||
|
email_data = []
|
||||||
|
for email, comments in by_email.items():
|
||||||
|
approved = comments["approved"]
|
||||||
|
rejected = comments["rejected"]
|
||||||
|
subject = ngettext(
|
||||||
|
"Your comment has been moderated.",
|
||||||
|
"Your comments have been moderated.",
|
||||||
|
len(approved) + len(rejected),
|
||||||
|
)
|
||||||
|
blog_title = settings.BLOG["title"]
|
||||||
|
message = render_to_string(
|
||||||
|
"articles/comments_notification_email.txt",
|
||||||
|
{"approved": approved, "rejected": rejected, "blog_title": blog_title},
|
||||||
|
).replace("'", "'")
|
||||||
|
from_email = settings.DEFAULT_FROM_EMAIL
|
||||||
|
recipient_list = [email]
|
||||||
|
email_data.append((subject, message, from_email, recipient_list))
|
||||||
|
send_mass_mail(tuple(email_data))
|
||||||
|
to_notify.update(user_notified=True)
|
18
articles/migrations/0016_comment_user_notified.py
Normal file
18
articles/migrations/0016_comment_user_notified.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 3.1 on 2020-08-20 13:42
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("articles", "0015_auto_20200818_2138"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="comment",
|
||||||
|
name="user_notified",
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
]
|
16
articles/migrations/0017_auto_20200820_1606.py
Normal file
16
articles/migrations/0017_auto_20200820_1606.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# Generated by Django 3.1 on 2020-08-20 14:06
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("articles", "0016_comment_user_notified"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name="comment", options={"ordering": ["-created_at"]},
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,9 +1,11 @@
|
||||||
import re
|
import re
|
||||||
|
|
||||||
import markdown
|
import markdown
|
||||||
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import AbstractUser
|
from django.contrib.auth.models import AbstractUser
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.http import HttpRequest
|
||||||
from django.template.defaultfilters import slugify
|
from django.template.defaultfilters import slugify
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
@ -64,6 +66,13 @@ class Article(AdminUrlMixin, models.Model):
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse("article-detail", kwargs={"slug": self.slug})
|
return reverse("article-detail", kwargs={"slug": self.slug})
|
||||||
|
|
||||||
|
def get_full_absolute_url(self, request: HttpRequest = None):
|
||||||
|
url = self.get_absolute_url()
|
||||||
|
if request:
|
||||||
|
return request.build_absolute_uri(url)
|
||||||
|
else:
|
||||||
|
return (settings.BLOG["base_url"] + url).replace("//", "/")
|
||||||
|
|
||||||
def get_abstract(self):
|
def get_abstract(self):
|
||||||
html = self.get_formatted_content()
|
html = self.get_formatted_content()
|
||||||
return html.split("<!--more-->")[0]
|
return html.split("<!--more-->")[0]
|
||||||
|
@ -130,12 +139,16 @@ class Comment(AdminUrlMixin, models.Model):
|
||||||
)
|
)
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default=PENDING)
|
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default=PENDING)
|
||||||
|
user_notified = models.BooleanField(default=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ["created_at"]
|
ordering = ["-created_at"]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.username} - {self.content[:50]}"
|
return f"{self.username} - {self.content[:50]}"
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return self.article.get_absolute_url() + "#" + str(self.id)
|
return self.article.get_absolute_url() + "#" + str(self.id)
|
||||||
|
|
||||||
|
def get_full_absolute_url(self, request: HttpRequest = None):
|
||||||
|
return self.article.get_full_absolute_url(request) + "#" + str(self.id)
|
||||||
|
|
20
articles/templates/articles/comments_notification_email.txt
Normal file
20
articles/templates/articles/comments_notification_email.txt
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
Hello,
|
||||||
|
|
||||||
|
This is a quick (automated) notification about the comments you left
|
||||||
|
on {{ blog_title }}.
|
||||||
|
|
||||||
|
{% if approved %}Approved:
|
||||||
|
{% for comment in approved %}* {{ comment.get_full_absolute_url }}
|
||||||
|
{% endfor %}{% endif %}
|
||||||
|
|
||||||
|
{% if rejected %}Rejected:
|
||||||
|
{% for comment in rejected %}* #{{comment.id}} on {{ comment.article.get_full_absolute_url }}:
|
||||||
|
{{ comment.content }}
|
||||||
|
|
||||||
|
{% endfor %}{% endif %}
|
||||||
|
|
||||||
|
You received this notification because you left your email address in the
|
||||||
|
comment form.
|
||||||
|
|
||||||
|
Cheers,
|
||||||
|
Gabriel
|
|
@ -162,6 +162,7 @@ else:
|
||||||
AUTH_USER_MODEL = "articles.User"
|
AUTH_USER_MODEL = "articles.User"
|
||||||
|
|
||||||
BLOG = {
|
BLOG = {
|
||||||
|
"title": "Gab's Notes",
|
||||||
"description": "My take on tech-related subjects (but not only)",
|
"description": "My take on tech-related subjects (but not only)",
|
||||||
"base_url": os.getenv("BLOG_BASE_URL", "https://gabnotes.org/"),
|
"base_url": os.getenv("BLOG_BASE_URL", "https://gabnotes.org/"),
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue