From ab57b5b906641df96be76fee71186d8eb4b51896 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 8 Mar 2021 15:49:44 -0800 Subject: [PATCH 01/19] First pass at views for reporting --- bookwyrm/templates/layout.html | 4 +- bookwyrm/templates/settings/admin_layout.html | 4 ++ .../templates/settings/report_preview.html | 14 +++++++ bookwyrm/templates/settings/reports.html | 27 +++++++++++++ .../snippets/status/status_options.html | 5 +++ bookwyrm/templates/snippets/user_options.html | 3 ++ bookwyrm/urls.py | 7 ++++ bookwyrm/views/__init__.py | 3 +- bookwyrm/views/reports.py | 38 +++++++++++++++++++ 9 files changed, 102 insertions(+), 3 deletions(-) create mode 100644 bookwyrm/templates/settings/report_preview.html create mode 100644 bookwyrm/templates/settings/reports.html create mode 100644 bookwyrm/views/reports.py diff --git a/bookwyrm/templates/layout.html b/bookwyrm/templates/layout.html index 901a12ff5..fc2ebdb74 100644 --- a/bookwyrm/templates/layout.html +++ b/bookwyrm/templates/layout.html @@ -114,8 +114,8 @@ {% endif %} {% if perms.bookwyrm.edit_instance_settings %}
  • - - {% trans 'Site Configuration' %} + + {% trans 'Admin' %}
  • {% endif %} diff --git a/bookwyrm/templates/settings/admin_layout.html b/bookwyrm/templates/settings/admin_layout.html index 16741436c..312d502a7 100644 --- a/bookwyrm/templates/settings/admin_layout.html +++ b/bookwyrm/templates/settings/admin_layout.html @@ -18,6 +18,10 @@ {% url 'settings-invites' as url %} {% trans "Invites" %} +
  • + {% url 'settings-reports' as url %} + {% trans "Reports" %} +
  • {% url 'settings-federation' as url %} {% trans "Federated Servers" %} diff --git a/bookwyrm/templates/settings/report_preview.html b/bookwyrm/templates/settings/report_preview.html new file mode 100644 index 000000000..67bffe275 --- /dev/null +++ b/bookwyrm/templates/settings/report_preview.html @@ -0,0 +1,14 @@ +{% extends 'components/card.html' %} +{% load i18n %} +{% block card-header %} +

    + report title

    +{% endblock %} + +{% block card-content %} +about this report +{% endblock %} + +{% block card-footer %} +footer +{% endblock diff --git a/bookwyrm/templates/settings/reports.html b/bookwyrm/templates/settings/reports.html new file mode 100644 index 000000000..efca7244b --- /dev/null +++ b/bookwyrm/templates/settings/reports.html @@ -0,0 +1,27 @@ +{% extends 'settings/admin_layout.html' %} +{% load i18n %} + +{% block header %}{% trans "Reports" %}{% endblock %} + +{% block panel %} + + + + +{% endblock %} + diff --git a/bookwyrm/templates/snippets/status/status_options.html b/bookwyrm/templates/snippets/status/status_options.html index babd82961..0f099e177 100644 --- a/bookwyrm/templates/snippets/status/status_options.html +++ b/bookwyrm/templates/snippets/status/status_options.html @@ -10,6 +10,7 @@ {% block dropdown-list %} {% if status.user == request.user %} +{# things you can do to your own statuses #}
  • {% else %} +{# things you can do to other people's statuses #}
  • {% trans "Send direct message" %}
  • +
  • + {% trans "Report status" %} +
  • {% include 'snippets/block_button.html' with user=status.user class="is-fullwidth" %}
  • diff --git a/bookwyrm/templates/snippets/user_options.html b/bookwyrm/templates/snippets/user_options.html index b3f667823..c9fdac0a4 100644 --- a/bookwyrm/templates/snippets/user_options.html +++ b/bookwyrm/templates/snippets/user_options.html @@ -12,6 +12,9 @@
  • {% trans "Send direct message" %}
  • +
  • + {% trans "Report user" %} +
  • {% include 'snippets/block_button.html' with user=user class="is-fullwidth" %}
  • diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index 844f89937..c7ec7f4b7 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -54,6 +54,13 @@ urlpatterns = [ re_path( r"^settings/invites/?$", views.ManageInvites.as_view(), name="settings-invites" ), + # moderation + re_path(r"^settings/reports/?$", views.Reports.as_view(), name="settings-reports"), + re_path( + r"^settings/report/(?P\d+)/?$", + views.Report.as_view(), + name="settings-report", + ), re_path(r"^invite/(?P[A-Za-z0-9]+)/?$", views.Invite.as_view()), # landing pages re_path(r"^about/?$", views.About.as_view()), diff --git a/bookwyrm/views/__init__.py b/bookwyrm/views/__init__.py index 48da8ec1a..36a64ddfe 100644 --- a/bookwyrm/views/__init__.py +++ b/bookwyrm/views/__init__.py @@ -20,15 +20,16 @@ from .notifications import Notifications from .outbox import Outbox from .reading import edit_readthrough, create_readthrough, delete_readthrough from .reading import start_reading, finish_reading, delete_progressupdate +from .reports import Report, Reports from .rss_feed import RssFeed from .password import PasswordResetRequest, PasswordReset, ChangePassword -from .tag import Tag, AddTag, RemoveTag from .search import Search from .shelf import Shelf from .shelf import user_shelves_page, create_shelf, delete_shelf from .shelf import shelve, unshelve from .site import Site from .status import CreateStatus, DeleteStatus +from .tag import Tag, AddTag, RemoveTag from .updates import Updates from .user import User, EditUser, Followers, Following from .isbn import Isbn diff --git a/bookwyrm/views/reports.py b/bookwyrm/views/reports.py new file mode 100644 index 000000000..a90fffaf4 --- /dev/null +++ b/bookwyrm/views/reports.py @@ -0,0 +1,38 @@ +""" moderation via flagged posts and users """ +from django.contrib.auth.decorators import login_required, permission_required +from django.shortcuts import get_object_or_404 +from django.template.response import TemplateResponse +from django.utils.decorators import method_decorator +from django.views import View + +from bookwyrm import models + + +# pylint: disable= no-self-use +@method_decorator(login_required, name="dispatch") +@method_decorator( + permission_required("bookwyrm.moderate_user", raise_exception=True), + name="dispatch", +) +@method_decorator( + permission_required("bookwyrm.moderate_post", raise_exception=True), + name="dispatch", +) +class Reports(View): + """ list of reports """ + + def get(self, request, status="open"): + """ view current reports """ + data = { + "status": status + } # {"reports": models.Report.objects.filter(status=status)} + return TemplateResponse(request, "settings/reports.html", data) + + +class Report(View): + """ view a specific report """ + + def get(self, request, report_id): + """ load a report """ + data = {"report": get_object_or_404(models.REport, id=report_id)} + return TemplateResponse(request, "settings/report.html", data) From 7337a357fa676459634cab18c47b9c3a2fd6690c Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 8 Mar 2021 16:02:16 -0800 Subject: [PATCH 02/19] Adds tests file --- bookwyrm/tests/views/test_reports.py | 48 ++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 bookwyrm/tests/views/test_reports.py diff --git a/bookwyrm/tests/views/test_reports.py b/bookwyrm/tests/views/test_reports.py new file mode 100644 index 000000000..3b8dec260 --- /dev/null +++ b/bookwyrm/tests/views/test_reports.py @@ -0,0 +1,48 @@ +""" test for app action functionality """ +from django.template.response import TemplateResponse +from django.test import TestCase +from django.test.client import RequestFactory + +from bookwyrm import models +from bookwyrm import views + + +class ReportViews(TestCase): + """ every response to a get request, html or json """ + + def setUp(self): + """ we need basic test data and mocks """ + self.factory = RequestFactory() + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.mouse", + "password", + local=True, + localname="mouse", + ) + models.SiteSettings.objects.create() + + def test_reports_page(self): + """ there are so many views, this just makes sure it LOADS """ + view = views.Reports.as_view() + request = self.factory.get("") + request.user = self.local_user + request.user.is_superuser = True + result = view(request) + self.assertIsInstance(result, TemplateResponse) + result.render() + self.assertEqual(result.status_code, 200) + + def test_report_page(self): + """ there are so many views, this just makes sure it LOADS """ + view = views.Report.as_view() + request = self.factory.get("") + request.user = self.local_user + request.user.is_superuser = True + report = models.Report.objects.create() + + result = view(request, report.id) + + self.assertIsInstance(result, TemplateResponse) + result.render() + self.assertEqual(result.status_code, 200) From e59c12768642a9ebaa810a138972eebf6098c6b7 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 8 Mar 2021 17:16:59 -0800 Subject: [PATCH 03/19] Adds models for reporting --- .gitignore | 3 +- .../migrations/0049_report_reportcomment.py | 48 +++++++++++++++++++ bookwyrm/models/__init__.py | 1 + bookwyrm/models/federated_server.py | 2 +- bookwyrm/models/report.py | 23 +++++++++ bookwyrm/tests/views/test_reports.py | 9 +++- bookwyrm/views/reports.py | 12 +++-- 7 files changed, 90 insertions(+), 8 deletions(-) create mode 100644 bookwyrm/migrations/0049_report_reportcomment.py create mode 100644 bookwyrm/models/report.py diff --git a/.gitignore b/.gitignore index 1384056f2..4b5b7fef2 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ /venv *.pyc *.swp +**/__pycache__ # VSCode /.vscode @@ -15,4 +16,4 @@ /images/ # Testing -.coverage \ No newline at end of file +.coverage diff --git a/bookwyrm/migrations/0049_report_reportcomment.py b/bookwyrm/migrations/0049_report_reportcomment.py new file mode 100644 index 000000000..deb8ba6f6 --- /dev/null +++ b/bookwyrm/migrations/0049_report_reportcomment.py @@ -0,0 +1,48 @@ +# Generated by Django 3.0.7 on 2021-03-09 00:55 + +import bookwyrm.models.fields +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('bookwyrm', '0048_merge_20210308_1754'), + ] + + operations = [ + migrations.CreateModel( + name='Report', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_date', models.DateTimeField(auto_now_add=True)), + ('updated_date', models.DateTimeField(auto_now=True)), + ('remote_id', bookwyrm.models.fields.RemoteIdField(max_length=255, null=True, validators=[bookwyrm.models.fields.validate_remote_id])), + ('note', models.TextField(blank=True, null=True)), + ('resolved', models.BooleanField(default=False)), + ('reporter', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='reporter', to=settings.AUTH_USER_MODEL)), + ('statuses', models.ManyToManyField(to='bookwyrm.Status')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='ReportComment', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_date', models.DateTimeField(auto_now_add=True)), + ('updated_date', models.DateTimeField(auto_now=True)), + ('remote_id', bookwyrm.models.fields.RemoteIdField(max_length=255, null=True, validators=[bookwyrm.models.fields.validate_remote_id])), + ('note', models.TextField()), + ('report', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='bookwyrm.Report')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/bookwyrm/models/__init__.py b/bookwyrm/models/__init__.py index 67ee16d3d..326a673e1 100644 --- a/bookwyrm/models/__init__.py +++ b/bookwyrm/models/__init__.py @@ -21,6 +21,7 @@ from .tag import Tag, UserTag from .user import User, KeyPair, AnnualGoal from .relationship import UserFollows, UserFollowRequest, UserBlocks +from .report import Report, ReportComment from .federated_server import FederatedServer from .import_job import ImportJob, ImportItem diff --git a/bookwyrm/models/federated_server.py b/bookwyrm/models/federated_server.py index ce8043102..8f7d903e4 100644 --- a/bookwyrm/models/federated_server.py +++ b/bookwyrm/models/federated_server.py @@ -4,7 +4,7 @@ from .base_model import BookWyrmModel class FederatedServer(BookWyrmModel): - """ store which server's we federate with """ + """ store which servers we federate with """ server_name = models.CharField(max_length=255, unique=True) # federated, blocked, whatever else diff --git a/bookwyrm/models/report.py b/bookwyrm/models/report.py new file mode 100644 index 000000000..afbc356b5 --- /dev/null +++ b/bookwyrm/models/report.py @@ -0,0 +1,23 @@ +""" flagged for moderation """ +from django.db import models +from .base_model import BookWyrmModel + + +class Report(BookWyrmModel): + """ reported status or user """ + + reporter = models.ForeignKey( + "User", related_name="reporter", on_delete=models.PROTECT + ) + note = models.TextField(null=True, blank=True) + user = models.ForeignKey("User", on_delete=models.PROTECT) + statuses = models.ManyToManyField("Status") + resolved = models.BooleanField(default=False) + + +class ReportComment(BookWyrmModel): + """ updates on a report """ + + user = models.ForeignKey("User", on_delete=models.PROTECT) + note = models.TextField() + report = models.ForeignKey(Report, on_delete=models.PROTECT) diff --git a/bookwyrm/tests/views/test_reports.py b/bookwyrm/tests/views/test_reports.py index 3b8dec260..d35633cbf 100644 --- a/bookwyrm/tests/views/test_reports.py +++ b/bookwyrm/tests/views/test_reports.py @@ -20,6 +20,13 @@ class ReportViews(TestCase): local=True, localname="mouse", ) + self.local_user = models.User.objects.create_user( + "rat@local.com", + "rat@mouse.mouse", + "password", + local=True, + localname="rat", + ) models.SiteSettings.objects.create() def test_reports_page(self): @@ -39,7 +46,7 @@ class ReportViews(TestCase): request = self.factory.get("") request.user = self.local_user request.user.is_superuser = True - report = models.Report.objects.create() + report = models.Report.objects.create(reporter=self.local_user, user=self.rat) result = view(request, report.id) diff --git a/bookwyrm/views/reports.py b/bookwyrm/views/reports.py index a90fffaf4..2e374d5ea 100644 --- a/bookwyrm/views/reports.py +++ b/bookwyrm/views/reports.py @@ -8,7 +8,7 @@ from django.views import View from bookwyrm import models -# pylint: disable= no-self-use +# pylint: disable=no-self-use @method_decorator(login_required, name="dispatch") @method_decorator( permission_required("bookwyrm.moderate_user", raise_exception=True), @@ -21,11 +21,13 @@ from bookwyrm import models class Reports(View): """ list of reports """ - def get(self, request, status="open"): + def get(self, request): """ view current reports """ + resolved = request.GET.get("resolved") data = { - "status": status - } # {"reports": models.Report.objects.filter(status=status)} + "resolved": resolved, + "reports": models.Report.objects.filter(resolved=resolved), + } return TemplateResponse(request, "settings/reports.html", data) @@ -34,5 +36,5 @@ class Report(View): def get(self, request, report_id): """ load a report """ - data = {"report": get_object_or_404(models.REport, id=report_id)} + data = {"report": get_object_or_404(models.Report, id=report_id)} return TemplateResponse(request, "settings/report.html", data) From 21f199c5482d2aae87058433ce68c2d6930e610a Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 8 Mar 2021 18:36:34 -0800 Subject: [PATCH 04/19] Make reports --- bookwyrm/forms.py | 6 ++++ ...tcomment.py => 0049_auto_20210309_0156.py} | 12 ++++--- bookwyrm/models/report.py | 11 ++++++- bookwyrm/templates/settings/report.html | 2 ++ .../templates/settings/report_preview.html | 4 +-- .../templates/snippets/report_button.html | 7 ++++ .../snippets/status/status_options.html | 2 +- bookwyrm/templates/snippets/user_options.html | 2 +- bookwyrm/tests/views/test_reports.py | 33 +++++++++++++++++-- bookwyrm/urls.py | 5 +-- bookwyrm/views/__init__.py | 2 +- bookwyrm/views/reports.py | 29 ++++++++++++++-- 12 files changed, 96 insertions(+), 19 deletions(-) rename bookwyrm/migrations/{0049_report_reportcomment.py => 0049_auto_20210309_0156.py} (83%) create mode 100644 bookwyrm/templates/settings/report.html create mode 100644 bookwyrm/templates/snippets/report_button.html diff --git a/bookwyrm/forms.py b/bookwyrm/forms.py index 380e701fa..654130cf9 100644 --- a/bookwyrm/forms.py +++ b/bookwyrm/forms.py @@ -231,3 +231,9 @@ class ListForm(CustomForm): class Meta: model = models.List fields = ["user", "name", "description", "curation", "privacy"] + + +class ReportForm(CustomForm): + class Meta: + model = models.Report + fields = ["user", "reporter", "statuses", "note"] diff --git a/bookwyrm/migrations/0049_report_reportcomment.py b/bookwyrm/migrations/0049_auto_20210309_0156.py similarity index 83% rename from bookwyrm/migrations/0049_report_reportcomment.py rename to bookwyrm/migrations/0049_auto_20210309_0156.py index deb8ba6f6..494f5bc8c 100644 --- a/bookwyrm/migrations/0049_report_reportcomment.py +++ b/bookwyrm/migrations/0049_auto_20210309_0156.py @@ -1,9 +1,10 @@ -# Generated by Django 3.0.7 on 2021-03-09 00:55 +# Generated by Django 3.0.7 on 2021-03-09 01:56 import bookwyrm.models.fields from django.conf import settings from django.db import migrations, models import django.db.models.deletion +import django.db.models.expressions class Migration(migrations.Migration): @@ -23,12 +24,9 @@ class Migration(migrations.Migration): ('note', models.TextField(blank=True, null=True)), ('resolved', models.BooleanField(default=False)), ('reporter', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='reporter', to=settings.AUTH_USER_MODEL)), - ('statuses', models.ManyToManyField(to='bookwyrm.Status')), + ('statuses', models.ManyToManyField(blank=True, null=True, to='bookwyrm.Status')), ('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), ], - options={ - 'abstract': False, - }, ), migrations.CreateModel( name='ReportComment', @@ -45,4 +43,8 @@ class Migration(migrations.Migration): 'abstract': False, }, ), + migrations.AddConstraint( + model_name='report', + constraint=models.CheckConstraint(check=models.Q(_negated=True, reporter=django.db.models.expressions.F('user')), name='self_report'), + ), ] diff --git a/bookwyrm/models/report.py b/bookwyrm/models/report.py index afbc356b5..e1e8c2a41 100644 --- a/bookwyrm/models/report.py +++ b/bookwyrm/models/report.py @@ -1,5 +1,6 @@ """ flagged for moderation """ from django.db import models +from django.db.models import F, Q from .base_model import BookWyrmModel @@ -11,9 +12,17 @@ class Report(BookWyrmModel): ) note = models.TextField(null=True, blank=True) user = models.ForeignKey("User", on_delete=models.PROTECT) - statuses = models.ManyToManyField("Status") + statuses = models.ManyToManyField("Status", null=True, blank=True) resolved = models.BooleanField(default=False) + class Meta: + """ don't let users report themselves """ + constraints = [ + models.CheckConstraint( + check=~Q(reporter=F('user')), + name='self_report' + ) + ] class ReportComment(BookWyrmModel): """ updates on a report """ diff --git a/bookwyrm/templates/settings/report.html b/bookwyrm/templates/settings/report.html new file mode 100644 index 000000000..1f55906bd --- /dev/null +++ b/bookwyrm/templates/settings/report.html @@ -0,0 +1,2 @@ +{% extends 'settings/admin_layout.html' %} +{% load i18n %} diff --git a/bookwyrm/templates/settings/report_preview.html b/bookwyrm/templates/settings/report_preview.html index 67bffe275..b72dd958d 100644 --- a/bookwyrm/templates/settings/report_preview.html +++ b/bookwyrm/templates/settings/report_preview.html @@ -2,7 +2,7 @@ {% load i18n %} {% block card-header %}

    - report title

    + report title {% endblock %} {% block card-content %} @@ -11,4 +11,4 @@ about this report {% block card-footer %} footer -{% endblock +{% endblock %} diff --git a/bookwyrm/templates/snippets/report_button.html b/bookwyrm/templates/snippets/report_button.html new file mode 100644 index 000000000..5fbaee99b --- /dev/null +++ b/bookwyrm/templates/snippets/report_button.html @@ -0,0 +1,7 @@ +{% load i18n %} +
    + {% csrf_token %} + + + +
    diff --git a/bookwyrm/templates/snippets/status/status_options.html b/bookwyrm/templates/snippets/status/status_options.html index 0f099e177..f703ba6e1 100644 --- a/bookwyrm/templates/snippets/status/status_options.html +++ b/bookwyrm/templates/snippets/status/status_options.html @@ -25,7 +25,7 @@ {% trans "Send direct message" %}
  • - {% trans "Report status" %} + {% include 'snippets/report_button.html' with user=status.user class="is-fullwidth" %}
  • {% include 'snippets/block_button.html' with user=status.user class="is-fullwidth" %} diff --git a/bookwyrm/templates/snippets/user_options.html b/bookwyrm/templates/snippets/user_options.html index c9fdac0a4..585417c71 100644 --- a/bookwyrm/templates/snippets/user_options.html +++ b/bookwyrm/templates/snippets/user_options.html @@ -13,7 +13,7 @@ {% trans "Send direct message" %}
  • - {% trans "Report user" %} + {% include 'snippets/report_button.html' with user=status.user class="is-fullwidth" %}
  • {% include 'snippets/block_button.html' with user=user class="is-fullwidth" %} diff --git a/bookwyrm/tests/views/test_reports.py b/bookwyrm/tests/views/test_reports.py index d35633cbf..70414dcb6 100644 --- a/bookwyrm/tests/views/test_reports.py +++ b/bookwyrm/tests/views/test_reports.py @@ -3,8 +3,7 @@ from django.template.response import TemplateResponse from django.test import TestCase from django.test.client import RequestFactory -from bookwyrm import models -from bookwyrm import views +from bookwyrm import forms, models, views class ReportViews(TestCase): @@ -20,7 +19,7 @@ class ReportViews(TestCase): local=True, localname="mouse", ) - self.local_user = models.User.objects.create_user( + self.rat = models.User.objects.create_user( "rat@local.com", "rat@mouse.mouse", "password", @@ -35,6 +34,20 @@ class ReportViews(TestCase): request = self.factory.get("") request.user = self.local_user request.user.is_superuser = True + + result = view(request) + self.assertIsInstance(result, TemplateResponse) + result.render() + self.assertEqual(result.status_code, 200) + + def test_reports_page_with_data(self): + """ there are so many views, this just makes sure it LOADS """ + view = views.Reports.as_view() + request = self.factory.get("") + request.user = self.local_user + request.user.is_superuser = True + report = models.Report.objects.create(reporter=self.local_user, user=self.rat) + result = view(request) self.assertIsInstance(result, TemplateResponse) result.render() @@ -53,3 +66,17 @@ class ReportViews(TestCase): self.assertIsInstance(result, TemplateResponse) result.render() self.assertEqual(result.status_code, 200) + + def test_make_report(self): + """ a user reports another user """ + form = forms.ReportForm() + form.data["reporter"] = self.local_user.id + form.data["user"] = self.rat.id + request = self.factory.post("", form.data) + request.user = self.local_user + + views.make_report(request) + + report = models.Report.objects.get() + self.assertEqual(report.reporter, self.local_user) + self.assertEqual(report.user, self.rat) diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index c7ec7f4b7..42b9803d3 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -54,14 +54,15 @@ urlpatterns = [ re_path( r"^settings/invites/?$", views.ManageInvites.as_view(), name="settings-invites" ), + re_path(r"^invite/(?P[A-Za-z0-9]+)/?$", views.Invite.as_view()), # moderation re_path(r"^settings/reports/?$", views.Reports.as_view(), name="settings-reports"), re_path( - r"^settings/report/(?P\d+)/?$", + r"^settings/reports/(?P\d+)/?$", views.Report.as_view(), name="settings-report", ), - re_path(r"^invite/(?P[A-Za-z0-9]+)/?$", views.Invite.as_view()), + re_path(r"^report/?$", views.make_report, name="report"), # landing pages re_path(r"^about/?$", views.About.as_view()), path("", views.Home.as_view()), diff --git a/bookwyrm/views/__init__.py b/bookwyrm/views/__init__.py index 36a64ddfe..b433dca24 100644 --- a/bookwyrm/views/__init__.py +++ b/bookwyrm/views/__init__.py @@ -20,7 +20,7 @@ from .notifications import Notifications from .outbox import Outbox from .reading import edit_readthrough, create_readthrough, delete_readthrough from .reading import start_reading, finish_reading, delete_progressupdate -from .reports import Report, Reports +from .reports import Report, Reports, make_report from .rss_feed import RssFeed from .password import PasswordResetRequest, PasswordReset, ChangePassword from .search import Search diff --git a/bookwyrm/views/reports.py b/bookwyrm/views/reports.py index 2e374d5ea..9eaf9bdc9 100644 --- a/bookwyrm/views/reports.py +++ b/bookwyrm/views/reports.py @@ -1,11 +1,12 @@ """ moderation via flagged posts and users """ from django.contrib.auth.decorators import login_required, permission_required -from django.shortcuts import get_object_or_404 +from django.shortcuts import get_object_or_404, redirect from django.template.response import TemplateResponse from django.utils.decorators import method_decorator from django.views import View +from django.views.decorators.http import require_POST -from bookwyrm import models +from bookwyrm import forms, models # pylint: disable=no-self-use @@ -23,7 +24,7 @@ class Reports(View): def get(self, request): """ view current reports """ - resolved = request.GET.get("resolved") + resolved = request.GET.get("resolved", False) data = { "resolved": resolved, "reports": models.Report.objects.filter(resolved=resolved), @@ -31,6 +32,15 @@ class Reports(View): return TemplateResponse(request, "settings/reports.html", data) +@method_decorator(login_required, name="dispatch") +@method_decorator( + permission_required("bookwyrm.moderate_user", raise_exception=True), + name="dispatch", +) +@method_decorator( + permission_required("bookwyrm.moderate_post", raise_exception=True), + name="dispatch", +) class Report(View): """ view a specific report """ @@ -38,3 +48,16 @@ class Report(View): """ load a report """ data = {"report": get_object_or_404(models.Report, id=report_id)} return TemplateResponse(request, "settings/report.html", data) + + +@login_required +@require_POST +def make_report(request): + """ a user reports something """ + form = forms.ReportForm(request.POST) + if not form.is_valid(): + print(form.errors) + return redirect(request.headers.get("Referer", "/")) + + form.save() + return redirect(request.headers.get("Referer", "/")) From ff624e33fa93a46ef238596709eea4b96d63bb14 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 9 Mar 2021 10:08:02 -0800 Subject: [PATCH 05/19] fixes display of report items on admin page --- bookwyrm/templates/settings/admin_layout.html | 2 +- bookwyrm/templates/settings/report_preview.html | 13 +++++++++---- bookwyrm/templates/settings/reports.html | 8 ++++---- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/bookwyrm/templates/settings/admin_layout.html b/bookwyrm/templates/settings/admin_layout.html index 312d502a7..a34fe6389 100644 --- a/bookwyrm/templates/settings/admin_layout.html +++ b/bookwyrm/templates/settings/admin_layout.html @@ -46,7 +46,7 @@ {% endif %} -
    +
    {% block panel %}{% endblock %}
    diff --git a/bookwyrm/templates/settings/report_preview.html b/bookwyrm/templates/settings/report_preview.html index b72dd958d..63eca0fca 100644 --- a/bookwyrm/templates/settings/report_preview.html +++ b/bookwyrm/templates/settings/report_preview.html @@ -1,14 +1,19 @@ {% extends 'components/card.html' %} {% load i18n %} {% block card-header %} -

    - report title

    +

    + report title +

    {% endblock %} {% block card-content %} -about this report +
    + about this report +
    {% endblock %} {% block card-footer %} -footer + {% endblock %} diff --git a/bookwyrm/templates/settings/reports.html b/bookwyrm/templates/settings/reports.html index efca7244b..59fbaf568 100644 --- a/bookwyrm/templates/settings/reports.html +++ b/bookwyrm/templates/settings/reports.html @@ -15,13 +15,13 @@ -
      +
      {% for report in reports %} -
    • +
      {% include 'settings/report_preview.html' with report=report %} -
    • +
      {% endfor %} -
    + {% endblock %} From 999bff4bbadb25f0e1a3015a4ece75bcc0559baa Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 9 Mar 2021 12:35:07 -0800 Subject: [PATCH 06/19] Basic reports admin templates --- bookwyrm/templates/settings/report.html | 41 +++++++++++++++++++ .../templates/settings/report_preview.html | 13 ++++-- bookwyrm/templates/settings/reports.html | 9 ++-- bookwyrm/views/reports.py | 2 +- 4 files changed, 57 insertions(+), 8 deletions(-) diff --git a/bookwyrm/templates/settings/report.html b/bookwyrm/templates/settings/report.html index 1f55906bd..74c3641bb 100644 --- a/bookwyrm/templates/settings/report.html +++ b/bookwyrm/templates/settings/report.html @@ -1,2 +1,43 @@ {% extends 'settings/admin_layout.html' %} {% load i18n %} + +{% block title %}{% blocktrans with report_id=report.id %}Report #{{ report_id }}{% endblocktrans %}{% endblock %} +{% block header %}{% blocktrans with report_id=report.id %}Report #{{ report_id }}{% endblocktrans %}{% endblock %} + +{% block panel %} + + +
    + {% include 'settings/report_preview.html' with report=report %} +
    + +
    +

    {% trans "Actions" %}

    +
    + + +
    + + {% for comment in report.reportcomment_set.all %} +
    + {{ comment }} +
    + {% endfor %} +
    + + + + +
    + +
    +

    {% trans "Reported statuses" %}

    +
      + {% for status in report.statuses.all %} +
    • {{ status.id }}
    • + {% endfor %} +
    +
    +{% endblock %} diff --git a/bookwyrm/templates/settings/report_preview.html b/bookwyrm/templates/settings/report_preview.html index 63eca0fca..25cb8a2fb 100644 --- a/bookwyrm/templates/settings/report_preview.html +++ b/bookwyrm/templates/settings/report_preview.html @@ -1,19 +1,26 @@ {% extends 'components/card.html' %} {% load i18n %} +{% load humanize %} {% block card-header %}

    - report title + {{ report.user.username }}

    {% endblock %} {% block card-content %}
    - about this report + {% if report.notes %}{{ report.notes }}{% else %}{% trans "No notes provided" %}{% endif %}
    {% endblock %} {% block card-footer %} + + {% endblock %} diff --git a/bookwyrm/templates/settings/reports.html b/bookwyrm/templates/settings/reports.html index 59fbaf568..329901eab 100644 --- a/bookwyrm/templates/settings/reports.html +++ b/bookwyrm/templates/settings/reports.html @@ -1,16 +1,17 @@ {% extends 'settings/admin_layout.html' %} {% load i18n %} +{% block title %}{% trans "Reports" %}{% endblock %} {% block header %}{% trans "Reports" %}{% endblock %} {% block panel %} diff --git a/bookwyrm/views/reports.py b/bookwyrm/views/reports.py index 9eaf9bdc9..eda565329 100644 --- a/bookwyrm/views/reports.py +++ b/bookwyrm/views/reports.py @@ -24,7 +24,7 @@ class Reports(View): def get(self, request): """ view current reports """ - resolved = request.GET.get("resolved", False) + resolved = request.GET.get("resolved") == "true" data = { "resolved": resolved, "reports": models.Report.objects.filter(resolved=resolved), From 0d2c641d01dc7017ebb2a45d8941569b1d5e3df5 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 9 Mar 2021 12:57:38 -0800 Subject: [PATCH 07/19] Reformats report model --- .../migrations/0049_auto_20210309_0156.py | 107 ++++++++++++++---- bookwyrm/models/report.py | 7 +- 2 files changed, 88 insertions(+), 26 deletions(-) diff --git a/bookwyrm/migrations/0049_auto_20210309_0156.py b/bookwyrm/migrations/0049_auto_20210309_0156.py index 494f5bc8c..ae9d77a89 100644 --- a/bookwyrm/migrations/0049_auto_20210309_0156.py +++ b/bookwyrm/migrations/0049_auto_20210309_0156.py @@ -10,41 +10,104 @@ import django.db.models.expressions class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0048_merge_20210308_1754'), + ("bookwyrm", "0048_merge_20210308_1754"), ] operations = [ migrations.CreateModel( - name='Report', + name="Report", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created_date', models.DateTimeField(auto_now_add=True)), - ('updated_date', models.DateTimeField(auto_now=True)), - ('remote_id', bookwyrm.models.fields.RemoteIdField(max_length=255, null=True, validators=[bookwyrm.models.fields.validate_remote_id])), - ('note', models.TextField(blank=True, null=True)), - ('resolved', models.BooleanField(default=False)), - ('reporter', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='reporter', to=settings.AUTH_USER_MODEL)), - ('statuses', models.ManyToManyField(blank=True, null=True, to='bookwyrm.Status')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("created_date", models.DateTimeField(auto_now_add=True)), + ("updated_date", models.DateTimeField(auto_now=True)), + ( + "remote_id", + bookwyrm.models.fields.RemoteIdField( + max_length=255, + null=True, + validators=[bookwyrm.models.fields.validate_remote_id], + ), + ), + ("note", models.TextField(blank=True, null=True)), + ("resolved", models.BooleanField(default=False)), + ( + "reporter", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="reporter", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "statuses", + models.ManyToManyField(blank=True, null=True, to="bookwyrm.Status"), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + to=settings.AUTH_USER_MODEL, + ), + ), ], ), migrations.CreateModel( - name='ReportComment', + name="ReportComment", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created_date', models.DateTimeField(auto_now_add=True)), - ('updated_date', models.DateTimeField(auto_now=True)), - ('remote_id', bookwyrm.models.fields.RemoteIdField(max_length=255, null=True, validators=[bookwyrm.models.fields.validate_remote_id])), - ('note', models.TextField()), - ('report', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='bookwyrm.Report')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("created_date", models.DateTimeField(auto_now_add=True)), + ("updated_date", models.DateTimeField(auto_now=True)), + ( + "remote_id", + bookwyrm.models.fields.RemoteIdField( + max_length=255, + null=True, + validators=[bookwyrm.models.fields.validate_remote_id], + ), + ), + ("note", models.TextField()), + ( + "report", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + to="bookwyrm.Report", + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + to=settings.AUTH_USER_MODEL, + ), + ), ], options={ - 'abstract': False, + "abstract": False, }, ), migrations.AddConstraint( - model_name='report', - constraint=models.CheckConstraint(check=models.Q(_negated=True, reporter=django.db.models.expressions.F('user')), name='self_report'), + model_name="report", + constraint=models.CheckConstraint( + check=models.Q( + _negated=True, reporter=django.db.models.expressions.F("user") + ), + name="self_report", + ), ), ] diff --git a/bookwyrm/models/report.py b/bookwyrm/models/report.py index e1e8c2a41..3a8fdd13a 100644 --- a/bookwyrm/models/report.py +++ b/bookwyrm/models/report.py @@ -17,13 +17,12 @@ class Report(BookWyrmModel): class Meta: """ don't let users report themselves """ + constraints = [ - models.CheckConstraint( - check=~Q(reporter=F('user')), - name='self_report' - ) + models.CheckConstraint(check=~Q(reporter=F("user")), name="self_report") ] + class ReportComment(BookWyrmModel): """ updates on a report """ From 7f452066939495ee74a7f2dac129bae1c1e2d7e8 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 10 Mar 2021 12:38:49 -0800 Subject: [PATCH 08/19] Moves moderation templates to their own directory --- bookwyrm/templates/{settings => moderation}/report.html | 2 +- .../templates/{settings => moderation}/report_preview.html | 0 bookwyrm/templates/{settings => moderation}/reports.html | 2 +- bookwyrm/templates/snippets/report_button.html | 1 + bookwyrm/views/reports.py | 4 ++-- 5 files changed, 5 insertions(+), 4 deletions(-) rename bookwyrm/templates/{settings => moderation}/report.html (94%) rename bookwyrm/templates/{settings => moderation}/report_preview.html (100%) rename bookwyrm/templates/{settings => moderation}/reports.html (91%) diff --git a/bookwyrm/templates/settings/report.html b/bookwyrm/templates/moderation/report.html similarity index 94% rename from bookwyrm/templates/settings/report.html rename to bookwyrm/templates/moderation/report.html index 74c3641bb..ca68d51d8 100644 --- a/bookwyrm/templates/settings/report.html +++ b/bookwyrm/templates/moderation/report.html @@ -10,7 +10,7 @@
    - {% include 'settings/report_preview.html' with report=report %} + {% include 'moderation/report_preview.html' with report=report %}
    diff --git a/bookwyrm/templates/settings/report_preview.html b/bookwyrm/templates/moderation/report_preview.html similarity index 100% rename from bookwyrm/templates/settings/report_preview.html rename to bookwyrm/templates/moderation/report_preview.html diff --git a/bookwyrm/templates/settings/reports.html b/bookwyrm/templates/moderation/reports.html similarity index 91% rename from bookwyrm/templates/settings/reports.html rename to bookwyrm/templates/moderation/reports.html index 329901eab..ebf29a7a7 100644 --- a/bookwyrm/templates/settings/reports.html +++ b/bookwyrm/templates/moderation/reports.html @@ -19,7 +19,7 @@
    {% for report in reports %}
    - {% include 'settings/report_preview.html' with report=report %} + {% include 'moderation/report_preview.html' with report=report %}
    {% endfor %}
    diff --git a/bookwyrm/templates/snippets/report_button.html b/bookwyrm/templates/snippets/report_button.html index 5fbaee99b..9d32d5fb9 100644 --- a/bookwyrm/templates/snippets/report_button.html +++ b/bookwyrm/templates/snippets/report_button.html @@ -3,5 +3,6 @@ {% csrf_token %} + diff --git a/bookwyrm/views/reports.py b/bookwyrm/views/reports.py index eda565329..875470811 100644 --- a/bookwyrm/views/reports.py +++ b/bookwyrm/views/reports.py @@ -29,7 +29,7 @@ class Reports(View): "resolved": resolved, "reports": models.Report.objects.filter(resolved=resolved), } - return TemplateResponse(request, "settings/reports.html", data) + return TemplateResponse(request, "moderation/reports.html", data) @method_decorator(login_required, name="dispatch") @@ -47,7 +47,7 @@ class Report(View): def get(self, request, report_id): """ load a report """ data = {"report": get_object_or_404(models.Report, id=report_id)} - return TemplateResponse(request, "settings/report.html", data) + return TemplateResponse(request, "moderation/report.html", data) @login_required From 33b8537a3d692c2dcbcab73b7c4da11958c79094 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Thu, 11 Mar 2021 17:38:21 -0800 Subject: [PATCH 09/19] Let user supply a note for report --- bookwyrm/models/report.py | 1 + bookwyrm/templates/moderation/report.html | 10 +++-- .../templates/moderation/report_modal.html | 37 +++++++++++++++++++ .../templates/moderation/report_preview.html | 2 +- bookwyrm/templates/notifications.html | 10 +---- .../templates/snippets/report_button.html | 16 ++++---- .../snippets/status/status_body.html | 9 ++++- .../snippets/status/status_options.html | 4 +- .../templates/snippets/status_preview.html | 9 +++++ 9 files changed, 73 insertions(+), 25 deletions(-) create mode 100644 bookwyrm/templates/moderation/report_modal.html create mode 100644 bookwyrm/templates/snippets/status_preview.html diff --git a/bookwyrm/models/report.py b/bookwyrm/models/report.py index 3a8fdd13a..8893f42fc 100644 --- a/bookwyrm/models/report.py +++ b/bookwyrm/models/report.py @@ -21,6 +21,7 @@ class Report(BookWyrmModel): constraints = [ models.CheckConstraint(check=~Q(reporter=F("user")), name="self_report") ] + ordering = ("-created_date",) class ReportComment(BookWyrmModel): diff --git a/bookwyrm/templates/moderation/report.html b/bookwyrm/templates/moderation/report.html index ca68d51d8..ce0a0b3a5 100644 --- a/bookwyrm/templates/moderation/report.html +++ b/bookwyrm/templates/moderation/report.html @@ -32,11 +32,13 @@
    -
    -

    {% trans "Reported statuses" %}

    +
    +

    {% trans "Reported statuses" %}

      - {% for status in report.statuses.all %} -
    • {{ status.id }}
    • + {% for status in report.statuses.select_subclasses.all %} +
    • + {% include 'snippets/status/status.html' with status=status moderation_mode=True %} +
    • {% endfor %}
    diff --git a/bookwyrm/templates/moderation/report_modal.html b/bookwyrm/templates/moderation/report_modal.html new file mode 100644 index 000000000..286131301 --- /dev/null +++ b/bookwyrm/templates/moderation/report_modal.html @@ -0,0 +1,37 @@ +{% extends 'components/modal.html' %} +{% load i18n %} +{% load humanize %} + +{% block modal-title %} +{% blocktrans with username=user.username %}Report @{{ username }}{% endblocktrans %} +{% endblock %} + +{% block modal-form-open %} +
    +{% endblock %} + +{% block modal-body %} + +{% csrf_token %} + + + + +
    +

    {% blocktrans with site_name=site.name %}This report will be sent to {{ site_name }}'s moderators for review.{% endblocktrans %}

    + + +
    + +{% endblock %} + + +{% block modal-footer %} + + +{% trans "Cancel" as button_text %} +{% include 'snippets/toggle/toggle_button.html' with text=button_text controls_text="report" controls_uid=report_uuid class="" %} + +{% endblock %} +{% block modal-form-close %}
    {% endblock %} + diff --git a/bookwyrm/templates/moderation/report_preview.html b/bookwyrm/templates/moderation/report_preview.html index 25cb8a2fb..9acc4f771 100644 --- a/bookwyrm/templates/moderation/report_preview.html +++ b/bookwyrm/templates/moderation/report_preview.html @@ -9,7 +9,7 @@ {% block card-content %}
    - {% if report.notes %}{{ report.notes }}{% else %}{% trans "No notes provided" %}{% endif %} + {% if report.note %}{{ report.note }}{% else %}{% trans "No notes provided" %}{% endif %}
    {% endblock %} diff --git a/bookwyrm/templates/notifications.html b/bookwyrm/templates/notifications.html index 80ee22508..3f0300bd8 100644 --- a/bookwyrm/templates/notifications.html +++ b/bookwyrm/templates/notifications.html @@ -115,15 +115,7 @@
    - {% if related_status.content %} - - {{ related_status.content | safe | truncatewords_html:10 }}{% if related_status.mention_books %} {{ related_status.mention_books.first.title }}{% endif %} - - {% elif related_status.quote %} - {{ related_status.quote | safe | truncatewords_html:10 }} - {% elif related_status.rating %} - {% include 'snippets/stars.html' with rating=related_status.rating %} - {% endif %} + {% include 'snippets/status_preview.html' with status=related_status %}
    {{ related_status.published_date | post_date }} diff --git a/bookwyrm/templates/snippets/report_button.html b/bookwyrm/templates/snippets/report_button.html index 9d32d5fb9..2fa0a3f30 100644 --- a/bookwyrm/templates/snippets/report_button.html +++ b/bookwyrm/templates/snippets/report_button.html @@ -1,8 +1,10 @@ {% load i18n %} -
    - {% csrf_token %} - - - - -
    +{% load bookwyrm_tags %} +{% with 0|uuid as report_uuid %} + +{% trans "Report" as button_text %} +{% include 'snippets/toggle/toggle_button.html' with class="is-danger is-light is-small is-fullwidth" text=button_text controls_text="report" controls_uid=report_uuid focus="modal-title-report" disabled=is_current %} + +{% include 'moderation/report_modal.html' with user=user reporter=request.user controls_text="report" controls_uid=report_uuid %} + +{% endwith %} diff --git a/bookwyrm/templates/snippets/status/status_body.html b/bookwyrm/templates/snippets/status/status_body.html index 8d6c21ed9..f3732d1a3 100644 --- a/bookwyrm/templates/snippets/status/status_body.html +++ b/bookwyrm/templates/snippets/status/status_body.html @@ -18,7 +18,10 @@ {% block card-footer %}