See #890: added moderation note model, serializers and views

This commit is contained in:
Eliot Berriot 2019-08-29 11:45:41 +02:00
parent 80c8610632
commit ab3bc96783
No known key found for this signature in database
GPG Key ID: DD6965E2476E5C27
13 changed files with 193 additions and 9 deletions

View File

@ -363,3 +363,19 @@ class ManageReportFilterSet(filters.FilterSet):
class Meta:
model = moderation_models.Report
fields = ["q", "is_handled", "type", "submitter_email"]
class ManageNoteFilterSet(filters.FilterSet):
q = fields.SmartSearchFilter(
config=search.SearchConfig(
search_fields={"summary": {"to": "summary"}},
filter_fields={
"uuid": {"to": "uuid"},
"author": get_actor_filter("author"),
},
)
)
class Meta:
model = moderation_models.Note
fields = ["q"]

View File

@ -3,6 +3,7 @@ from django.db import transaction
from rest_framework import serializers
from funkwhale_api.common import fields as common_fields
from funkwhale_api.common import serializers as common_serializers
from funkwhale_api.common import utils as common_utils
from funkwhale_api.federation import models as federation_models
@ -676,3 +677,27 @@ class ManageReportSerializer(serializers.ModelSerializer):
"target_owner",
"summary",
]
class ManageNoteSerializer(serializers.ModelSerializer):
author = ManageBaseActorSerializer(required=False)
target = common_fields.GenericRelation(
{
"report": {
"queryset": moderation_models.Report.objects.all(),
"id_attr": "uuid",
"id_field": serializers.UUIDField(),
},
"account": {
"queryset": federation_models.Actor.objects.all(),
"id_attr": "full_username",
"id_field": serializers.EmailField(),
"get_query": moderation_serializers.get_actor_query,
},
}
)
class Meta:
model = moderation_models.Note
fields = ["id", "uuid", "creation_date", "summary", "author", "target"]
read_only_fields = ["uuid", "creation_date", "author"]

View File

@ -18,6 +18,7 @@ moderation_router.register(
r"instance-policies", views.ManageInstancePolicyViewSet, "instance-policies"
)
moderation_router.register(r"reports", views.ManageReportViewSet, "reports")
moderation_router.register(r"notes", views.ManageNoteViewSet, "notes")
users_router = routers.OptionalSlashRouter()
users_router.register(r"users", views.ManageUserViewSet, "users")

View File

@ -478,6 +478,27 @@ class ManageReportViewSet(
ordering_fields = ["id", "creation_date", "handled_date"]
class ManageNoteViewSet(
mixins.ListModelMixin,
mixins.RetrieveModelMixin,
mixins.DestroyModelMixin,
mixins.CreateModelMixin,
viewsets.GenericViewSet,
):
lookup_field = "uuid"
queryset = (
moderation_models.Note.objects.all().order_by("-creation_date").select_related()
)
serializer_class = serializers.ManageNoteSerializer
filterset_class = filters.ManageNoteFilterSet
required_scope = "instance:notes"
ordering_fields = ["id", "creation_date"]
def perform_create(self, serializer):
author = self.request.user.actor
return serializer.save(author=author)
class ManageTagViewSet(
mixins.ListModelMixin,
mixins.RetrieveModelMixin,

View File

@ -30,7 +30,6 @@ class InstancePolicyAdmin(admin.ModelAdmin):
list_select_related = True
@admin.register(models.Report)
class ReportAdmin(admin.ModelAdmin):
list_display = [
@ -42,13 +41,8 @@ class ReportAdmin(admin.ModelAdmin):
"creation_date",
"handled_date",
]
list_filter = [
"type",
"is_handled",
]
search_fields = [
"summary",
]
list_filter = ["type", "is_handled"]
search_fields = ["summary"]
list_select_related = True

View File

@ -41,6 +41,16 @@ class UserFilterFactory(NoUpdateOnCreate, factory.DjangoModelFactory):
)
@registry.register
class NoteFactory(NoUpdateOnCreate, factory.DjangoModelFactory):
author = factory.SubFactory(federation_factories.ActorFactory)
target = None
summary = factory.Faker("paragraph")
class Meta:
model = "moderation.Note"
@registry.register
class ReportFactory(NoUpdateOnCreate, factory.DjangoModelFactory):
submitter = factory.SubFactory(federation_factories.ActorFactory)

View File

@ -0,0 +1,30 @@
# Generated by Django 2.2.4 on 2019-08-29 09:08
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import uuid
class Migration(migrations.Migration):
dependencies = [
('federation', '0020_auto_20190730_0846'),
('contenttypes', '0002_remove_content_type_name'),
('moderation', '0003_report'),
]
operations = [
migrations.CreateModel(
name='Note',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('uuid', models.UUIDField(default=uuid.uuid4, unique=True)),
('creation_date', models.DateTimeField(default=django.utils.timezone.now)),
('summary', models.TextField(max_length=50000)),
('target_id', models.IntegerField(null=True)),
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='moderation_notes', to='federation.Actor')),
('target_content_type', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')),
],
),
]

View File

@ -164,6 +164,21 @@ class Report(federation_models.FederationMixin):
return super().save(**kwargs)
class Note(models.Model):
uuid = models.UUIDField(default=uuid.uuid4, unique=True)
creation_date = models.DateTimeField(default=timezone.now)
summary = models.TextField(max_length=50000)
author = models.ForeignKey(
"federation.Actor", related_name="moderation_notes", on_delete=models.CASCADE
)
target_id = models.IntegerField(null=True)
target_content_type = models.ForeignKey(
ContentType, null=True, on_delete=models.CASCADE
)
target = GenericForeignKey("target_content_type", "target_id")
@receiver(pre_save, sender=Report)
def set_handled_date(sender, instance, **kwargs):
if instance.is_handled is True and not instance.handled_date:

View File

@ -119,7 +119,15 @@ class TrackStateSerializer(serializers.ModelSerializer):
class LibraryStateSerializer(serializers.ModelSerializer):
class Meta:
model = music_models.Library
fields = ["id", "uuid", "fid", "name", "description", "creation_date", "privacy_level"]
fields = [
"id",
"uuid",
"fid",
"name",
"description",
"creation_date",
"privacy_level",
]
@state_serializers.register(name="playlists.Playlist")

View File

@ -48,6 +48,8 @@ PERMISSIONS_CONFIGURATION = {
"write:instance:domains",
"read:instance:reports",
"write:instance:reports",
"read:instance:notes",
"write:instance:notes",
},
},
"library": {

View File

@ -35,6 +35,7 @@ BASE_SCOPES = [
Scope("instance:domains", "Access instance domains"),
Scope("instance:policies", "Access instance moderation policies"),
Scope("instance:reports", "Access instance moderation reports"),
Scope("instance:notes", "Access instance moderation notes"),
]
SCOPES = [
Scope("read", children=[s.copy("read") for s in BASE_SCOPES]),

View File

@ -550,3 +550,20 @@ def test_manage_report_serializer(factories, to_api_date):
s = serializers.ManageReportSerializer(report)
assert s.data == expected
def test_manage_note_serializer(factories, to_api_date):
actor = factories["federation.Actor"]()
note = factories["moderation.Note"](target=actor)
expected = {
"id": note.id,
"uuid": str(note.uuid),
"summary": note.summary,
"creation_date": to_api_date(note.creation_date),
"author": serializers.ManageBaseActorSerializer(note.author).data,
"target": {"type": "account", "full_username": actor.full_username},
}
s = serializers.ManageNoteSerializer(note)
assert s.data == expected

View File

@ -391,6 +391,50 @@ def test_upload_delete(factories, superuser_api_client):
assert response.status_code == 204
def test_note_create(factories, superuser_api_client):
actor = superuser_api_client.user.create_actor()
target = factories["federation.Actor"]()
data = {
"summary": "Hello",
"target": {"type": "account", "full_username": target.full_username},
}
url = reverse("api:v1:manage:moderation:notes-list")
response = superuser_api_client.post(url, data, format="json")
assert response.status_code == 201
note = actor.moderation_notes.latest("id")
assert note.target == target
assert response.data == serializers.ManageNoteSerializer(note).data
def test_note_list(factories, superuser_api_client, settings):
note = factories["moderation.Note"]()
url = reverse("api:v1:manage:moderation:notes-list")
response = superuser_api_client.get(url)
assert response.status_code == 200
assert response.data["count"] == 1
assert response.data["results"][0] == serializers.ManageNoteSerializer(note).data
def test_note_delete(factories, superuser_api_client):
note = factories["moderation.Note"]()
url = reverse("api:v1:manage:moderation:notes-detail", kwargs={"uuid": note.uuid})
response = superuser_api_client.delete(url)
assert response.status_code == 204
def test_note_detail(factories, superuser_api_client):
note = factories["moderation.Note"]()
url = reverse("api:v1:manage:moderation:notes-detail", kwargs={"uuid": note.uuid})
response = superuser_api_client.get(url)
assert response.status_code == 200
assert response.data == serializers.ManageNoteSerializer(note).data
def test_tag_detail(factories, superuser_api_client):
tag = factories["tags.Tag"]()
url = reverse("api:v1:manage:tags-detail", kwargs={"name": tag.name})