+ {% trans "Auto-moderation rules will create reports for any user or status with fields matching the provided string." %}
+ {% trans "At this time, reports are not being generated automatically, and you must manually trigger a scan." %}
+
+
+
+
+{% if success %}
+
+
+
+ {% trans "Successfully added rule" %}
+
+
+{% endif %}
+
+
+
+
+ {% for rule in rules %}
+
+
+ {{ rule.string_match }}
+
+
+ {{ rule.flag_users|yesno }}
+
+
+ {{ rule.flag_statuses|yesno }}
+
+
+
+
+
+ {% endfor %}
+
+
+{% endblock %}
+
diff --git a/bookwyrm/templates/settings/layout.html b/bookwyrm/templates/settings/layout.html
index a74e111c..c5a3c5af 100644
--- a/bookwyrm/templates/settings/layout.html
+++ b/bookwyrm/templates/settings/layout.html
@@ -56,6 +56,10 @@
{% url 'settings-reports' as url %}
{% trans "Reports" %}
+
{% trans "Auto-moderation rules will create reports for any user or status with fields matching the provided string." %}
+ {% trans "Users or statuses that have already been reported (regardless of whether the report was resolved) will not be flagged." %}
{% trans "At this time, reports are not being generated automatically, and you must manually trigger a scan." %}
-{% if report.statuses.exists %}
+{% if report.status %}
-
{% trans "Reported statuses" %}
-
- {% for status in report.statuses.select_subclasses.all %}
-
- {% if status.deleted %}
- {% trans "Status has been deleted" %}
- {% else %}
- {% include 'snippets/status/status.html' with status=status moderation_mode=True %}
- {% endif %}
-
- {% endfor %}
-
+
{% trans "Reported status" %}
+ {% if report.status.deleted %}
+ {% trans "Status has been deleted" %}
+ {% else %}
+ {% include 'snippets/status/status.html' with status=report.status|load_subclass moderation_mode=True %}
+ {% endif %}
{% endif %}
diff --git a/bookwyrm/templates/settings/reports/report_header.html b/bookwyrm/templates/settings/reports/report_header.html
index d76db104..878a825d 100644
--- a/bookwyrm/templates/settings/reports/report_header.html
+++ b/bookwyrm/templates/settings/reports/report_header.html
@@ -1,7 +1,7 @@
{% load i18n %}
{% load utilities %}
-{% if report.statuses.exists %}
+{% if report.status %}
{% blocktrans trimmed with report_id=report.id username=report.user|username %}
Report #{{ report_id }}: Status posted by @{{ username }}
diff --git a/bookwyrm/templates/snippets/report_button.html b/bookwyrm/templates/snippets/report_button.html
index 9d94d2af..60b542f4 100644
--- a/bookwyrm/templates/snippets/report_button.html
+++ b/bookwyrm/templates/snippets/report_button.html
@@ -12,6 +12,6 @@
>
{% trans "Report" %}
-{% include 'snippets/report_modal.html' with user=user id=modal_id status=status.id %}
+{% include 'snippets/report_modal.html' with user=user id=modal_id status_id=status.id %}
{% endwith %}
diff --git a/bookwyrm/templates/snippets/report_modal.html b/bookwyrm/templates/snippets/report_modal.html
index f65cab59..64e0c298 100644
--- a/bookwyrm/templates/snippets/report_modal.html
+++ b/bookwyrm/templates/snippets/report_modal.html
@@ -23,7 +23,7 @@
{% if status_id %}
-
+
{% endif %}
{% if link %}
From 93f82fbf18079b7d9dd5a28f5edb7466eb515122 Mon Sep 17 00:00:00 2001
From: Mouse Reeve
Date: Thu, 24 Feb 2022 13:20:18 -0800
Subject: [PATCH 06/12] Adds notifications
---
bookwyrm/migrations/0139_report_status.py | 16 +++++++++---
.../migrations/0140_remove_report_statuses.py | 6 ++---
.../migrations/0141_alter_report_status.py | 13 +++++++---
bookwyrm/models/antispam.py | 26 ++++++++++++++++---
4 files changed, 46 insertions(+), 15 deletions(-)
diff --git a/bookwyrm/migrations/0139_report_status.py b/bookwyrm/migrations/0139_report_status.py
index fc938633..c85a43b8 100644
--- a/bookwyrm/migrations/0139_report_status.py
+++ b/bookwyrm/migrations/0139_report_status.py
@@ -13,6 +13,7 @@ def set_report_statuses(apps, schema_editor):
report.status = report.statuses.first()
report.save()
+
def set_reverse(apps, schema_editor):
"""copy over status fields"""
db_alias = schema_editor.connection.alias
@@ -21,17 +22,24 @@ def set_reverse(apps, schema_editor):
for report in reports:
report.statuses.set(report.status)
+
class Migration(migrations.Migration):
dependencies = [
- ('bookwyrm', '0138_automod'),
+ ("bookwyrm", "0138_automod"),
]
operations = [
migrations.AddField(
- model_name='report',
- name='status',
- field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='reports', to='bookwyrm.status'),
+ model_name="report",
+ name="status",
+ field=models.ForeignKey(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.PROTECT,
+ related_name="reports",
+ to="bookwyrm.status",
+ ),
),
migrations.RunPython(set_report_statuses, reverse_code=set_reverse),
]
diff --git a/bookwyrm/migrations/0140_remove_report_statuses.py b/bookwyrm/migrations/0140_remove_report_statuses.py
index 94303ce9..57651c3b 100644
--- a/bookwyrm/migrations/0140_remove_report_statuses.py
+++ b/bookwyrm/migrations/0140_remove_report_statuses.py
@@ -6,12 +6,12 @@ from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
- ('bookwyrm', '0139_report_status'),
+ ("bookwyrm", "0139_report_status"),
]
operations = [
migrations.RemoveField(
- model_name='report',
- name='statuses',
+ model_name="report",
+ name="statuses",
),
]
diff --git a/bookwyrm/migrations/0141_alter_report_status.py b/bookwyrm/migrations/0141_alter_report_status.py
index e0a7e58e..58102dd5 100644
--- a/bookwyrm/migrations/0141_alter_report_status.py
+++ b/bookwyrm/migrations/0141_alter_report_status.py
@@ -7,13 +7,18 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
- ('bookwyrm', '0140_remove_report_statuses'),
+ ("bookwyrm", "0140_remove_report_statuses"),
]
operations = [
migrations.AlterField(
- model_name='report',
- name='status',
- field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='bookwyrm.status'),
+ model_name="report",
+ name="status",
+ field=models.ForeignKey(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.PROTECT,
+ to="bookwyrm.status",
+ ),
),
]
diff --git a/bookwyrm/models/antispam.py b/bookwyrm/models/antispam.py
index d948d419..8508c395 100644
--- a/bookwyrm/models/antispam.py
+++ b/bookwyrm/models/antispam.py
@@ -57,8 +57,26 @@ def automod_task():
if not AutoMod.objects.exists():
return
reporter = AutoMod.objects.first().created_by
- automod_users(reporter)
- automod_statuses(reporter)
+ reports = automod_users(reporter) + automod_statuses(reporter)
+ if reports:
+ admins = User.objects.filter(
+ models.Q(user_permissions__name__in=["moderate_user", "moderate_post"])
+ | models.Q(is_superuser=True)
+ ).all()
+ notification_model = apps.get_model(
+ "bookwyrm", "Notification", require_ready=True
+ )
+ for admin in admins:
+ notification_model.objects.bulk_create(
+ [
+ notification_model(
+ user=admin,
+ related_report=r,
+ notification_type="REPORT",
+ )
+ for r in reports
+ ]
+ )
def automod_users(reporter):
@@ -76,7 +94,7 @@ def automod_users(reporter):
report_model = apps.get_model("bookwyrm", "Report", require_ready=True)
- report_model.objects.bulk_create(
+ return report_model.objects.bulk_create(
[
report_model(
reporter=reporter,
@@ -103,7 +121,7 @@ def automod_statuses(reporter):
)
report_model = apps.get_model("bookwyrm", "Report", require_ready=True)
- report_model.objects.bulk_create(
+ return report_model.objects.bulk_create(
[
report_model(
reporter=reporter,
From 84b9a19339e969f92cb8fc34f2124807c1fc200f Mon Sep 17 00:00:00 2001
From: Mouse Reeve
Date: Thu, 24 Feb 2022 13:27:26 -0800
Subject: [PATCH 07/12] Expands scanned fields
---
bookwyrm/models/antispam.py | 25 +++++++++++++++++--------
1 file changed, 17 insertions(+), 8 deletions(-)
diff --git a/bookwyrm/models/antispam.py b/bookwyrm/models/antispam.py
index 8508c395..4632c64e 100644
--- a/bookwyrm/models/antispam.py
+++ b/bookwyrm/models/antispam.py
@@ -85,12 +85,18 @@ def automod_users(reporter):
"string_match", flat=True
)
- filters = [{"username__icontains": r} for r in user_rules]
- users = User.objects.filter(
- reduce(operator.or_, (Q(**f) for f in filters)),
- is_active=True,
- report__isnull=True, # don't flag users that already have reports
- ).values_list("id", flat=True)
+ filters = []
+ for field in ["username", "summary", "name"]:
+ filters += [{f"{field}__icontains": r} for r in user_rules]
+ users = (
+ User.objects.filter(
+ reduce(operator.or_, (Q(**f) for f in filters)),
+ is_active=True,
+ report__isnull=True, # don't flag users that already have reports
+ )
+ .distinct()
+ .values_list("id", flat=True)
+ )
report_model = apps.get_model("bookwyrm", "Report", require_ready=True)
@@ -112,13 +118,16 @@ def automod_statuses(reporter):
"string_match", flat=True
)
- filters = [{"content__icontains": r} for r in status_rules]
+ filters = []
+ for field in ["content", "content_warning", "quotation__quote", "review__name"]:
+ filters += [{f"{field}__icontains": r} for r in status_rules]
+
status_model = apps.get_model("bookwyrm", "Status", require_ready=True)
statuses = status_model.objects.filter(
reduce(operator.or_, (Q(**f) for f in filters)),
deleted=False,
report__isnull=True, # don't flag statuses that already have reports
- )
+ ).distinct()
report_model = apps.get_model("bookwyrm", "Report", require_ready=True)
return report_model.objects.bulk_create(
From f4468281754b07fa59f385c6a7b499de04d6251e Mon Sep 17 00:00:00 2001
From: Mouse Reeve
Date: Thu, 24 Feb 2022 14:39:09 -0800
Subject: [PATCH 08/12] Fixes template typo
---
bookwyrm/templates/settings/automod/rules.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/bookwyrm/templates/settings/automod/rules.html b/bookwyrm/templates/settings/automod/rules.html
index 5b4b0c76..78b88eba 100644
--- a/bookwyrm/templates/settings/automod/rules.html
+++ b/bookwyrm/templates/settings/automod/rules.html
@@ -46,7 +46,7 @@
-
+
From 1aa6b99d1fb49e691fae925f491fe4643795bd1f Mon Sep 17 00:00:00 2001
From: Mouse Reeve
Date: Thu, 24 Feb 2022 17:33:05 -0800
Subject: [PATCH 09/12] Adds tests
---
bookwyrm/tests/models/test_automod.py | 75 ++++++++++++++++++++++
bookwyrm/tests/views/admin/test_automod.py | 58 +++++++++++++++++
2 files changed, 133 insertions(+)
create mode 100644 bookwyrm/tests/models/test_automod.py
create mode 100644 bookwyrm/tests/views/admin/test_automod.py
diff --git a/bookwyrm/tests/models/test_automod.py b/bookwyrm/tests/models/test_automod.py
new file mode 100644
index 00000000..abb9aa55
--- /dev/null
+++ b/bookwyrm/tests/models/test_automod.py
@@ -0,0 +1,75 @@
+""" test for app action functionality """
+from unittest.mock import patch
+
+from django.test import TestCase
+from django.test.client import RequestFactory
+
+from bookwyrm import models
+from bookwyrm.models.antispam import automod_task
+
+
+@patch("bookwyrm.models.Status.broadcast")
+@patch("bookwyrm.activitystreams.add_status_task.delay")
+@patch("bookwyrm.activitystreams.remove_status_task.delay")
+class AutomodModel(TestCase):
+ """every response to a get request, html or json"""
+
+ def setUp(self):
+ """we need basic test data and mocks"""
+ self.factory = RequestFactory()
+ with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"), patch(
+ "bookwyrm.activitystreams.populate_stream_task.delay"
+ ), patch("bookwyrm.lists_stream.populate_lists_task.delay"):
+ self.local_user = models.User.objects.create_user(
+ "mouse@local.com",
+ "mouse@mouse.mouse",
+ "password",
+ local=True,
+ localname="mouse",
+ )
+
+ def test_automod_task_no_rules(self, *_):
+ """nothing to see here"""
+ self.assertFalse(models.Report.objects.exists())
+ automod_task()
+ self.assertFalse(models.Report.objects.exists())
+
+ def test_automod_task_user(self, *_):
+ """scan activity"""
+ self.assertFalse(models.Report.objects.exists())
+ models.AutoMod.objects.create(
+ string_match="hi",
+ flag_users=True,
+ flag_statuses=True,
+ created_by=self.local_user,
+ )
+
+ self.local_user.name = "okay hi"
+ self.local_user.save(broadcast=False, update_fields=["name"])
+
+ automod_task()
+
+ reports = models.Report.objects.all()
+ self.assertEqual(reports.count(), 1)
+ self.assertEqual(reports.first().user, self.local_user)
+
+ def test_automod_status(self, *_):
+ """scan activity"""
+ self.assertFalse(models.Report.objects.exists())
+ models.AutoMod.objects.create(
+ string_match="hi",
+ flag_users=True,
+ flag_statuses=True,
+ created_by=self.local_user,
+ )
+
+ status = models.Status.objects.create(
+ user=self.local_user, content="hello", content_warning="hi"
+ )
+
+ automod_task()
+
+ reports = models.Report.objects.all()
+ self.assertEqual(reports.count(), 1)
+ self.assertEqual(reports.first().status, status)
+ self.assertEqual(reports.first().user, self.local_user)
diff --git a/bookwyrm/tests/views/admin/test_automod.py b/bookwyrm/tests/views/admin/test_automod.py
new file mode 100644
index 00000000..1ed36caf
--- /dev/null
+++ b/bookwyrm/tests/views/admin/test_automod.py
@@ -0,0 +1,58 @@
+""" test for app action functionality """
+from unittest.mock import patch
+
+from django.template.response import TemplateResponse
+from django.test import TestCase
+from django.test.client import RequestFactory
+
+from bookwyrm import forms, models, views
+from bookwyrm.tests.validate_html import validate_html
+
+
+class AutomodViews(TestCase):
+ """every response to a get request, html or json"""
+
+ def setUp(self):
+ """we need basic test data and mocks"""
+ self.factory = RequestFactory()
+ with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"), patch(
+ "bookwyrm.activitystreams.populate_stream_task.delay"
+ ), patch("bookwyrm.lists_stream.populate_lists_task.delay"):
+ 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_automod_rules_get(self):
+ """there are so many views, this just makes sure it LOADS"""
+ view = views.AutoMod.as_view()
+ request = self.factory.get("")
+ request.user = self.local_user
+ request.user.is_superuser = True
+
+ result = view(request)
+ self.assertIsInstance(result, TemplateResponse)
+ validate_html(result.render())
+ self.assertEqual(result.status_code, 200)
+
+ def test_automod_rules_post(self):
+ """there are so many views, this just makes sure it LOADS"""
+ form = forms.AutoModRuleForm()
+ form.data["string_match"] = "hello"
+ form.data["flag_users"] = True
+ form.data["flag_statuses"] = False
+ form.data["created_by"] = self.local_user
+
+ view = views.AutoMod.as_view()
+ request = self.factory.post("", form.data)
+ request.user = self.local_user
+ request.user.is_superuser = True
+
+ result = view(request)
+ self.assertIsInstance(result, TemplateResponse)
+ validate_html(result.render())
+ self.assertEqual(result.status_code, 200)
From eb8b9fdaed85ee56743f6a6c2352a0e3a3af6e7f Mon Sep 17 00:00:00 2001
From: Mouse Reeve
Date: Thu, 24 Feb 2022 17:33:22 -0800
Subject: [PATCH 10/12] Fixes bugs in model task
---
bookwyrm/models/antispam.py | 19 ++++++++++---------
1 file changed, 10 insertions(+), 9 deletions(-)
diff --git a/bookwyrm/models/antispam.py b/bookwyrm/models/antispam.py
index 4632c64e..b894ade7 100644
--- a/bookwyrm/models/antispam.py
+++ b/bookwyrm/models/antispam.py
@@ -84,19 +84,17 @@ def automod_users(reporter):
user_rules = AutoMod.objects.filter(flag_users=True).values_list(
"string_match", flat=True
)
+ if not user_rules:
+ return []
filters = []
for field in ["username", "summary", "name"]:
filters += [{f"{field}__icontains": r} for r in user_rules]
- users = (
- User.objects.filter(
- reduce(operator.or_, (Q(**f) for f in filters)),
- is_active=True,
- report__isnull=True, # don't flag users that already have reports
- )
- .distinct()
- .values_list("id", flat=True)
- )
+ users = User.objects.filter(
+ reduce(operator.or_, (Q(**f) for f in filters)),
+ is_active=True,
+ report__isnull=True, # don't flag users that already have reports
+ ).distinct()
report_model = apps.get_model("bookwyrm", "Report", require_ready=True)
@@ -118,6 +116,9 @@ def automod_statuses(reporter):
"string_match", flat=True
)
+ if not status_rules:
+ return []
+
filters = []
for field in ["content", "content_warning", "quotation__quote", "review__name"]:
filters += [{f"{field}__icontains": r} for r in status_rules]
From 84ef214ca16ab46fb6585b67a02071cfcfe06b28 Mon Sep 17 00:00:00 2001
From: Mouse Reeve
Date: Thu, 24 Feb 2022 17:34:02 -0800
Subject: [PATCH 11/12] Valid template markup
---
.../templates/settings/automod/rules.html | 141 +++++++++++-------
1 file changed, 88 insertions(+), 53 deletions(-)
diff --git a/bookwyrm/templates/settings/automod/rules.html b/bookwyrm/templates/settings/automod/rules.html
index 78b88eba..5585dfde 100644
--- a/bookwyrm/templates/settings/automod/rules.html
+++ b/bookwyrm/templates/settings/automod/rules.html
@@ -33,63 +33,98 @@
{% endif %}
-
{% endblock %}
From 689be8c94bcfd13c0a2ab4640c70b795cdfce4fb Mon Sep 17 00:00:00 2001
From: Mouse Reeve
Date: Thu, 24 Feb 2022 17:42:28 -0800
Subject: [PATCH 12/12] Only scan local data
---
bookwyrm/models/antispam.py | 2 ++
bookwyrm/templates/settings/automod/rules.html | 2 +-
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/bookwyrm/models/antispam.py b/bookwyrm/models/antispam.py
index b894ade7..f506b6f1 100644
--- a/bookwyrm/models/antispam.py
+++ b/bookwyrm/models/antispam.py
@@ -93,6 +93,7 @@ def automod_users(reporter):
users = User.objects.filter(
reduce(operator.or_, (Q(**f) for f in filters)),
is_active=True,
+ local=True,
report__isnull=True, # don't flag users that already have reports
).distinct()
@@ -127,6 +128,7 @@ def automod_statuses(reporter):
statuses = status_model.objects.filter(
reduce(operator.or_, (Q(**f) for f in filters)),
deleted=False,
+ local=True,
report__isnull=True, # don't flag statuses that already have reports
).distinct()
diff --git a/bookwyrm/templates/settings/automod/rules.html b/bookwyrm/templates/settings/automod/rules.html
index 5585dfde..8205b3d7 100644
--- a/bookwyrm/templates/settings/automod/rules.html
+++ b/bookwyrm/templates/settings/automod/rules.html
@@ -14,7 +14,7 @@
- {% trans "Auto-moderation rules will create reports for any user or status with fields matching the provided string." %}
+ {% trans "Auto-moderation rules will create reports for any local user or status with fields matching the provided string." %}
{% trans "Users or statuses that have already been reported (regardless of whether the report was resolved) will not be flagged." %}
{% trans "At this time, reports are not being generated automatically, and you must manually trigger a scan." %}