diff --git a/articles/admin.py b/articles/admin.py index 0b151a2..9089785 100644 --- a/articles/admin.py +++ b/articles/admin.py @@ -116,7 +116,15 @@ class PageAdmin(ArticleAdmin): @register(Comment) 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",) search_fields = ("username", "email", "content") actions = ["approve_comments", "reject_comments"] diff --git a/articles/management/commands/notify_commenters.py b/articles/management/commands/notify_commenters.py new file mode 100644 index 0000000..2af7644 --- /dev/null +++ b/articles/management/commands/notify_commenters.py @@ -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) diff --git a/articles/migrations/0016_comment_user_notified.py b/articles/migrations/0016_comment_user_notified.py new file mode 100644 index 0000000..ea0c110 --- /dev/null +++ b/articles/migrations/0016_comment_user_notified.py @@ -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), + ), + ] diff --git a/articles/migrations/0017_auto_20200820_1606.py b/articles/migrations/0017_auto_20200820_1606.py new file mode 100644 index 0000000..16bd5b7 --- /dev/null +++ b/articles/migrations/0017_auto_20200820_1606.py @@ -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"]}, + ), + ] diff --git a/articles/models.py b/articles/models.py index 2788d4a..f0687cb 100644 --- a/articles/models.py +++ b/articles/models.py @@ -1,9 +1,11 @@ import re import markdown +from django.conf import settings from django.contrib.auth.models import AbstractUser from django.contrib.contenttypes.models import ContentType from django.db import models +from django.http import HttpRequest from django.template.defaultfilters import slugify from django.urls import reverse from django.utils import timezone @@ -64,6 +66,13 @@ class Article(AdminUrlMixin, models.Model): def get_absolute_url(self): 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): html = self.get_formatted_content() return html.split("")[0] @@ -130,12 +139,16 @@ class Comment(AdminUrlMixin, models.Model): ) created_at = models.DateTimeField(auto_now_add=True) status = models.CharField(max_length=10, choices=STATUS_CHOICES, default=PENDING) + user_notified = models.BooleanField(default=False) class Meta: - ordering = ["created_at"] + ordering = ["-created_at"] def __str__(self): return f"{self.username} - {self.content[:50]}" def get_absolute_url(self): 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) diff --git a/articles/templates/articles/comments_notification_email.txt b/articles/templates/articles/comments_notification_email.txt new file mode 100644 index 0000000..4fa8c8a --- /dev/null +++ b/articles/templates/articles/comments_notification_email.txt @@ -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 diff --git a/blog/settings.py b/blog/settings.py index 08e0cb2..8fc3b40 100644 --- a/blog/settings.py +++ b/blog/settings.py @@ -162,6 +162,7 @@ else: AUTH_USER_MODEL = "articles.User" BLOG = { + "title": "Gab's Notes", "description": "My take on tech-related subjects (but not only)", "base_url": os.getenv("BLOG_BASE_URL", "https://gabnotes.org/"), }