Add the ability to include moderator notes on feeds.
authorDave Page <dpage@pgadmin.org>
Tue, 19 Mar 2024 14:52:24 +0000 (14:52 +0000)
committerDave Page <dpage@pgadmin.org>
Tue, 19 Mar 2024 14:52:24 +0000 (14:52 +0000)
hamnadmin/hamnadmin/register/admin.py
hamnadmin/hamnadmin/register/migrations/0007_moderatornotes.py [new file with mode: 0644]
hamnadmin/hamnadmin/register/models.py
hamnadmin/hamnadmin/register/templates/edit.html
hamnadmin/hamnadmin/register/templates/moderate.html
hamnadmin/hamnadmin/register/views.py

index 7ca4a1ffe506266612d6616b72329ccd8f84ba0c..7dde1007497d4398ec15f4274529b52ee3070bb1 100644 (file)
@@ -1,6 +1,6 @@
 from django.contrib import admin
 
-from hamnadmin.register.models import Blog, Team, Post, AggregatorLog
+from hamnadmin.register.models import Blog, Team, Post, AggregatorLog, ModeratorNotes
 
 
 class TeamAdmin(admin.ModelAdmin):
@@ -29,7 +29,12 @@ class AggregatorLogAdmin(admin.ModelAdmin):
     list_display = ['ts', 'success', 'feed', 'info']
 
 
+class ModeratorNotesAdmin(admin.ModelAdmin):
+    list_display = ['ts', 'user', 'feed', 'note']
+
+
 admin.site.register(Team, TeamAdmin)
 admin.site.register(Blog, BlogAdmin)
 admin.site.register(Post, PostAdmin)
 admin.site.register(AggregatorLog, AggregatorLogAdmin)
+admin.site.register(ModeratorNotes, ModeratorNotesAdmin)
diff --git a/hamnadmin/hamnadmin/register/migrations/0007_moderatornotes.py b/hamnadmin/hamnadmin/register/migrations/0007_moderatornotes.py
new file mode 100644 (file)
index 0000000..cbc28a8
--- /dev/null
@@ -0,0 +1,31 @@
+# Generated by Django 3.2.16 on 2024-03-19 14:51
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+        ('register', '0006_blog_lastsuccess'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='ModeratorNotes',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('ts', models.DateTimeField(auto_now=True)),
+                ('note', models.TextField()),
+                ('feed', models.ForeignKey(db_column='feed', on_delete=django.db.models.deletion.CASCADE, to='register.blog')),
+                ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+            ],
+            options={
+                'verbose_name_plural': 'Moderator notes',
+                'db_table': 'moderatornotes',
+                'ordering': ['-ts'],
+            },
+        ),
+    ]
index 1ce886daf9b60c9c89e4588a2e437e078e64ca69..a06c7a0ec7149c7ceaf2bcc4a2578cd473f008d3 100644 (file)
@@ -136,3 +136,18 @@ class AggregatorLog(models.Model):
 
     def __str__(self):
         return "Log entry for %s (%s)" % (self.feed.name, self.ts)
+
+
+class ModeratorNotes(models.Model):
+    ts = models.DateTimeField(auto_now=True)
+    user = models.ForeignKey(User, null=False, blank=False, on_delete=models.CASCADE)
+    feed = models.ForeignKey(Blog, null=False, blank=False, db_column='feed', on_delete=models.CASCADE)
+    note = models.TextField()
+
+    class Meta:
+        db_table = 'moderatornotes'
+        verbose_name_plural = 'Moderator notes'
+        ordering = ['-ts']
+
+    def __str__(self):
+        return "Note for %s by %s at %s" % (self.feed.name, self.user, self.ts)
index 190dbd2523a78428574ae87514411398dd8aaa45..d39bc20fed48e3699ee4deb25edcce37d9e36729 100644 (file)
                         automatically submitted for approval.
                     {% endif %}{#has_entries#}
                 {% endif %}{#approved#}
+            </p>
         {% endif %}{#not new#}
-    </p>
+
+    {% if user.is_superuser %}
+        <h3>Moderator Notes</h3>
+        {% if notes %}
+        <table class="table table-condensed table-striped">
+            <tr>
+                <th style="width: 60%">Note</th>
+                <th style="width: 20%">Moderator</th>
+                <th style="width: 20%">Date/Time</th>
+            </tr>
+            {% for n in notes %}
+                <tr>
+                    <td>{{ n.note }}</td>
+                    <td>{{ n.user }}</td>
+                    <td>{{ n.ts|date:"Y-m-d H:i:s" }}</td>
+                </tr>
+            {% endfor %}
+        </table>
+        {% endif %}
+
+        <form method="post" action=".">{% csrf_token %}
+            <div class="form-row align-items-center">
+                <div class="col-auto" style="width: 60%">
+                    <label for="id_note" class="sr-only">New note</label>
+                    <input type="text" name="note" class="form-control mb-2" id="id_note">
+                </div>
+                <div class="col-auto" style="width: 40%">
+                    <button type="submit" class="btn btn-primary mb-2">Add Note</button>
+                </div>
+            </div>
+        </form>
+    {% endif %}
 
     {% if messages %}
         <h3>Results</h3>
index 4dbe38a15e1a6569d24e66dbbaf53eca02235eee..1efb5820c67d767e1592fe3c17b0a4ea15550a1d 100644 (file)
                             {% if blog.authorfilter %}<br/>Author filter: {{ blog.authorfilter }}{% endif %}<br/>&nbsp;
                         </div>
                     </div>
+
+                    {% if user.is_superuser and blog.moderatornotes_set.all %}
+
+                    <div class="row">
+                        <div class="col-sm-1"><strong>Moderator notes</strong></div>
+                        <div class="col-sm-10">
+                            <table class="table table-condensed table-striped table-bordered">
+                                <tr><th>Notes</th><th>Moderator</th><th>Date/Time</th></tr>
+                                {% for n in blog.moderatornotes_set.all %}
+                                    <tr>
+                                        <td style="width: 60%">{{ n.note }}</td>
+                                        <td style="width: 20%">{{ n.user }}</td>
+                                        <td style="width: 20%">{{ n.ts|date:"Y-m-d H:i:s" }}</td>
+                                    </tr>
+                                {% endfor %}
+                            </table>
+                        </div>
+                    </div>
+                    {% endif %}
+
                     <div class="row">
                         <div class="col-sm-1"><strong>Posts</strong></div>
                         <div class="col-sm-10">
index 299be79ff22348110ce256da0e786e036b44f0c0..f5d38af0a9794140a20dc294f49a05177d6af7b9 100644 (file)
@@ -6,7 +6,7 @@ from django.db import transaction
 from django.db.models import Count, Max, Q, Subquery, OuterRef, Exists, FilteredRelation
 from django.contrib import messages
 
-from hamnadmin.register.models import Post, Blog, Team, AggregatorLog, AuditEntry
+from hamnadmin.register.models import Post, Blog, Team, AggregatorLog, AuditEntry, ModeratorNotes
 from hamnadmin.mailqueue.util import send_simple_mail
 from hamnadmin.util.varnish import purge_url, purge_xkey, purge_root_and_feeds
 
@@ -87,51 +87,56 @@ def edit(request, id=None):
         blog = Blog(user=request.user, name="{0} {1}".format(request.user.first_name, request.user.last_name))
 
     if request.method == 'POST':
-        saved_url = blog.feedurl
-        saved_filter = blog.authorfilter
-        saved_team = blog.team
-        form = BlogEditForm(request, data=request.POST, instance=blog)
-        if form.is_valid():
-            if id:
-                # This is an existing one. If we change the URL of the blog, it needs to be
-                # de-moderated if it was previously approved.
-                if blog.approved:
-                    if saved_url != form.cleaned_data['feedurl'] or saved_filter != form.cleaned_data['authorfilter']:
-                        obj = form.save()
-                        obj.approved = False
-                        obj.save(update_fields=['approved'])
-
-                        send_simple_mail(
-                            settings.EMAIL_SENDER,
-                            settings.NOTIFICATION_RECEIVER,
-                            "A blog was edited on Planet PostgreSQL",
-                            "The blog at {0}\nwas edited by {1} in a way that needs new moderation.\n\nTo moderate: https://planet.postgresql.org/register/moderate/\n\n".format(blog.feedurl, blog.user),
-                            sendername="Planet PostgreSQL",
-                            receivername="Planet PostgreSQL Moderators",
-                        )
-
-                        messages.warning(request, "Blog has been resubmitted for moderation, and is temporarily disabled.")
-
-                        purge_root_and_feeds()
-                        purge_url('/feeds.html')
-
-                        return HttpResponseRedirect("/register/edit/{0}/".format(obj.id))
-
-            obj = form.save()
-
-            if obj.team and obj.team != saved_team:
-                # We allow anybody to join a team by default, and will just send a notice
-                # so the team manager can undo it.
-                send_simple_mail(settings.EMAIL_SENDER,
-                                 obj.team.manager.email,
-                                 "A blog joined your team on Planet PostgreSQL",
-                                 "The blog at {0} by {1} {2}\nhas been added to your team {3} on Planet PostgreSQL\n\nIf this is correct, you do not need to do anything.\n\nIf this is incorrect, please go to\n\nhttps://planet.postgresql.org/register/\n\nand click the button to remove the blog from your team.\nWe apologize if this causes work for you.\n\n".format(
-                                     obj.feedurl,
-                                     obj.user.first_name, obj.user.last_name,
-                                     obj.team.name),
-                                 sendername="Planet PostgreSQL",
-                                 receivername="{0} {1}".format(obj.team.manager.first_name, obj.team.manager.last_name),
-                                 )
+        if 'note' in request.POST:
+            form = BlogEditForm(request, instance=blog)
+            note = ModeratorNotes(feed=blog, note=request.POST['note'], user=request.user)
+            note.save()
+        else:
+            saved_url = blog.feedurl
+            saved_filter = blog.authorfilter
+            saved_team = blog.team
+            form = BlogEditForm(request, data=request.POST, instance=blog)
+            if form.is_valid():
+                if id:
+                    # This is an existing one. If we change the URL of the blog, it needs to be
+                    # de-moderated if it was previously approved.
+                    if blog.approved:
+                        if saved_url != form.cleaned_data['feedurl'] or saved_filter != form.cleaned_data['authorfilter']:
+                            obj = form.save()
+                            obj.approved = False
+                            obj.save(update_fields=['approved'])
+
+                            send_simple_mail(
+                                settings.EMAIL_SENDER,
+                                settings.NOTIFICATION_RECEIVER,
+                                "A blog was edited on Planet PostgreSQL",
+                                "The blog at {0}\nwas edited by {1} in a way that needs new moderation.\n\nTo moderate: https://planet.postgresql.org/register/moderate/\n\n".format(blog.feedurl, blog.user),
+                                sendername="Planet PostgreSQL",
+                                receivername="Planet PostgreSQL Moderators",
+                            )
+
+                            messages.warning(request, "Blog has been resubmitted for moderation, and is temporarily disabled.")
+
+                            purge_root_and_feeds()
+                            purge_url('/feeds.html')
+
+                            return HttpResponseRedirect("/register/edit/{0}/".format(obj.id))
+
+                obj = form.save()
+
+                if obj.team and obj.team != saved_team:
+                    # We allow anybody to join a team by default, and will just send a notice
+                    # so the team manager can undo it.
+                    send_simple_mail(settings.EMAIL_SENDER,
+                                     obj.team.manager.email,
+                                     "A blog joined your team on Planet PostgreSQL",
+                                     "The blog at {0} by {1} {2}\nhas been added to your team {3} on Planet PostgreSQL\n\nIf this is correct, you do not need to do anything.\n\nIf this is incorrect, please go to\n\nhttps://planet.postgresql.org/register/\n\nand click the button to remove the blog from your team.\nWe apologize if this causes work for you.\n\n".format(
+                                         obj.feedurl,
+                                         obj.user.first_name, obj.user.last_name,
+                                         obj.team.name),
+                                     sendername="Planet PostgreSQL",
+                                     receivername="{0} {1}".format(obj.team.manager.first_name, obj.team.manager.last_name),
+                                     )
 
             return HttpResponseRedirect("/register/edit/{0}/".format(obj.id))
     else:
@@ -142,6 +147,7 @@ def edit(request, id=None):
         'form': form,
         'blog': blog,
         'log': AggregatorLog.objects.filter(feed=blog).order_by('-ts')[:30],
+        'notes': ModeratorNotes.objects.filter(feed=blog).order_by('-ts'),
         'posts': Post.objects.filter(feed=blog).order_by('-dat')[:10],
         'title': 'Edit blog: %s' % blog.name,
     })