Report function and admin
This commit is contained in:
parent
b3b2c6effd
commit
e8d6dccbb2
@ -251,6 +251,7 @@ class Post(StatorModel):
|
||||
action_unboost = "{view}unboost/"
|
||||
action_delete = "{view}delete/"
|
||||
action_edit = "{view}edit/"
|
||||
action_report = "{view}report/"
|
||||
action_reply = "/compose/?reply_to={self.id}"
|
||||
admin_edit = "/djadmin/activities/post/{self.id}/change/"
|
||||
|
||||
|
@ -709,7 +709,9 @@ button,
|
||||
}
|
||||
|
||||
button.delete,
|
||||
.button.delete {
|
||||
.button.delete,
|
||||
button.danger,
|
||||
.button.danger {
|
||||
background: var(--color-delete);
|
||||
}
|
||||
|
||||
@ -833,6 +835,20 @@ table.metadata th {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
table.buttons {
|
||||
margin: -10px 0 10px 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
table.buttons th {
|
||||
padding: 5px 20px 5px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
table.buttons th button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.stats {
|
||||
margin: 0 0 20px 0;
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ from api.views import api_router, oauth
|
||||
from core import views as core
|
||||
from mediaproxy import views as mediaproxy
|
||||
from stator import views as stator
|
||||
from users.views import activitypub, admin, auth, identity, settings
|
||||
from users.views import activitypub, admin, auth, identity, report, settings
|
||||
|
||||
urlpatterns = [
|
||||
path("", core.homepage),
|
||||
@ -114,6 +114,16 @@ urlpatterns = [
|
||||
admin.IdentityEdit.as_view(),
|
||||
name="admin_identity_edit",
|
||||
),
|
||||
path(
|
||||
"admin/reports/",
|
||||
admin.ReportsRoot.as_view(),
|
||||
name="admin_reports",
|
||||
),
|
||||
path(
|
||||
"admin/reports/<id>/",
|
||||
admin.ReportView.as_view(),
|
||||
name="admin_report_view",
|
||||
),
|
||||
path(
|
||||
"admin/invites/",
|
||||
admin.Invites.as_view(),
|
||||
@ -147,6 +157,7 @@ urlpatterns = [
|
||||
path("@<handle>/inbox/", activitypub.Inbox.as_view()),
|
||||
path("@<handle>/action/", identity.ActionIdentity.as_view()),
|
||||
path("@<handle>/rss/", identity.IdentityFeed()),
|
||||
path("@<handle>/report/", report.SubmitReport.as_view()),
|
||||
# Posts
|
||||
path("compose/", compose.Compose.as_view(), name="compose"),
|
||||
path(
|
||||
@ -160,6 +171,7 @@ urlpatterns = [
|
||||
path("@<handle>/posts/<int:post_id>/boost/", posts.Boost.as_view()),
|
||||
path("@<handle>/posts/<int:post_id>/unboost/", posts.Boost.as_view(undo=True)),
|
||||
path("@<handle>/posts/<int:post_id>/delete/", posts.Delete.as_view()),
|
||||
path("@<handle>/posts/<int:post_id>/report/", report.SubmitReport.as_view()),
|
||||
path("@<handle>/posts/<int:post_id>/edit/", compose.Compose.as_view()),
|
||||
# Authentication
|
||||
path("auth/login/", auth.Login.as_view(), name="login"),
|
||||
|
@ -37,6 +37,9 @@
|
||||
<a href="{{ post.urls.view }}" role="menuitem">
|
||||
<i class="fa-solid fa-comment"></i> View Post & Replies
|
||||
</a>
|
||||
<a href="{{ post.urls.action_report }}" role="menuitem">
|
||||
<i class="fa-solid fa-flag"></i> Report
|
||||
</a>
|
||||
{% if post.author == request.identity %}
|
||||
<a href="{{ post.urls.action_edit }}" role="menuitem">
|
||||
<i class="fa-solid fa-pen-to-square"></i> Edit
|
||||
|
84
templates/admin/report_view.html
Normal file
84
templates/admin/report_view.html
Normal file
@ -0,0 +1,84 @@
|
||||
{% extends "settings/base.html" %}
|
||||
|
||||
{% block subtitle %}Report {{ report.pk }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form action="." method="POST">
|
||||
{% csrf_token %}
|
||||
<fieldset>
|
||||
<legend>Report</legend>
|
||||
<label>Report about</label>
|
||||
{% if report.subject_post %}
|
||||
{% include "activities/_mini_post.html" with post=report.subject_post %}
|
||||
{% else %}
|
||||
{% include "activities/_identity.html" with identity=report.subject_identity %}
|
||||
{% endif %}
|
||||
<label>Reported by</label>
|
||||
{% if report.source_identity %}
|
||||
{% include "activities/_identity.html" with identity=report.source_identity %}
|
||||
{% else %}
|
||||
<p>Remote server {{ report.source_domain.domain }}</p>
|
||||
{% endif %}
|
||||
<label>Complaint</label>
|
||||
<p>{{ report.complaint|linebreaks }}</p>
|
||||
{% if report.resolved %}
|
||||
<label>Resolved</label>
|
||||
<p>
|
||||
{{ report.resolved|timesince }} ago by
|
||||
<a href="{{ report.moderator.urls.view }}">{{ report.moderator.name_or_handle }}</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Moderator Notes</legend>
|
||||
{% include "forms/_field.html" with field=form.notes %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Resolution Options</legend>
|
||||
<table class="buttons">
|
||||
<tr>
|
||||
{% if report.resolved and report.valid %}
|
||||
<th><button disabled="true">Resolve Valid</button></th>
|
||||
<td>Report is already resolved as valid</td>
|
||||
{% else %}
|
||||
<th><button name="valid">Resolve Valid</button></th>
|
||||
<td>Mark report against the identity but take no further action</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
<tr>
|
||||
{% if report.resolved and not report.valid %}
|
||||
<th><button disabled="true">Resolve Invalid</button></th>
|
||||
<td>Report is already resolved as invalid</td>
|
||||
{% else %}
|
||||
<th><button name="invalid">Resolve Invalid</button></th>
|
||||
<td>Mark report as invalid and take no action</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
<tr>
|
||||
{% if report.subject_identity.limited %}
|
||||
<th><button class="danger" disabled="true">Limit</button></th>
|
||||
<td>User is already limited</td>
|
||||
{% else %}
|
||||
<th><button class="danger" name="limit">Limit</button></th>
|
||||
<td>Make them less visible on this server</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
<tr>
|
||||
{% if report.subject_identity.blocked %}
|
||||
<th><button class="danger" disabled="true">Block</button></th>
|
||||
<td>User is already blocked</td>
|
||||
{% else %}
|
||||
<th><button class="danger" name="block">Block</button></th>
|
||||
<td>Remove their existence entirely from this server</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</table>
|
||||
</fieldset>
|
||||
<div class="buttons">
|
||||
<a href="{{ report.urls.admin }}" class="button secondary left">Back</a>
|
||||
<a href="{{ report.subject_identity.urls.view }}" class="button secondary">View Profile</a>
|
||||
<a href="{{ report.subject_identity.urls.admin_edit }}" class="button secondary">Identity Admin</a>
|
||||
<button>Save Notes</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
43
templates/admin/reports.html
Normal file
43
templates/admin/reports.html
Normal file
@ -0,0 +1,43 @@
|
||||
{% extends "settings/base.html" %}
|
||||
{% load activity_tags %}
|
||||
|
||||
{% block subtitle %}Reports{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="view-options">
|
||||
{% if all %}
|
||||
<a href="." class="selected"><i class="fa-solid fa-check"></i> Show Resolved</a>
|
||||
{% else %}
|
||||
<a href=".?all=true"><i class="fa-solid fa-xmark"></i> Show Resolved</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<section class="icon-menu">
|
||||
{% for report in page_obj %}
|
||||
<a class="option" href="{{ report.urls.admin_view }}">
|
||||
<img src="{{ report.subject_identity.local_icon_url.relative }}" class="icon" alt="Avatar for {{ report.subject_identity.name_or_handle }}">
|
||||
<span class="handle">
|
||||
{{ report.subject_identity.html_name_or_handle }}
|
||||
{% if report.subject_post %}
|
||||
(post {{ report.subject_post.pk }})
|
||||
{% endif %}
|
||||
<small>
|
||||
{{ report.type|title }}
|
||||
</small>
|
||||
</span>
|
||||
<time>{{ report.created|timedeltashort }} ago</time>
|
||||
</a>
|
||||
{% empty %}
|
||||
<p class="option empty">
|
||||
There are no {% if all %}reports yet{% else %}unresolved reports{% endif %}.
|
||||
</p>
|
||||
{% endfor %}
|
||||
<div class="load-more">
|
||||
{% if page_obj.has_previous %}
|
||||
<a class="button" href=".?page={{ page_obj.previous_page_number }}{% if all %}&all=true{% endif %}">Previous Page</a>
|
||||
{% endif %}
|
||||
{% if page_obj.has_next %}
|
||||
<a class="button" href=".?page={{ page_obj.next_page_number }}{% if all %}&all=true{% endif %}">Next Page</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
@ -39,8 +39,8 @@
|
||||
<a href="{% url "admin_hashtags" %}" {% if section == "hashtags" %}class="selected"{% endif %} title="Hashtags">
|
||||
<i class="fa-solid fa-hashtag"></i> Hashtags
|
||||
</a>
|
||||
<a href="{% url "admin_tuning" %}" {% if section == "tuning" %}class="selected"{% endif %} title="Tuning">
|
||||
<i class="fa-solid fa-wrench"></i> Tuning
|
||||
<a href="{% url "admin_reports" %}" {% if section == "reports" %}class="selected"{% endif %} title="Reports">
|
||||
<i class="fa-solid fa-flag"></i> Reports
|
||||
</a>
|
||||
<a href="{% url "admin_stator" %}" {% if section == "stator" %}class="selected"{% endif %} title="Stator">
|
||||
<i class="fa-solid fa-clock-rotate-left"></i> Stator
|
||||
|
27
templates/users/report.html
Normal file
27
templates/users/report.html
Normal file
@ -0,0 +1,27 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Report{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form action="." method="POST">
|
||||
{% csrf_token %}
|
||||
<fieldset>
|
||||
<legend>Report</legend>
|
||||
<label>Reporting</label>
|
||||
{% if post %}
|
||||
{% include "activities/_mini_post.html" %}
|
||||
{% else %}
|
||||
{% include "activities/_identity.html" %}
|
||||
{% endif %}
|
||||
{% include "forms/_field.html" with field=form.type %}
|
||||
{% include "forms/_field.html" with field=form.complaint %}
|
||||
{% if not identity.local %}
|
||||
{% include "forms/_field.html" with field=form.forward %}
|
||||
{% endif %}
|
||||
</fieldset>
|
||||
|
||||
<div class="buttons">
|
||||
<button>Send Report</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
13
templates/users/report_sent.html
Normal file
13
templates/users/report_sent.html
Normal file
@ -0,0 +1,13 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Report Sent{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form action="." method="POST">
|
||||
{% csrf_token %}
|
||||
<fieldset>
|
||||
<legend>Report</legend>
|
||||
<p>Your report has been sent.</p>
|
||||
</fieldset>
|
||||
</form>
|
||||
{% endblock %}
|
@ -9,6 +9,7 @@ from users.models import (
|
||||
InboxMessage,
|
||||
Invite,
|
||||
PasswordReset,
|
||||
Report,
|
||||
User,
|
||||
UserEvent,
|
||||
)
|
||||
@ -113,3 +114,8 @@ class InboxMessageAdmin(admin.ModelAdmin):
|
||||
@admin.register(Invite)
|
||||
class InviteAdmin(admin.ModelAdmin):
|
||||
list_display = ["id", "created", "token", "note"]
|
||||
|
||||
|
||||
@admin.register(Report)
|
||||
class ReportAdmin(admin.ModelAdmin):
|
||||
list_display = ["id", "created", "resolved", "type", "subject_identity"]
|
||||
|
119
users/migrations/0005_report.py
Normal file
119
users/migrations/0005_report.py
Normal file
@ -0,0 +1,119 @@
|
||||
# Generated by Django 4.1.4 on 2022-12-17 20:38
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
import stator.models
|
||||
import users.models.report
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("activities", "0004_emoji_post_emojis"),
|
||||
("users", "0004_identity_admin_notes_identity_restriction_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="Report",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("state_ready", models.BooleanField(default=True)),
|
||||
("state_changed", models.DateTimeField(auto_now_add=True)),
|
||||
("state_attempted", models.DateTimeField(blank=True, null=True)),
|
||||
("state_locked_until", models.DateTimeField(blank=True, null=True)),
|
||||
(
|
||||
"state",
|
||||
stator.models.StateField(
|
||||
choices=[("new", "new"), ("sent", "sent")],
|
||||
default="new",
|
||||
graph=users.models.report.ReportStates,
|
||||
max_length=100,
|
||||
),
|
||||
),
|
||||
(
|
||||
"type",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("spam", "Spam"),
|
||||
("hateful", "Hateful"),
|
||||
("illegal", "Illegal"),
|
||||
("remote", "Remote"),
|
||||
("other", "Other"),
|
||||
],
|
||||
max_length=100,
|
||||
),
|
||||
),
|
||||
("complaint", models.TextField()),
|
||||
("forward", models.BooleanField(default=False)),
|
||||
("valid", models.BooleanField(null=True)),
|
||||
("seen", models.DateTimeField(blank=True, null=True)),
|
||||
("resolved", models.DateTimeField(blank=True, null=True)),
|
||||
("notes", models.TextField(blank=True, null=True)),
|
||||
("created", models.DateTimeField(auto_now_add=True)),
|
||||
("updated", models.DateTimeField(auto_now=True)),
|
||||
(
|
||||
"moderator",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="moderated_reports",
|
||||
to="users.identity",
|
||||
),
|
||||
),
|
||||
(
|
||||
"source_domain",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="filed_reports",
|
||||
to="users.domain",
|
||||
),
|
||||
),
|
||||
(
|
||||
"source_identity",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="filed_reports",
|
||||
to="users.identity",
|
||||
),
|
||||
),
|
||||
(
|
||||
"subject_identity",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="reports",
|
||||
to="users.identity",
|
||||
),
|
||||
),
|
||||
(
|
||||
"subject_post",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="reports",
|
||||
to="activities.post",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"abstract": False,
|
||||
},
|
||||
),
|
||||
]
|
@ -5,6 +5,7 @@ from .identity import Identity, IdentityStates # noqa
|
||||
from .inbox_message import InboxMessage, InboxMessageStates # noqa
|
||||
from .invite import Invite # noqa
|
||||
from .password_reset import PasswordReset # noqa
|
||||
from .report import Report # noqa
|
||||
from .system_actor import SystemActor # noqa
|
||||
from .user import User # noqa
|
||||
from .user_event import UserEvent # noqa
|
||||
|
129
users/models/report.py
Normal file
129
users/models/report.py
Normal file
@ -0,0 +1,129 @@
|
||||
import httpx
|
||||
import urlman
|
||||
from django.db import models
|
||||
|
||||
from core.ld import canonicalise
|
||||
from stator.models import State, StateField, StateGraph, StatorModel
|
||||
from users.models import SystemActor
|
||||
|
||||
|
||||
class ReportStates(StateGraph):
|
||||
new = State(try_interval=600)
|
||||
sent = State()
|
||||
|
||||
new.transitions_to(sent)
|
||||
|
||||
@classmethod
|
||||
async def handle_new(cls, instance: "Report"):
|
||||
"""
|
||||
Sends the report to the remote server if we need to
|
||||
"""
|
||||
report = await instance.afetch_full()
|
||||
if report.forward and not report.subject_identity.domain.local:
|
||||
system_actor = SystemActor()
|
||||
try:
|
||||
await system_actor.signed_request(
|
||||
method="post",
|
||||
uri=report.subject_identity.inbox_uri,
|
||||
body=canonicalise(report.to_ap()),
|
||||
)
|
||||
except httpx.RequestError:
|
||||
return
|
||||
return cls.sent
|
||||
|
||||
|
||||
class Report(StatorModel):
|
||||
"""
|
||||
A complaint about a user or post.
|
||||
"""
|
||||
|
||||
class Types(models.TextChoices):
|
||||
spam = "spam"
|
||||
hateful = "hateful"
|
||||
illegal = "illegal"
|
||||
remote = "remote"
|
||||
other = "other"
|
||||
|
||||
state = StateField(ReportStates)
|
||||
|
||||
subject_identity = models.ForeignKey(
|
||||
"users.Identity",
|
||||
on_delete=models.CASCADE,
|
||||
blank=True,
|
||||
null=True,
|
||||
related_name="reports",
|
||||
)
|
||||
subject_post = models.ForeignKey(
|
||||
"activities.Post",
|
||||
on_delete=models.SET_NULL,
|
||||
blank=True,
|
||||
null=True,
|
||||
related_name="reports",
|
||||
)
|
||||
|
||||
source_identity = models.ForeignKey(
|
||||
"users.Identity",
|
||||
on_delete=models.CASCADE,
|
||||
blank=True,
|
||||
null=True,
|
||||
related_name="filed_reports",
|
||||
)
|
||||
source_domain = models.ForeignKey(
|
||||
"users.Domain",
|
||||
on_delete=models.CASCADE,
|
||||
blank=True,
|
||||
null=True,
|
||||
related_name="filed_reports",
|
||||
)
|
||||
|
||||
moderator = models.ForeignKey(
|
||||
"users.Identity",
|
||||
on_delete=models.SET_NULL,
|
||||
blank=True,
|
||||
null=True,
|
||||
related_name="moderated_reports",
|
||||
)
|
||||
|
||||
type = models.CharField(max_length=100, choices=Types.choices)
|
||||
complaint = models.TextField()
|
||||
forward = models.BooleanField(default=False)
|
||||
valid = models.BooleanField(null=True)
|
||||
|
||||
seen = models.DateTimeField(blank=True, null=True)
|
||||
resolved = models.DateTimeField(blank=True, null=True)
|
||||
notes = models.TextField(blank=True, null=True)
|
||||
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
updated = models.DateTimeField(auto_now=True)
|
||||
|
||||
class urls(urlman.Urls):
|
||||
admin = "/admin/reports/"
|
||||
admin_view = "{admin}{self.pk}/"
|
||||
|
||||
### ActivityPub ###
|
||||
|
||||
async def afetch_full(self) -> "Report":
|
||||
return await Report.objects.select_related(
|
||||
"source_identity",
|
||||
"source_domain",
|
||||
"subject_identity__domain",
|
||||
"subject_identity",
|
||||
"subject_post",
|
||||
).aget(pk=self.pk)
|
||||
|
||||
def to_ap(self):
|
||||
system_actor = SystemActor()
|
||||
if self.subject_post:
|
||||
objects = [
|
||||
self.subject_post.object_uri,
|
||||
self.subject_identity.actor_uri,
|
||||
]
|
||||
else:
|
||||
objects = self.subject_identity.actor_uri
|
||||
return {
|
||||
"id": f"https://{self.source_domain.uri_domain}/reports/{self.id}/",
|
||||
"type": "Flag",
|
||||
"actor": system_actor.actor_uri,
|
||||
"object": objects,
|
||||
"content": self.complaint,
|
||||
}
|
@ -17,6 +17,7 @@ from users.views.admin.hashtags import ( # noqa
|
||||
Hashtags,
|
||||
)
|
||||
from users.views.admin.identities import IdentitiesRoot, IdentityEdit # noqa
|
||||
from users.views.admin.reports import ReportsRoot, ReportView # noqa
|
||||
from users.views.admin.settings import ( # noqa
|
||||
BasicSettings,
|
||||
PoliciesSettings,
|
||||
|
80
users/views/admin/reports.py
Normal file
80
users/views/admin/reports.py
Normal file
@ -0,0 +1,80 @@
|
||||
from django import forms
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.utils import timezone
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.generic import FormView, ListView
|
||||
|
||||
from users.decorators import admin_required
|
||||
from users.models import Identity, Report
|
||||
|
||||
|
||||
@method_decorator(admin_required, name="dispatch")
|
||||
class ReportsRoot(ListView):
|
||||
|
||||
template_name = "admin/reports.html"
|
||||
paginate_by = 30
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.query = request.GET.get("query")
|
||||
self.all = request.GET.get("all")
|
||||
self.extra_context = {
|
||||
"section": "reports",
|
||||
"all": self.all,
|
||||
}
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
reports = Report.objects.select_related(
|
||||
"subject_post", "subject_identity"
|
||||
).order_by("created")
|
||||
if not self.all:
|
||||
reports = reports.filter(resolved__isnull=True)
|
||||
return reports
|
||||
|
||||
|
||||
@method_decorator(admin_required, name="dispatch")
|
||||
class ReportView(FormView):
|
||||
|
||||
template_name = "admin/report_view.html"
|
||||
extra_context = {
|
||||
"section": "reports",
|
||||
}
|
||||
|
||||
class form_class(forms.Form):
|
||||
notes = forms.CharField(widget=forms.Textarea, required=False)
|
||||
|
||||
def dispatch(self, request, id, *args, **kwargs):
|
||||
self.report = get_object_or_404(Report, id=id)
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
if "limit" in request.POST:
|
||||
self.report.subject_identity.restriction = Identity.Restriction.limited
|
||||
self.report.subject_identity.save()
|
||||
if "block" in request.POST:
|
||||
self.report.subject_identity.restriction = Identity.Restriction.blocked
|
||||
self.report.subject_identity.save()
|
||||
if "valid" in request.POST:
|
||||
self.report.resolved = timezone.now()
|
||||
self.report.valid = True
|
||||
self.report.moderator = self.request.identity
|
||||
self.report.save()
|
||||
if "invalid" in request.POST:
|
||||
self.report.resolved = timezone.now()
|
||||
self.report.valid = False
|
||||
self.report.moderator = self.request.identity
|
||||
self.report.save()
|
||||
return super().post(request, *args, **kwargs)
|
||||
|
||||
def get_initial(self):
|
||||
return {"notes": self.report.notes}
|
||||
|
||||
def form_valid(self, form):
|
||||
self.report.notes = form.cleaned_data["notes"]
|
||||
self.report.save()
|
||||
return redirect(".")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["report"] = self.report
|
||||
return context
|
76
users/views/report.py
Normal file
76
users/views/report.py
Normal file
@ -0,0 +1,76 @@
|
||||
from django import forms
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.generic import FormView
|
||||
|
||||
from users.decorators import identity_required
|
||||
from users.models import Report
|
||||
from users.shortcuts import by_handle_or_404
|
||||
|
||||
|
||||
@method_decorator(identity_required, name="dispatch")
|
||||
class SubmitReport(FormView):
|
||||
"""
|
||||
Submits a report on a user or a post
|
||||
"""
|
||||
|
||||
template_name = "users/report.html"
|
||||
|
||||
class form_class(forms.Form):
|
||||
type = forms.ChoiceField(
|
||||
choices=[
|
||||
("", "------"),
|
||||
("spam", "Spam or inappropriate advertising"),
|
||||
("hateful", "Hateful, abusive, or violent speech"),
|
||||
("other", "Something else"),
|
||||
],
|
||||
label="Why are you reporting this?",
|
||||
)
|
||||
|
||||
complaint = forms.CharField(
|
||||
widget=forms.Textarea,
|
||||
help_text="Please describe why you think this should be removed",
|
||||
)
|
||||
|
||||
forward = forms.BooleanField(
|
||||
widget=forms.Select(
|
||||
choices=[
|
||||
(False, "Do not send to other server"),
|
||||
(True, "Send to other server"),
|
||||
]
|
||||
),
|
||||
help_text="Should we also send an anonymous copy of this to their server?",
|
||||
required=False,
|
||||
)
|
||||
|
||||
def dispatch(self, request, handle, post_id=None):
|
||||
self.identity = by_handle_or_404(self.request, handle, local=False)
|
||||
if post_id:
|
||||
self.post_obj = get_object_or_404(self.identity.posts, pk=post_id)
|
||||
else:
|
||||
self.post_obj = None
|
||||
return super().dispatch(request)
|
||||
|
||||
def form_valid(self, form):
|
||||
# Create the report
|
||||
report = Report.objects.create(
|
||||
type=form.cleaned_data["type"],
|
||||
complaint=form.cleaned_data["complaint"],
|
||||
subject_identity=self.identity,
|
||||
subject_post=self.post_obj,
|
||||
source_identity=self.request.identity,
|
||||
source_domain=self.request.identity.domain,
|
||||
forward=form.cleaned_data.get("forward", False),
|
||||
)
|
||||
# Show a thanks page
|
||||
return render(
|
||||
self.request,
|
||||
"users/report_sent.html",
|
||||
{"report": report},
|
||||
)
|
||||
|
||||
def get_context_data(self, *args, **kwargs):
|
||||
context = super().get_context_data(*args, **kwargs)
|
||||
context["identity"] = self.identity
|
||||
context["post"] = self.post_obj
|
||||
return context
|
Reference in New Issue
Block a user