mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2024-11-22 17:41:08 +00:00
Merge pull request #2854 from bookwyrm-social/report-actions
Record report actions
This commit is contained in:
commit
15e82ece07
18 changed files with 313 additions and 70 deletions
36
bookwyrm/migrations/0179_reportcomment_comment_type.py
Normal file
36
bookwyrm/migrations/0179_reportcomment_comment_type.py
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
# Generated by Django 3.2.18 on 2023-05-16 16:02
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("bookwyrm", "0178_auto_20230328_2132"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="reportcomment",
|
||||||
|
name="action_type",
|
||||||
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("comment", "Comment"),
|
||||||
|
("resolve", "Resolved report"),
|
||||||
|
("reopen", "Re-opened report"),
|
||||||
|
("message_reporter", "Messaged reporter"),
|
||||||
|
("message_offender", "Messaged reported user"),
|
||||||
|
("user_suspension", "Suspended user"),
|
||||||
|
("user_unsuspension", "Un-suspended user"),
|
||||||
|
("user_perms", "Changed user permission level"),
|
||||||
|
("user_deletion", "Deleted user account"),
|
||||||
|
("block_domain", "Blocked domain"),
|
||||||
|
("approve_domain", "Approved domain"),
|
||||||
|
("delete_item", "Deleted item"),
|
||||||
|
],
|
||||||
|
default="comment",
|
||||||
|
max_length=20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.RenameModel("ReportComment", "ReportAction"),
|
||||||
|
]
|
17
bookwyrm/migrations/0180_alter_reportaction_options.py
Normal file
17
bookwyrm/migrations/0180_alter_reportaction_options.py
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# Generated by Django 3.2.18 on 2023-06-21 22:01
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("bookwyrm", "0179_reportcomment_comment_type"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name="reportaction",
|
||||||
|
options={"ordering": ("created_date",)},
|
||||||
|
),
|
||||||
|
]
|
13
bookwyrm/migrations/0181_merge_20230806_2302.py
Normal file
13
bookwyrm/migrations/0181_merge_20230806_2302.py
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
# Generated by Django 3.2.20 on 2023-08-06 23:02
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("bookwyrm", "0180_alter_reportaction_options"),
|
||||||
|
("bookwyrm", "0180_alter_user_preferred_language"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = []
|
|
@ -20,7 +20,7 @@ from .readthrough import ReadThrough, ProgressUpdate, ProgressMode
|
||||||
from .user import User, KeyPair
|
from .user import User, KeyPair
|
||||||
from .annual_goal import AnnualGoal
|
from .annual_goal import AnnualGoal
|
||||||
from .relationship import UserFollows, UserFollowRequest, UserBlocks
|
from .relationship import UserFollows, UserFollowRequest, UserBlocks
|
||||||
from .report import Report, ReportComment
|
from .report import Report, ReportAction
|
||||||
from .federated_server import FederatedServer
|
from .federated_server import FederatedServer
|
||||||
|
|
||||||
from .group import Group, GroupMember, GroupMemberInvitation
|
from .group import Group, GroupMember, GroupMemberInvitation
|
||||||
|
|
|
@ -1,11 +1,27 @@
|
||||||
""" flagged for moderation """
|
""" flagged for moderation """
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from bookwyrm.settings import DOMAIN
|
from bookwyrm.settings import DOMAIN
|
||||||
from .base_model import BookWyrmModel
|
from .base_model import BookWyrmModel
|
||||||
|
|
||||||
|
|
||||||
|
# Report action enums
|
||||||
|
COMMENT = "comment"
|
||||||
|
RESOLVE = "resolve"
|
||||||
|
REOPEN = "reopen"
|
||||||
|
MESSAGE_REPORTER = "message_reporter"
|
||||||
|
MESSAGE_OFFENDER = "message_offender"
|
||||||
|
USER_SUSPENSION = "user_suspension"
|
||||||
|
USER_UNSUSPENSION = "user_unsuspension"
|
||||||
|
USER_DELETION = "user_deletion"
|
||||||
|
USER_PERMS = "user_perms"
|
||||||
|
BLOCK_DOMAIN = "block_domain"
|
||||||
|
APPROVE_DOMAIN = "approve_domain"
|
||||||
|
DELETE_ITEM = "delete_item"
|
||||||
|
|
||||||
|
|
||||||
class Report(BookWyrmModel):
|
class Report(BookWyrmModel):
|
||||||
"""reported status or user"""
|
"""reported status or user"""
|
||||||
|
|
||||||
|
@ -32,20 +48,65 @@ class Report(BookWyrmModel):
|
||||||
def get_remote_id(self):
|
def get_remote_id(self):
|
||||||
return f"https://{DOMAIN}/settings/reports/{self.id}"
|
return f"https://{DOMAIN}/settings/reports/{self.id}"
|
||||||
|
|
||||||
|
def comment(self, user, note):
|
||||||
|
"""comment on a report"""
|
||||||
|
ReportAction.objects.create(
|
||||||
|
action_type=COMMENT, user=user, note=note, report=self
|
||||||
|
)
|
||||||
|
|
||||||
|
def resolve(self, user):
|
||||||
|
"""Mark a report as complete"""
|
||||||
|
self.resolved = True
|
||||||
|
self.save()
|
||||||
|
ReportAction.objects.create(action_type=RESOLVE, user=user, report=self)
|
||||||
|
|
||||||
|
def reopen(self, user):
|
||||||
|
"""Wait! This report isn't complete after all"""
|
||||||
|
self.resolved = False
|
||||||
|
self.save()
|
||||||
|
ReportAction.objects.create(action_type=REOPEN, user=user, report=self)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def record_action(cls, report_id: int, action: str, user):
|
||||||
|
"""Note that someone did something"""
|
||||||
|
if not report_id:
|
||||||
|
return
|
||||||
|
report = cls.objects.get(id=report_id)
|
||||||
|
ReportAction.objects.create(action_type=action, user=user, report=report)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""set order by default"""
|
"""set order by default"""
|
||||||
|
|
||||||
ordering = ("-created_date",)
|
ordering = ("-created_date",)
|
||||||
|
|
||||||
|
|
||||||
class ReportComment(BookWyrmModel):
|
ReportActionTypes = [
|
||||||
|
(COMMENT, _("Comment")),
|
||||||
|
(RESOLVE, _("Resolved report")),
|
||||||
|
(REOPEN, _("Re-opened report")),
|
||||||
|
(MESSAGE_REPORTER, _("Messaged reporter")),
|
||||||
|
(MESSAGE_OFFENDER, _("Messaged reported user")),
|
||||||
|
(USER_SUSPENSION, _("Suspended user")),
|
||||||
|
(USER_UNSUSPENSION, _("Un-suspended user")),
|
||||||
|
(USER_PERMS, _("Changed user permission level")),
|
||||||
|
(USER_DELETION, _("Deleted user account")),
|
||||||
|
(BLOCK_DOMAIN, _("Blocked domain")),
|
||||||
|
(APPROVE_DOMAIN, _("Approved domain")),
|
||||||
|
(DELETE_ITEM, _("Deleted item")),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class ReportAction(BookWyrmModel):
|
||||||
"""updates on a report"""
|
"""updates on a report"""
|
||||||
|
|
||||||
user = models.ForeignKey("User", on_delete=models.PROTECT)
|
user = models.ForeignKey("User", on_delete=models.PROTECT)
|
||||||
|
action_type = models.CharField(
|
||||||
|
max_length=20, blank=False, default="comment", choices=ReportActionTypes
|
||||||
|
)
|
||||||
note = models.TextField()
|
note = models.TextField()
|
||||||
report = models.ForeignKey(Report, on_delete=models.PROTECT)
|
report = models.ForeignKey(Report, on_delete=models.PROTECT)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""sort comments"""
|
"""sort comments"""
|
||||||
|
|
||||||
ordering = ("-created_date",)
|
ordering = ("created_date",)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
{% extends 'settings/layout.html' %}
|
{% extends 'settings/layout.html' %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load humanize %}
|
{% load humanize %}
|
||||||
|
{% load utilities %}
|
||||||
{% load feed_page_tags %}
|
{% load feed_page_tags %}
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
|
@ -21,7 +22,7 @@
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<details class="details-panel box">
|
<details class="details-panel box">
|
||||||
<summary>
|
<summary>
|
||||||
<span class="title is-4">{% trans "Message reporter" %}</span>
|
<span class="title is-6">{% trans "Message reporter" %}</span>
|
||||||
<span class="details-close icon icon-x" aria-hidden="true"></span>
|
<span class="details-close icon icon-x" aria-hidden="true"></span>
|
||||||
</summary>
|
</summary>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
|
@ -61,21 +62,49 @@
|
||||||
{% include 'settings/users/user_moderation_actions.html' with user=report.user %}
|
{% include 'settings/users/user_moderation_actions.html' with user=report.user %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="block">
|
<div class="block content">
|
||||||
<h3 class="title is-4">{% trans "Moderator Comments" %}</h3>
|
<h3 class="title is-4">{% trans "Moderation Activity" %}</h3>
|
||||||
{% for comment in report.reportcomment_set.all %}
|
|
||||||
<div class="card block">
|
<div class="box">
|
||||||
<p class="card-content">{{ comment.note }}</p>
|
<ul class="mt-0">
|
||||||
<div class="card-footer">
|
<li class="mb-2">
|
||||||
<div class="card-footer-item">
|
<div class="is-flex">
|
||||||
<a href="{{ comment.user.local_path }}">{{ comment.user.display_name }}</a>
|
<p class="mb-0 is-flex-grow-1">
|
||||||
</div>
|
{% blocktrans trimmed with user=report.reporter|username user_link=report.reporter.local_path %}
|
||||||
<div class="card-footer-item">
|
<a href="{{ user_link }}">{{ user}}</a> opened this report
|
||||||
{{ comment.created_date|naturaltime }}
|
{% endblocktrans %}
|
||||||
</div>
|
</p>
|
||||||
|
<span class="tag">{{ report.created_date }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
{% for comment in report.reportaction_set.all %}
|
||||||
|
<li class="mb-2">
|
||||||
|
<div class="is-flex">
|
||||||
|
<p class="mb-0 is-flex-grow-1">
|
||||||
|
{% if comment.action_type == "comment" %}
|
||||||
|
{% blocktrans trimmed with user=comment.user|username user_link=comment.user.local_path %}
|
||||||
|
<a href="{{ user_link }}">{{ user}}</a> commented on this report:
|
||||||
|
{% endblocktrans %}
|
||||||
|
{% else %}
|
||||||
|
{% blocktrans trimmed with user=comment.user|username user_link=comment.user.local_path %}
|
||||||
|
<a href="{{ user_link }}">{{ user}}</a> took an action on this report:
|
||||||
|
{% endblocktrans %}
|
||||||
|
<span class="has-text-weight-bold">
|
||||||
|
{{ comment.get_action_type_display }}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
<span class="tag">{{ comment.created_date }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if comment.note %}
|
||||||
|
<blockquote>{{ comment.note }}</blockquote>
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
<form class="block" name="report-comment" method="post" action="{% url 'settings-report' report.id %}">
|
<form class="block" name="report-comment" method="post" action="{% url 'settings-report' report.id %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="field">
|
<div class="field">
|
||||||
|
@ -86,5 +115,6 @@
|
||||||
<button class="button">{% trans "Comment" %}</button>
|
<button class="button">{% trans "Comment" %}</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -13,8 +13,18 @@
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<form>
|
{% if link.domain.status != "approved" %}
|
||||||
|
<form method="POST" action="{% url 'settings-link-domain-status' link.domain.id 'approved' report.id %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
<button type="submit" class="button is-success is-light">{% trans "Approve domain" %}</button>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if link.domain.status != "blocked" %}
|
||||||
|
<form method="POST" action="{% url 'settings-link-domain-status' link.domain.id 'blocked' report.id %}">
|
||||||
|
{% csrf_token %}
|
||||||
<button type="submit" class="button is-danger is-light">{% trans "Block domain" %}</button>
|
<button type="submit" class="button is-danger is-light">{% trans "Block domain" %}</button>
|
||||||
</form>
|
</form>
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block form %}
|
{% block form %}
|
||||||
<form name="delete-user" action="{% url 'settings-delete-user' user.id %}" method="post">
|
<form name="delete-user" action="{% url 'settings-delete-user' user.id report.id %}" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<p>
|
<p>
|
||||||
{% blocktrans trimmed with username=user.localname %}
|
{% blocktrans trimmed with username=user.localname %}
|
||||||
|
|
|
@ -22,12 +22,12 @@
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if user.is_active or user.deactivation_reason == "pending" %}
|
{% if user.is_active or user.deactivation_reason == "pending" %}
|
||||||
<form name="suspend" method="post" action="{% url 'settings-report-suspend' user.id %}" class="mr-1">
|
<form name="suspend" method="post" action="{% url 'settings-report-suspend' user.id report.id %}" class="mr-1">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<button type="submit" class="button is-danger is-light">{% trans "Suspend user" %}</button>
|
<button type="submit" class="button is-danger is-light">{% trans "Suspend user" %}</button>
|
||||||
</form>
|
</form>
|
||||||
{% else %}
|
{% else %}
|
||||||
<form name="unsuspend" method="post" action="{% url 'settings-report-unsuspend' user.id %}" class="mr-1">
|
<form name="unsuspend" method="post" action="{% url 'settings-report-unsuspend' user.id report.id %}" class="mr-1">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<button class="button">{% trans "Un-suspend user" %}</button>
|
<button class="button">{% trans "Un-suspend user" %}</button>
|
||||||
</form>
|
</form>
|
||||||
|
@ -49,7 +49,7 @@
|
||||||
|
|
||||||
{% if user.local %}
|
{% if user.local %}
|
||||||
<div>
|
<div>
|
||||||
<form name="permission" method="post" action="{% url 'settings-user' user.id %}">
|
<form name="permission" method="post" action="{% url 'settings-user' user.id report.id %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<label class="label" for="id_user_group">{% trans "Access level:" %}</label>
|
<label class="label" for="id_user_group">{% trans "Access level:" %}</label>
|
||||||
{% if group_form.non_field_errors %}
|
{% if group_form.non_field_errors %}
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
<div class="card-footer-item">
|
<div class="card-footer-item">
|
||||||
{# moderation options #}
|
{# moderation options #}
|
||||||
<form name="delete-{{ status.id }}" action="/delete-status/{{ status.id }}" method="post">
|
<form name="delete-{{ status.id }}" action="/delete-status/{{ status.id }}/{{ report.id }}" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<button class="button is-danger is-light" type="submit">
|
<button class="button is-danger is-light" type="submit">
|
||||||
{% trans "Delete status" %}
|
{% trans "Delete status" %}
|
||||||
|
|
|
@ -78,8 +78,8 @@ class ReportViews(TestCase):
|
||||||
validate_html(result.render())
|
validate_html(result.render())
|
||||||
self.assertEqual(result.status_code, 200)
|
self.assertEqual(result.status_code, 200)
|
||||||
|
|
||||||
def test_report_comment(self):
|
def test_report_action(self):
|
||||||
"""comment on a report"""
|
"""action on a report"""
|
||||||
view = views.ReportAdmin.as_view()
|
view = views.ReportAdmin.as_view()
|
||||||
request = self.factory.post("", {"note": "hi"})
|
request = self.factory.post("", {"note": "hi"})
|
||||||
request.user = self.local_user
|
request.user = self.local_user
|
||||||
|
@ -87,15 +87,17 @@ class ReportViews(TestCase):
|
||||||
|
|
||||||
view(request, report.id)
|
view(request, report.id)
|
||||||
|
|
||||||
comment = models.ReportComment.objects.get()
|
action = models.ReportAction.objects.get()
|
||||||
self.assertEqual(comment.user, self.local_user)
|
self.assertEqual(action.user, self.local_user)
|
||||||
self.assertEqual(comment.note, "hi")
|
self.assertEqual(action.note, "hi")
|
||||||
self.assertEqual(comment.report, report)
|
self.assertEqual(action.report, report)
|
||||||
|
self.assertEqual(action.action_type, "comment")
|
||||||
|
|
||||||
def test_resolve_report(self):
|
def test_resolve_report(self):
|
||||||
"""toggle report resolution status"""
|
"""toggle report resolution status"""
|
||||||
report = models.Report.objects.create(reporter=self.local_user, user=self.rat)
|
report = models.Report.objects.create(reporter=self.local_user, user=self.rat)
|
||||||
self.assertFalse(report.resolved)
|
self.assertFalse(report.resolved)
|
||||||
|
self.assertFalse(models.ReportAction.objects.exists())
|
||||||
request = self.factory.post("")
|
request = self.factory.post("")
|
||||||
request.user = self.local_user
|
request.user = self.local_user
|
||||||
|
|
||||||
|
@ -104,11 +106,25 @@ class ReportViews(TestCase):
|
||||||
report.refresh_from_db()
|
report.refresh_from_db()
|
||||||
self.assertTrue(report.resolved)
|
self.assertTrue(report.resolved)
|
||||||
|
|
||||||
|
# check that the action was noted
|
||||||
|
self.assertTrue(
|
||||||
|
models.ReportAction.objects.filter(
|
||||||
|
report=report, action_type="resolve", user=self.local_user
|
||||||
|
).exists()
|
||||||
|
)
|
||||||
|
|
||||||
# un-resolve
|
# un-resolve
|
||||||
views.resolve_report(request, report.id)
|
views.resolve_report(request, report.id)
|
||||||
report.refresh_from_db()
|
report.refresh_from_db()
|
||||||
self.assertFalse(report.resolved)
|
self.assertFalse(report.resolved)
|
||||||
|
|
||||||
|
# check that the action was noted
|
||||||
|
self.assertTrue(
|
||||||
|
models.ReportAction.objects.filter(
|
||||||
|
report=report, action_type="reopen", user=self.local_user
|
||||||
|
).exists()
|
||||||
|
)
|
||||||
|
|
||||||
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
|
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
|
||||||
@patch("bookwyrm.activitystreams.populate_stream_task.delay")
|
@patch("bookwyrm.activitystreams.populate_stream_task.delay")
|
||||||
@patch("bookwyrm.suggested_users.remove_user_task.delay")
|
@patch("bookwyrm.suggested_users.remove_user_task.delay")
|
||||||
|
|
|
@ -7,6 +7,7 @@ from django.test import TestCase
|
||||||
from django.test.client import RequestFactory
|
from django.test.client import RequestFactory
|
||||||
|
|
||||||
from bookwyrm import models, views
|
from bookwyrm import models, views
|
||||||
|
from bookwyrm.models.report import USER_PERMS
|
||||||
from bookwyrm.management.commands import initdb
|
from bookwyrm.management.commands import initdb
|
||||||
from bookwyrm.tests.validate_html import validate_html
|
from bookwyrm.tests.validate_html import validate_html
|
||||||
|
|
||||||
|
@ -79,3 +80,37 @@ class UserAdminViews(TestCase):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
list(self.local_user.groups.values_list("name", flat=True)), ["editor"]
|
list(self.local_user.groups.values_list("name", flat=True)), ["editor"]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
|
||||||
|
@patch("bookwyrm.activitystreams.populate_stream_task.delay")
|
||||||
|
@patch("bookwyrm.suggested_users.remove_user_task.delay")
|
||||||
|
def test_user_admin_page_post_with_report(self, *_):
|
||||||
|
"""set the user's group"""
|
||||||
|
group = Group.objects.get(name="editor")
|
||||||
|
self.assertEqual(
|
||||||
|
list(self.local_user.groups.values_list("name", flat=True)), ["moderator"]
|
||||||
|
)
|
||||||
|
|
||||||
|
report = models.Report.objects.create(
|
||||||
|
user=self.local_user, reporter=self.local_user
|
||||||
|
)
|
||||||
|
|
||||||
|
view = views.UserAdmin.as_view()
|
||||||
|
request = self.factory.post("", {"groups": [group.id]})
|
||||||
|
request.user = self.local_user
|
||||||
|
|
||||||
|
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||||
|
result = view(request, self.local_user.id, report.id)
|
||||||
|
|
||||||
|
self.assertIsInstance(result, TemplateResponse)
|
||||||
|
validate_html(result.render())
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
list(self.local_user.groups.values_list("name", flat=True)), ["editor"]
|
||||||
|
)
|
||||||
|
# make sure a report action was created
|
||||||
|
self.assertTrue(
|
||||||
|
models.ReportAction.objects.filter(
|
||||||
|
report=report, action_type=USER_PERMS
|
||||||
|
).exists()
|
||||||
|
)
|
||||||
|
|
|
@ -141,12 +141,12 @@ urlpatterns = [
|
||||||
name="settings-users",
|
name="settings-users",
|
||||||
),
|
),
|
||||||
re_path(
|
re_path(
|
||||||
r"^settings/users/(?P<user>\d+)/?$",
|
r"^settings/users/(?P<user_id>\d+)/(?P<report_id>\d+)?$",
|
||||||
views.UserAdmin.as_view(),
|
views.UserAdmin.as_view(),
|
||||||
name="settings-user",
|
name="settings-user",
|
||||||
),
|
),
|
||||||
re_path(
|
re_path(
|
||||||
r"^settings/users/(?P<user>\d+)/activate/?$",
|
r"^settings/users/(?P<user_id>\d+)/activate/?$",
|
||||||
views.ActivateUserAdmin.as_view(),
|
views.ActivateUserAdmin.as_view(),
|
||||||
name="settings-activate-user",
|
name="settings-activate-user",
|
||||||
),
|
),
|
||||||
|
@ -231,7 +231,7 @@ urlpatterns = [
|
||||||
name="settings-link-domain",
|
name="settings-link-domain",
|
||||||
),
|
),
|
||||||
re_path(
|
re_path(
|
||||||
r"^setting/link-domains/(?P<domain_id>\d+)/(?P<status>(pending|approved|blocked))/?$",
|
r"^setting/link-domains/(?P<domain_id>\d+)/(?P<status>(pending|approved|blocked))/(?P<report_id>\d+)?$",
|
||||||
views.update_domain_status,
|
views.update_domain_status,
|
||||||
name="settings-link-domain-status",
|
name="settings-link-domain-status",
|
||||||
),
|
),
|
||||||
|
@ -275,17 +275,17 @@ urlpatterns = [
|
||||||
name="settings-report",
|
name="settings-report",
|
||||||
),
|
),
|
||||||
re_path(
|
re_path(
|
||||||
r"^settings/reports/(?P<user_id>\d+)/suspend/?$",
|
r"^settings/reports/(?P<user_id>\d+)/suspend/(?P<report_id>\d+)?$",
|
||||||
views.suspend_user,
|
views.suspend_user,
|
||||||
name="settings-report-suspend",
|
name="settings-report-suspend",
|
||||||
),
|
),
|
||||||
re_path(
|
re_path(
|
||||||
r"^settings/reports/(?P<user_id>\d+)/unsuspend/?$",
|
r"^settings/reports/(?P<user_id>\d+)/unsuspend/(?P<report_id>\d+)?$",
|
||||||
views.unsuspend_user,
|
views.unsuspend_user,
|
||||||
name="settings-report-unsuspend",
|
name="settings-report-unsuspend",
|
||||||
),
|
),
|
||||||
re_path(
|
re_path(
|
||||||
r"^settings/reports/(?P<user_id>\d+)/delete/?$",
|
r"^settings/reports/(?P<user_id>\d+)/delete/(?P<report_id>\d+)?$",
|
||||||
views.moderator_delete_user,
|
views.moderator_delete_user,
|
||||||
name="settings-delete-user",
|
name="settings-delete-user",
|
||||||
),
|
),
|
||||||
|
@ -633,7 +633,7 @@ urlpatterns = [
|
||||||
name="create-status",
|
name="create-status",
|
||||||
),
|
),
|
||||||
re_path(
|
re_path(
|
||||||
r"^delete-status/(?P<status_id>\d+)/?$",
|
r"^delete-status/(?P<status_id>\d+)/?(?P<report_id>\d+)?$",
|
||||||
views.DeleteStatus.as_view(),
|
views.DeleteStatus.as_view(),
|
||||||
name="delete-status",
|
name="delete-status",
|
||||||
),
|
),
|
||||||
|
|
|
@ -7,6 +7,8 @@ from django.views import View
|
||||||
from django.views.decorators.http import require_POST
|
from django.views.decorators.http import require_POST
|
||||||
|
|
||||||
from bookwyrm import forms, models
|
from bookwyrm import forms, models
|
||||||
|
from bookwyrm.models.report import APPROVE_DOMAIN, BLOCK_DOMAIN
|
||||||
|
from bookwyrm.views.helpers import redirect_to_referer
|
||||||
|
|
||||||
# pylint: disable=no-self-use
|
# pylint: disable=no-self-use
|
||||||
@method_decorator(login_required, name="dispatch")
|
@method_decorator(login_required, name="dispatch")
|
||||||
|
@ -46,11 +48,17 @@ class LinkDomain(View):
|
||||||
@require_POST
|
@require_POST
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required("bookwyrm.moderate_user")
|
@permission_required("bookwyrm.moderate_user")
|
||||||
def update_domain_status(request, domain_id, status):
|
def update_domain_status(request, domain_id, status, report_id=None):
|
||||||
"""This domain seems fine"""
|
"""This domain seems fine"""
|
||||||
domain = get_object_or_404(models.LinkDomain, id=domain_id)
|
domain = get_object_or_404(models.LinkDomain, id=domain_id)
|
||||||
domain.raise_not_editable(request.user)
|
domain.raise_not_editable(request.user)
|
||||||
|
|
||||||
domain.status = status
|
domain.status = status
|
||||||
domain.save()
|
domain.save()
|
||||||
return redirect("settings-link-domain", status="pending")
|
|
||||||
|
if status == "approved":
|
||||||
|
models.Report.record_action(report_id, APPROVE_DOMAIN, request.user)
|
||||||
|
elif status == "blocked":
|
||||||
|
models.Report.record_action(report_id, BLOCK_DOMAIN, request.user)
|
||||||
|
|
||||||
|
return redirect_to_referer(request, "settings-link-domain", status="pending")
|
||||||
|
|
|
@ -8,6 +8,7 @@ from django.utils.decorators import method_decorator
|
||||||
from django.views import View
|
from django.views import View
|
||||||
|
|
||||||
from bookwyrm import forms, models
|
from bookwyrm import forms, models
|
||||||
|
from bookwyrm.models.report import USER_SUSPENSION, USER_UNSUSPENSION, USER_DELETION
|
||||||
from bookwyrm.views.helpers import redirect_to_referer
|
from bookwyrm.views.helpers import redirect_to_referer
|
||||||
from bookwyrm.settings import PAGE_LENGTH
|
from bookwyrm.settings import PAGE_LENGTH
|
||||||
|
|
||||||
|
@ -81,41 +82,42 @@ class ReportAdmin(View):
|
||||||
def post(self, request, report_id):
|
def post(self, request, report_id):
|
||||||
"""comment on a report"""
|
"""comment on a report"""
|
||||||
report = get_object_or_404(models.Report, id=report_id)
|
report = get_object_or_404(models.Report, id=report_id)
|
||||||
models.ReportComment.objects.create(
|
note = request.POST.get("note")
|
||||||
user=request.user,
|
report.comment(request.user, note)
|
||||||
report=report,
|
|
||||||
note=request.POST.get("note"),
|
|
||||||
)
|
|
||||||
return redirect("settings-report", report.id)
|
return redirect("settings-report", report.id)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required("bookwyrm.moderate_user")
|
@permission_required("bookwyrm.moderate_user")
|
||||||
def suspend_user(request, user_id):
|
def suspend_user(request, user_id, report_id=None):
|
||||||
"""mark an account as inactive"""
|
"""mark an account as inactive"""
|
||||||
user = get_object_or_404(models.User, id=user_id)
|
user = get_object_or_404(models.User, id=user_id)
|
||||||
user.is_active = False
|
user.is_active = False
|
||||||
user.deactivation_reason = "moderator_suspension"
|
user.deactivation_reason = "moderator_suspension"
|
||||||
# this isn't a full deletion, so we don't want to tell the world
|
# this isn't a full deletion, so we don't want to tell the world
|
||||||
user.save(broadcast=False)
|
user.save(broadcast=False)
|
||||||
|
|
||||||
|
models.Report.record_action(report_id, USER_SUSPENSION, request.user)
|
||||||
return redirect_to_referer(request, "settings-user", user.id)
|
return redirect_to_referer(request, "settings-user", user.id)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required("bookwyrm.moderate_user")
|
@permission_required("bookwyrm.moderate_user")
|
||||||
def unsuspend_user(request, user_id):
|
def unsuspend_user(request, user_id, report_id=None):
|
||||||
"""mark an account as inactive"""
|
"""mark an account as inactive"""
|
||||||
user = get_object_or_404(models.User, id=user_id)
|
user = get_object_or_404(models.User, id=user_id)
|
||||||
user.is_active = True
|
user.is_active = True
|
||||||
user.deactivation_reason = None
|
user.deactivation_reason = None
|
||||||
# this isn't a full deletion, so we don't want to tell the world
|
# this isn't a full deletion, so we don't want to tell the world
|
||||||
user.save(broadcast=False)
|
user.save(broadcast=False)
|
||||||
|
|
||||||
|
models.Report.record_action(report_id, USER_UNSUSPENSION, request.user)
|
||||||
return redirect_to_referer(request, "settings-user", user.id)
|
return redirect_to_referer(request, "settings-user", user.id)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required("bookwyrm.moderate_user")
|
@permission_required("bookwyrm.moderate_user")
|
||||||
def moderator_delete_user(request, user_id):
|
def moderator_delete_user(request, user_id, report_id=None):
|
||||||
"""permanently delete a user"""
|
"""permanently delete a user"""
|
||||||
user = get_object_or_404(models.User, id=user_id)
|
user = get_object_or_404(models.User, id=user_id)
|
||||||
|
|
||||||
|
@ -130,6 +132,9 @@ def moderator_delete_user(request, user_id):
|
||||||
if form.is_valid() and moderator.check_password(form.cleaned_data["password"]):
|
if form.is_valid() and moderator.check_password(form.cleaned_data["password"]):
|
||||||
user.deactivation_reason = "moderator_deletion"
|
user.deactivation_reason = "moderator_deletion"
|
||||||
user.delete()
|
user.delete()
|
||||||
|
|
||||||
|
# make a note of the fact that we did this
|
||||||
|
models.Report.record_action(report_id, USER_DELETION, request.user)
|
||||||
return redirect_to_referer(request, "settings-user", user.id)
|
return redirect_to_referer(request, "settings-user", user.id)
|
||||||
|
|
||||||
form.errors["password"] = ["Invalid password"]
|
form.errors["password"] = ["Invalid password"]
|
||||||
|
@ -140,11 +145,12 @@ def moderator_delete_user(request, user_id):
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required("bookwyrm.moderate_post")
|
@permission_required("bookwyrm.moderate_post")
|
||||||
def resolve_report(_, report_id):
|
def resolve_report(request, report_id):
|
||||||
"""mark a report as (un)resolved"""
|
"""mark a report as (un)resolved"""
|
||||||
report = get_object_or_404(models.Report, id=report_id)
|
report = get_object_or_404(models.Report, id=report_id)
|
||||||
report.resolved = not report.resolved
|
if report.resolved:
|
||||||
report.save()
|
report.reopen(request.user)
|
||||||
if not report.resolved:
|
|
||||||
return redirect("settings-report", report.id)
|
return redirect("settings-report", report.id)
|
||||||
|
|
||||||
|
report.resolve(request.user)
|
||||||
return redirect("settings-reports")
|
return redirect("settings-reports")
|
||||||
|
|
|
@ -7,6 +7,7 @@ from django.utils.decorators import method_decorator
|
||||||
from django.views import View
|
from django.views import View
|
||||||
|
|
||||||
from bookwyrm import forms, models
|
from bookwyrm import forms, models
|
||||||
|
from bookwyrm.models.report import USER_PERMS
|
||||||
from bookwyrm.settings import PAGE_LENGTH
|
from bookwyrm.settings import PAGE_LENGTH
|
||||||
|
|
||||||
|
|
||||||
|
@ -76,15 +77,16 @@ class UserAdminList(View):
|
||||||
class UserAdmin(View):
|
class UserAdmin(View):
|
||||||
"""moderate an individual user"""
|
"""moderate an individual user"""
|
||||||
|
|
||||||
def get(self, request, user):
|
# pylint: disable=unused-argument
|
||||||
|
def get(self, request, user_id, report_id=None):
|
||||||
"""user view"""
|
"""user view"""
|
||||||
user = get_object_or_404(models.User, id=user)
|
user = get_object_or_404(models.User, id=user_id)
|
||||||
data = {"user": user, "group_form": forms.UserGroupForm()}
|
data = {"user": user, "group_form": forms.UserGroupForm()}
|
||||||
return TemplateResponse(request, "settings/users/user.html", data)
|
return TemplateResponse(request, "settings/users/user.html", data)
|
||||||
|
|
||||||
def post(self, request, user):
|
def post(self, request, user_id, report_id=None):
|
||||||
"""update user group"""
|
"""update user group"""
|
||||||
user = get_object_or_404(models.User, id=user)
|
user = get_object_or_404(models.User, id=user_id)
|
||||||
|
|
||||||
if request.POST.get("groups") == "":
|
if request.POST.get("groups") == "":
|
||||||
user.groups.set([])
|
user.groups.set([])
|
||||||
|
@ -93,6 +95,10 @@ class UserAdmin(View):
|
||||||
form = forms.UserGroupForm(request.POST, instance=user)
|
form = forms.UserGroupForm(request.POST, instance=user)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
form.save(request)
|
form.save(request)
|
||||||
|
|
||||||
|
if report_id:
|
||||||
|
models.Report.record_action(report_id, USER_PERMS, request.user)
|
||||||
|
|
||||||
data = {"user": user, "group_form": form}
|
data = {"user": user, "group_form": form}
|
||||||
return TemplateResponse(request, "settings/users/user.html", data)
|
return TemplateResponse(request, "settings/users/user.html", data)
|
||||||
|
|
||||||
|
@ -106,8 +112,8 @@ class ActivateUserAdmin(View):
|
||||||
"""activate a user manually"""
|
"""activate a user manually"""
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def post(self, request, user):
|
def post(self, request, user_id):
|
||||||
"""activate user"""
|
"""activate user"""
|
||||||
user = get_object_or_404(models.User, id=user)
|
user = get_object_or_404(models.User, id=user_id)
|
||||||
user.reactivate()
|
user.reactivate()
|
||||||
return redirect("settings-user", user.id)
|
return redirect("settings-user", user.id)
|
||||||
|
|
|
@ -222,7 +222,7 @@ def maybe_redirect_local_path(request, model):
|
||||||
return redirect(new_path, permanent=True)
|
return redirect(new_path, permanent=True)
|
||||||
|
|
||||||
|
|
||||||
def redirect_to_referer(request, *args):
|
def redirect_to_referer(request, *args, **kwargs):
|
||||||
"""Redirect to the referrer, if it's in our domain, with get params"""
|
"""Redirect to the referrer, if it's in our domain, with get params"""
|
||||||
# make sure the refer is part of this instance
|
# make sure the refer is part of this instance
|
||||||
validated = validate_url_domain(request.META.get("HTTP_REFERER"))
|
validated = validate_url_domain(request.META.get("HTTP_REFERER"))
|
||||||
|
@ -231,4 +231,4 @@ def redirect_to_referer(request, *args):
|
||||||
return redirect(validated)
|
return redirect(validated)
|
||||||
|
|
||||||
# if not, use the args passed you'd normally pass to redirect()
|
# if not, use the args passed you'd normally pass to redirect()
|
||||||
return redirect(*args or "/")
|
return redirect(*args or "/", **kwargs)
|
||||||
|
|
|
@ -9,7 +9,7 @@ from django.core.exceptions import ValidationError
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.http import HttpResponse, HttpResponseBadRequest, Http404
|
from django.http import HttpResponse, HttpResponseBadRequest, Http404
|
||||||
from django.shortcuts import get_object_or_404, redirect
|
from django.shortcuts import get_object_or_404
|
||||||
from django.template.response import TemplateResponse
|
from django.template.response import TemplateResponse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
|
@ -18,6 +18,7 @@ from django.views.decorators.http import require_POST
|
||||||
|
|
||||||
from markdown import markdown
|
from markdown import markdown
|
||||||
from bookwyrm import forms, models
|
from bookwyrm import forms, models
|
||||||
|
from bookwyrm.models.report import DELETE_ITEM
|
||||||
from bookwyrm.utils import regex, sanitizer
|
from bookwyrm.utils import regex, sanitizer
|
||||||
from .helpers import handle_remote_webfinger, is_api_request
|
from .helpers import handle_remote_webfinger, is_api_request
|
||||||
from .helpers import load_date_in_user_tz_as_utc, redirect_to_referer
|
from .helpers import load_date_in_user_tz_as_utc, redirect_to_referer
|
||||||
|
@ -167,7 +168,7 @@ def format_hashtags(content, hashtags):
|
||||||
class DeleteStatus(View):
|
class DeleteStatus(View):
|
||||||
"""tombstone that bad boy"""
|
"""tombstone that bad boy"""
|
||||||
|
|
||||||
def post(self, request, status_id):
|
def post(self, request, status_id, report_id=None):
|
||||||
"""delete and tombstone a status"""
|
"""delete and tombstone a status"""
|
||||||
status = get_object_or_404(models.Status, id=status_id)
|
status = get_object_or_404(models.Status, id=status_id)
|
||||||
|
|
||||||
|
@ -176,7 +177,11 @@ class DeleteStatus(View):
|
||||||
|
|
||||||
# perform deletion
|
# perform deletion
|
||||||
status.delete()
|
status.delete()
|
||||||
return redirect("/")
|
# record deletion if it's related to a report
|
||||||
|
if report_id:
|
||||||
|
models.Report.record_action(report_id, DELETE_ITEM, request.user)
|
||||||
|
|
||||||
|
return redirect_to_referer(request, "/")
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
|
|
Loading…
Reference in a new issue