Make reports

This commit is contained in:
Mouse Reeve 2021-03-08 18:36:34 -08:00
parent e59c127686
commit 21f199c548
12 changed files with 96 additions and 19 deletions

View File

@ -231,3 +231,9 @@ class ListForm(CustomForm):
class Meta: class Meta:
model = models.List model = models.List
fields = ["user", "name", "description", "curation", "privacy"] fields = ["user", "name", "description", "curation", "privacy"]
class ReportForm(CustomForm):
class Meta:
model = models.Report
fields = ["user", "reporter", "statuses", "note"]

View File

@ -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 import bookwyrm.models.fields
from django.conf import settings from django.conf import settings
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
import django.db.models.expressions
class Migration(migrations.Migration): class Migration(migrations.Migration):
@ -23,12 +24,9 @@ class Migration(migrations.Migration):
('note', models.TextField(blank=True, null=True)), ('note', models.TextField(blank=True, null=True)),
('resolved', models.BooleanField(default=False)), ('resolved', models.BooleanField(default=False)),
('reporter', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='reporter', to=settings.AUTH_USER_MODEL)), ('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)), ('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
], ],
options={
'abstract': False,
},
), ),
migrations.CreateModel( migrations.CreateModel(
name='ReportComment', name='ReportComment',
@ -45,4 +43,8 @@ class Migration(migrations.Migration):
'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'),
),
] ]

View File

@ -1,5 +1,6 @@
""" flagged for moderation """ """ flagged for moderation """
from django.db import models from django.db import models
from django.db.models import F, Q
from .base_model import BookWyrmModel from .base_model import BookWyrmModel
@ -11,9 +12,17 @@ class Report(BookWyrmModel):
) )
note = models.TextField(null=True, blank=True) note = models.TextField(null=True, blank=True)
user = models.ForeignKey("User", on_delete=models.PROTECT) 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) 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): class ReportComment(BookWyrmModel):
""" updates on a report """ """ updates on a report """

View File

@ -0,0 +1,2 @@
{% extends 'settings/admin_layout.html' %}
{% load i18n %}

View File

@ -2,7 +2,7 @@
{% load i18n %} {% load i18n %}
{% block card-header %} {% block card-header %}
<h2 class="title is-4"> <h2 class="title is-4">
<a href="{% url settings-report report.id %}">report title</a></h2> <a href="{% url 'settings-report' report.id %}">report title</a></h2>
{% endblock %} {% endblock %}
{% block card-content %} {% block card-content %}
@ -11,4 +11,4 @@ about this report
{% block card-footer %} {% block card-footer %}
footer footer
{% endblock {% endblock %}

View File

@ -0,0 +1,7 @@
{% load i18n %}
<form name="report" method="post" action="{% url 'report' %}">
{% csrf_token %}
<input type="hidden" name="reporter" value="{{ request.user.id }}">
<input type="hidden" name="user" value="{{ user.id }}">
<button class="button is-danger is-light is-small {{ class }}" type="submit">{% trans "Report" %}</button>
</form>

View File

@ -25,7 +25,7 @@
<a href="/direct-messages/{{ status.user|username }}" class="button is-fullwidth is-small">{% trans "Send direct message" %}</a> <a href="/direct-messages/{{ status.user|username }}" class="button is-fullwidth is-small">{% trans "Send direct message" %}</a>
</li> </li>
<li role="menuitem"> <li role="menuitem">
<a href="{{ status.local_path }}/report" class="button is-fullwidth is-small">{% trans "Report status" %}</a> {% include 'snippets/report_button.html' with user=status.user class="is-fullwidth" %}
</li> </li>
<li role="menuitem"> <li role="menuitem">
{% include 'snippets/block_button.html' with user=status.user class="is-fullwidth" %} {% include 'snippets/block_button.html' with user=status.user class="is-fullwidth" %}

View File

@ -13,7 +13,7 @@
<a href="/direct-messages/{{ user|username }}" class="button is-fullwidth is-small">{% trans "Send direct message" %}</a> <a href="/direct-messages/{{ user|username }}" class="button is-fullwidth is-small">{% trans "Send direct message" %}</a>
</li> </li>
<li role="menuitem"> <li role="menuitem">
<a href="{{ user.local_path }}/report" class="button is-fullwidth is-small">{% trans "Report user" %}</a> {% include 'snippets/report_button.html' with user=status.user class="is-fullwidth" %}
</li> </li>
<li role="menuitem"> <li role="menuitem">
{% include 'snippets/block_button.html' with user=user class="is-fullwidth" %} {% include 'snippets/block_button.html' with user=user class="is-fullwidth" %}

View File

@ -3,8 +3,7 @@ from django.template.response import TemplateResponse
from django.test import TestCase from django.test import TestCase
from django.test.client import RequestFactory from django.test.client import RequestFactory
from bookwyrm import models from bookwyrm import forms, models, views
from bookwyrm import views
class ReportViews(TestCase): class ReportViews(TestCase):
@ -20,7 +19,7 @@ class ReportViews(TestCase):
local=True, local=True,
localname="mouse", localname="mouse",
) )
self.local_user = models.User.objects.create_user( self.rat = models.User.objects.create_user(
"rat@local.com", "rat@local.com",
"rat@mouse.mouse", "rat@mouse.mouse",
"password", "password",
@ -35,6 +34,20 @@ class ReportViews(TestCase):
request = self.factory.get("") request = self.factory.get("")
request.user = self.local_user request.user = self.local_user
request.user.is_superuser = True 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) result = view(request)
self.assertIsInstance(result, TemplateResponse) self.assertIsInstance(result, TemplateResponse)
result.render() result.render()
@ -53,3 +66,17 @@ class ReportViews(TestCase):
self.assertIsInstance(result, TemplateResponse) self.assertIsInstance(result, TemplateResponse)
result.render() result.render()
self.assertEqual(result.status_code, 200) 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)

View File

@ -54,14 +54,15 @@ urlpatterns = [
re_path( re_path(
r"^settings/invites/?$", views.ManageInvites.as_view(), name="settings-invites" r"^settings/invites/?$", views.ManageInvites.as_view(), name="settings-invites"
), ),
re_path(r"^invite/(?P<code>[A-Za-z0-9]+)/?$", views.Invite.as_view()),
# moderation # moderation
re_path(r"^settings/reports/?$", views.Reports.as_view(), name="settings-reports"), re_path(r"^settings/reports/?$", views.Reports.as_view(), name="settings-reports"),
re_path( re_path(
r"^settings/report/(?P<report_id>\d+)/?$", r"^settings/reports/(?P<report_id>\d+)/?$",
views.Report.as_view(), views.Report.as_view(),
name="settings-report", name="settings-report",
), ),
re_path(r"^invite/(?P<code>[A-Za-z0-9]+)/?$", views.Invite.as_view()), re_path(r"^report/?$", views.make_report, name="report"),
# landing pages # landing pages
re_path(r"^about/?$", views.About.as_view()), re_path(r"^about/?$", views.About.as_view()),
path("", views.Home.as_view()), path("", views.Home.as_view()),

View File

@ -20,7 +20,7 @@ from .notifications import Notifications
from .outbox import Outbox from .outbox import Outbox
from .reading import edit_readthrough, create_readthrough, delete_readthrough from .reading import edit_readthrough, create_readthrough, delete_readthrough
from .reading import start_reading, finish_reading, delete_progressupdate 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 .rss_feed import RssFeed
from .password import PasswordResetRequest, PasswordReset, ChangePassword from .password import PasswordResetRequest, PasswordReset, ChangePassword
from .search import Search from .search import Search

View File

@ -1,11 +1,12 @@
""" moderation via flagged posts and users """ """ moderation via flagged posts and users """
from django.contrib.auth.decorators import login_required, permission_required 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.template.response import TemplateResponse
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.views import View 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 # pylint: disable=no-self-use
@ -23,7 +24,7 @@ class Reports(View):
def get(self, request): def get(self, request):
""" view current reports """ """ view current reports """
resolved = request.GET.get("resolved") resolved = request.GET.get("resolved", False)
data = { data = {
"resolved": resolved, "resolved": resolved,
"reports": models.Report.objects.filter(resolved=resolved), "reports": models.Report.objects.filter(resolved=resolved),
@ -31,6 +32,15 @@ class Reports(View):
return TemplateResponse(request, "settings/reports.html", data) 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): class Report(View):
""" view a specific report """ """ view a specific report """
@ -38,3 +48,16 @@ class Report(View):
""" load a report """ """ 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) 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", "/"))