From 6bda7f4f2d7987c1a2b15ecc398b3ba8e4efba7d Mon Sep 17 00:00:00 2001 From: Alek Ratzloff Date: Sun, 19 Jun 2022 22:03:01 -0700 Subject: [PATCH] Big upheaval of how the reports system works * There are now Reports and ReportRecords. * Reports coordinate to what moderators see, and ReportRecords coordinate with the reports that are created by individual users. * Reports keep track of the report reason and the creating user. * ReportRecords keep track of the total weight and whether this report requires urgent attention or not. * ReportRecord keeps track of its own weight and urgency because then we can sort by weight and urgency in the admin view. Signed-off-by: Alek Ratzloff --- board/admin.py | 17 +++++-- board/forms.py | 17 ++++++- board/models.py | 49 ++++++++++++++++--- .../admin/board/reportrecord/change_list.html | 13 +++++ board/views.py | 2 +- 5 files changed, 85 insertions(+), 13 deletions(-) create mode 100644 board/templates/admin/board/reportrecord/change_list.html diff --git a/board/admin.py b/board/admin.py index f4c6a25..691ee72 100644 --- a/board/admin.py +++ b/board/admin.py @@ -1,6 +1,6 @@ from django.contrib import admin from django.utils.safestring import mark_safe -from board.models import Board, Post, ReportReason, Report +from board.models import Board, Post, ReportReason, ReportRecord @admin.register(Board) @@ -18,9 +18,13 @@ class ReportReasonAdmin(admin.ModelAdmin): pass -@admin.register(Report) -class ReportAdmin(admin.ModelAdmin): - readonly_fields = ("post", "reason", "ip") +@admin.register(ReportRecord) +class ReportRecordAdmin(admin.ModelAdmin): + ordering = ( + "-urgent", + "-weight", + ) + readonly_fields = ("post",) list_display = ("post_thumbnail", "post_body") save_as = False @@ -32,7 +36,12 @@ class ReportAdmin(admin.ModelAdmin): def post_body(self, obj): html = "" + if obj.urgent: + html += '
' + else: + html += "
" if obj.post.subject: html += f"{obj.post.subject}" html += f"

{obj.post.text}

" + html += "
" return mark_safe(html) diff --git a/board/forms.py b/board/forms.py index b04d735..0a0b7b3 100644 --- a/board/forms.py +++ b/board/forms.py @@ -1,6 +1,7 @@ from django.conf import settings +from django.db import transaction from django.forms import ModelForm -from board.models import Post, Report +from board.models import Post, Report, ReportRecord from hcaptcha.fields import hCaptchaField @@ -50,6 +51,8 @@ class ReportForm(ModelForm): This requires the board and the IP address to be specified. """ + # uses report_form.html + class Meta: model = Report fields = ["reason"] @@ -57,4 +60,14 @@ class ReportForm(ModelForm): def __init__(self, *args, op, board, ip, **kwargs): super(ReportForm, self).__init__(*args, **kwargs) self.instance.ip = ip - self.instance.post = op + self.op = op + + def clean(self): + # Get or create the record before creating the model + with transaction.atomic(): + try: + record = ReportRecord.objects.get(post=self.op) + except ReportRecord.DoesNotExist: + record = ReportRecord.objects.create(post=self.op) + self.instance.record = record + return super(ReportForm, self).clean() diff --git a/board/models.py b/board/models.py index 371963d..8dd3757 100644 --- a/board/models.py +++ b/board/models.py @@ -1,11 +1,11 @@ from datetime import timedelta import os from pathlib import Path -from django.db import models -from django.db.models import signals from django.conf import settings from django.core.exceptions import ValidationError from django.core.files.base import ContentFile +from django.db import models +from django.db.models import signals from django.dispatch import receiver from django.urls import reverse from django.utils import timezone @@ -218,15 +218,51 @@ class ReportReason(models.Model): return self.reason -class Report(models.Model): +class ReportRecord(models.Model): # Post that this report is for - post = models.ForeignKey("Post", on_delete=models.CASCADE) + post = models.OneToOneField("Post", on_delete=models.CASCADE) + # Report weight + weight = models.IntegerField(default=0) + # If this report is urgent or not + urgent = models.BooleanField(default=False) + + # def urgent(self) -> bool: + # return self.report_set().aggregate( + + +class Report(models.Model): + """ + There are two reasons why this model exists: + + 1. In the report view in the Django admin, there is probably not a good way + to specially modify the list of reports. Since we don't want to see every + single report, we need a way to limit this *in data* rather than in code. + We accomplish this by having: + - Report model, which is all reports unique to users. + - ReportRecord model, which is the collection of all reports unique to posts. + 2. We want to keep a log of all reports created by users, in case we think + they are abusing the system. + """ + + # The report that was made by this user + record = models.ForeignKey("ReportRecord", on_delete=models.CASCADE) # Reason for this report reason = models.ForeignKey("ReportReason", on_delete=models.SET_NULL, null=True) # IP address of the reporter ip = models.GenericIPAddressField() +@receiver(signals.post_save, sender=Report) +def report_created(sender, instance, created, **kwargs): + if created: + # get the total weight, this is probably going to be more reliable than + # adding the weight every time + weights = [report.reason.weight for report in instance.record.report_set.all()] + instance.record.weight = sum(weights) + instance.record.urgent |= instance.reason.urgent + instance.record.save() + + class Ban(models.Model): # IP address of the reporter ip = models.GenericIPAddressField() @@ -239,7 +275,7 @@ class Ban(models.Model): # The time that this ban was created. created = models.DateTimeField(auto_now_add=True) # Expiration date of this ban. - expires = models.DateTimeField(auto_now_add=True) + expires = models.DateTimeField() class BanTemplate(models.Model): @@ -249,4 +285,5 @@ class BanTemplate(models.Model): duration = models.DurationField() def create_ban(self, ip: str) -> Ban: - return Ban.objects.create(ip=ip, ban_reason=self.ban_reason) + expires = timezone.now() + self.duration + return Ban.objects.create(ip=ip, ban_reason=self.ban_reason, expires=expires) diff --git a/board/templates/admin/board/reportrecord/change_list.html b/board/templates/admin/board/reportrecord/change_list.html new file mode 100644 index 0000000..e7c073c --- /dev/null +++ b/board/templates/admin/board/reportrecord/change_list.html @@ -0,0 +1,13 @@ +{% extends "admin/change_list.html" %} +{% block extrastyle %} +{{ block.super }} + +{% endblock extrastyle %} \ No newline at end of file diff --git a/board/views.py b/board/views.py index e829d81..a42b856 100644 --- a/board/views.py +++ b/board/views.py @@ -4,7 +4,7 @@ from django.shortcuts import render, get_object_or_404 from django.views.generic import DetailView from django.views.generic.edit import CreateView from django.urls import reverse, reverse_lazy -from board.models import Post, Board, Report +from board.models import Post, Board, Report, ReportRecord from board.forms import PostForm, ReplyForm, ReportForm __all__ = ("BoardView", "PostView", "ReportView")