mirror of
https://code.eliotberriot.com/funkwhale/funkwhale.git
synced 2025-10-06 05:09:56 +02:00
Merge branch '890-mods-workflow' into 'develop'
moderator interface for reports (#890) See merge request funkwhale/funkwhale!866
This commit is contained in:
commit
bc39b18173
48 changed files with 2164 additions and 43 deletions
|
@ -57,6 +57,64 @@ class SmartSearchFilter(django_filters.CharFilter):
|
|||
return search.apply(qs, cleaned)
|
||||
|
||||
|
||||
def get_generic_filter_query(value, relation_name, choices):
|
||||
parts = value.split(":", 1)
|
||||
type = parts[0]
|
||||
try:
|
||||
conf = choices[type]
|
||||
except KeyError:
|
||||
raise forms.ValidationError("Invalid type")
|
||||
related_queryset = conf["queryset"]
|
||||
related_model = related_queryset.model
|
||||
filter_query = models.Q(
|
||||
**{
|
||||
"{}_content_type__app_label".format(
|
||||
relation_name
|
||||
): related_model._meta.app_label,
|
||||
"{}_content_type__model".format(
|
||||
relation_name
|
||||
): related_model._meta.model_name,
|
||||
}
|
||||
)
|
||||
if len(parts) > 1:
|
||||
id_attr = conf.get("id_attr", "id")
|
||||
id_field = conf.get("id_field", serializers.IntegerField(min_value=1))
|
||||
try:
|
||||
id_value = parts[1]
|
||||
id_value = id_field.to_internal_value(id_value)
|
||||
except (TypeError, KeyError, serializers.ValidationError):
|
||||
raise forms.ValidationError("Invalid id")
|
||||
query_getter = conf.get(
|
||||
"get_query", lambda attr, value: models.Q(**{attr: value})
|
||||
)
|
||||
obj_query = query_getter(id_attr, id_value)
|
||||
try:
|
||||
obj = related_queryset.get(obj_query)
|
||||
except related_queryset.model.DoesNotExist:
|
||||
raise forms.ValidationError("Invalid object")
|
||||
filter_query &= models.Q(**{"{}_id".format(relation_name): obj.id})
|
||||
|
||||
return filter_query
|
||||
|
||||
|
||||
class GenericRelationFilter(django_filters.CharFilter):
|
||||
def __init__(self, relation_name, choices, *args, **kwargs):
|
||||
self.relation_name = relation_name
|
||||
self.choices = choices
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def filter(self, qs, value):
|
||||
if not value:
|
||||
return qs
|
||||
try:
|
||||
filter_query = get_generic_filter_query(
|
||||
value, relation_name=self.relation_name, choices=self.choices
|
||||
)
|
||||
except forms.ValidationError:
|
||||
return qs.none()
|
||||
return qs.filter(filter_query)
|
||||
|
||||
|
||||
class GenericRelation(serializers.JSONField):
|
||||
def __init__(self, choices, *args, **kwargs):
|
||||
self.choices = choices
|
||||
|
@ -68,14 +126,16 @@ class GenericRelation(serializers.JSONField):
|
|||
return
|
||||
type = None
|
||||
id = None
|
||||
id_attr = None
|
||||
for key, choice in self.choices.items():
|
||||
if isinstance(value, choice["queryset"].model):
|
||||
type = key
|
||||
id = getattr(value, choice.get("id_attr", "id"))
|
||||
id_attr = choice.get("id_attr", "id")
|
||||
id = getattr(value, id_attr)
|
||||
break
|
||||
|
||||
if type:
|
||||
return {"type": type, "id": id}
|
||||
return {"type": type, id_attr: id}
|
||||
|
||||
def to_internal_value(self, v):
|
||||
v = super().to_internal_value(v)
|
||||
|
|
|
@ -15,7 +15,7 @@ class NoneObject(object):
|
|||
|
||||
|
||||
NONE = NoneObject()
|
||||
NULL_BOOLEAN_CHOICES = [
|
||||
BOOLEAN_CHOICES = [
|
||||
(True, True),
|
||||
("true", True),
|
||||
("True", True),
|
||||
|
@ -26,6 +26,8 @@ NULL_BOOLEAN_CHOICES = [
|
|||
("False", False),
|
||||
("0", False),
|
||||
("no", False),
|
||||
]
|
||||
NULL_BOOLEAN_CHOICES = BOOLEAN_CHOICES + [
|
||||
("None", NONE),
|
||||
("none", NONE),
|
||||
("Null", NONE),
|
||||
|
@ -76,10 +78,26 @@ def clean_null_boolean_filter(v):
|
|||
return v
|
||||
|
||||
|
||||
def clean_boolean_filter(v):
|
||||
return CoerceChoiceField(choices=BOOLEAN_CHOICES).clean(v)
|
||||
|
||||
|
||||
def get_null_boolean_filter(name):
|
||||
return {"handler": lambda v: Q(**{name: clean_null_boolean_filter(v)})}
|
||||
|
||||
|
||||
def get_boolean_filter(name):
|
||||
return {"handler": lambda v: Q(**{name: clean_boolean_filter(v)})}
|
||||
|
||||
|
||||
def get_generic_relation_filter(relation_name, choices):
|
||||
return {
|
||||
"handler": lambda v: fields.get_generic_filter_query(
|
||||
v, relation_name=relation_name, choices=choices
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
class DummyTypedMultipleChoiceField(forms.TypedMultipleChoiceField):
|
||||
def valid_value(self, value):
|
||||
return True
|
||||
|
@ -142,7 +160,7 @@ class MutationFilter(filters.FilterSet):
|
|||
"domain": {"to": "created_by__domain__name__iexact"},
|
||||
"is_approved": get_null_boolean_filter("is_approved"),
|
||||
"target": {"handler": filter_target},
|
||||
"is_applied": {"to": "is_applied"},
|
||||
"is_applied": get_boolean_filter("is_applied"),
|
||||
},
|
||||
)
|
||||
)
|
||||
|
|
|
@ -5,11 +5,14 @@ import django_filters
|
|||
from django_filters import rest_framework as filters
|
||||
|
||||
from funkwhale_api.common import fields
|
||||
from funkwhale_api.common import filters as common_filters
|
||||
from funkwhale_api.common import search
|
||||
|
||||
from funkwhale_api.federation import models as federation_models
|
||||
from funkwhale_api.federation import utils as federation_utils
|
||||
from funkwhale_api.moderation import models as moderation_models
|
||||
from funkwhale_api.moderation import serializers as moderation_serializers
|
||||
from funkwhale_api.moderation import utils as moderation_utils
|
||||
from funkwhale_api.music import models as music_models
|
||||
from funkwhale_api.users import models as users_models
|
||||
from funkwhale_api.tags import models as tags_models
|
||||
|
@ -26,7 +29,7 @@ class ActorField(forms.CharField):
|
|||
|
||||
def get_actor_filter(actor_field):
|
||||
def handler(v):
|
||||
federation_utils.get_actor_from_username_data_query(actor_field, v)
|
||||
return federation_utils.get_actor_from_username_data_query(actor_field, v)
|
||||
|
||||
return {"field": ActorField(), "handler": handler}
|
||||
|
||||
|
@ -322,6 +325,10 @@ class ManageInstancePolicyFilterSet(filters.FilterSet):
|
|||
]
|
||||
)
|
||||
|
||||
target_domain = filters.CharFilter("target_domain__name")
|
||||
target_account_domain = filters.CharFilter("target_actor__domain__name")
|
||||
target_account_username = filters.CharFilter("target_actor__preferred_username")
|
||||
|
||||
class Meta:
|
||||
model = moderation_models.InstancePolicy
|
||||
fields = [
|
||||
|
@ -330,6 +337,9 @@ class ManageInstancePolicyFilterSet(filters.FilterSet):
|
|||
"silence_activity",
|
||||
"silence_notifications",
|
||||
"reject_media",
|
||||
"target_domain",
|
||||
"target_account_domain",
|
||||
"target_account_username",
|
||||
]
|
||||
|
||||
|
||||
|
@ -339,3 +349,48 @@ class ManageTagFilterSet(filters.FilterSet):
|
|||
class Meta:
|
||||
model = tags_models.Tag
|
||||
fields = ["q"]
|
||||
|
||||
|
||||
class ManageReportFilterSet(filters.FilterSet):
|
||||
q = fields.SmartSearchFilter(
|
||||
config=search.SearchConfig(
|
||||
search_fields={"summary": {"to": "summary"}},
|
||||
filter_fields={
|
||||
"uuid": {"to": "uuid"},
|
||||
"id": {"to": "id"},
|
||||
"resolved": common_filters.get_boolean_filter("is_handled"),
|
||||
"domain": {"to": "target_owner__domain_id"},
|
||||
"category": {"to": "type"},
|
||||
"submitter": get_actor_filter("submitter"),
|
||||
"assigned_to": get_actor_filter("assigned_to"),
|
||||
"target_owner": get_actor_filter("target_owner"),
|
||||
"submitter_email": {"to": "submitter_email"},
|
||||
"target": common_filters.get_generic_relation_filter(
|
||||
"target", moderation_serializers.TARGET_CONFIG
|
||||
),
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
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"),
|
||||
"target": common_filters.get_generic_relation_filter(
|
||||
"target", moderation_utils.NOTE_TARGET_FIELDS
|
||||
),
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = moderation_models.Note
|
||||
fields = ["q"]
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
from django.conf import settings
|
||||
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
|
||||
from funkwhale_api.federation import fields as federation_fields
|
||||
from funkwhale_api.federation import tasks as federation_tasks
|
||||
from funkwhale_api.moderation import models as moderation_models
|
||||
from funkwhale_api.moderation import serializers as moderation_serializers
|
||||
from funkwhale_api.moderation import utils as moderation_utils
|
||||
from funkwhale_api.music import models as music_models
|
||||
from funkwhale_api.music import serializers as music_serializers
|
||||
from funkwhale_api.tags import models as tags_models
|
||||
|
@ -182,6 +186,8 @@ class ManageDomainActionSerializer(common_serializers.ActionSerializer):
|
|||
|
||||
|
||||
class ManageBaseActorSerializer(serializers.ModelSerializer):
|
||||
is_local = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = federation_models.Actor
|
||||
fields = [
|
||||
|
@ -200,9 +206,13 @@ class ManageBaseActorSerializer(serializers.ModelSerializer):
|
|||
"outbox_url",
|
||||
"shared_inbox_url",
|
||||
"manually_approves_followers",
|
||||
"is_local",
|
||||
]
|
||||
read_only_fields = ["creation_date", "instance_policy"]
|
||||
|
||||
def get_is_local(self, o):
|
||||
return o.domain_id == settings.FEDERATION_HOSTNAME
|
||||
|
||||
|
||||
class ManageActorSerializer(ManageBaseActorSerializer):
|
||||
uploads_count = serializers.SerializerMethodField()
|
||||
|
@ -629,3 +639,64 @@ class ManageTagActionSerializer(common_serializers.ActionSerializer):
|
|||
@transaction.atomic
|
||||
def handle_delete(self, objects):
|
||||
return objects.delete()
|
||||
|
||||
|
||||
class ManageBaseNoteSerializer(serializers.ModelSerializer):
|
||||
author = ManageBaseActorSerializer(required=False, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = moderation_models.Note
|
||||
fields = ["id", "uuid", "creation_date", "summary", "author"]
|
||||
read_only_fields = ["uuid", "creation_date", "author"]
|
||||
|
||||
|
||||
class ManageNoteSerializer(ManageBaseNoteSerializer):
|
||||
target = common_fields.GenericRelation(moderation_utils.NOTE_TARGET_FIELDS)
|
||||
|
||||
class Meta(ManageBaseNoteSerializer.Meta):
|
||||
fields = ManageBaseNoteSerializer.Meta.fields + ["target"]
|
||||
|
||||
|
||||
class ManageReportSerializer(serializers.ModelSerializer):
|
||||
assigned_to = ManageBaseActorSerializer()
|
||||
target_owner = ManageBaseActorSerializer()
|
||||
submitter = ManageBaseActorSerializer()
|
||||
target = moderation_serializers.TARGET_FIELD
|
||||
notes = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = moderation_models.Report
|
||||
fields = [
|
||||
"id",
|
||||
"uuid",
|
||||
"fid",
|
||||
"creation_date",
|
||||
"handled_date",
|
||||
"summary",
|
||||
"type",
|
||||
"target",
|
||||
"target_state",
|
||||
"is_handled",
|
||||
"assigned_to",
|
||||
"target_owner",
|
||||
"submitter",
|
||||
"submitter_email",
|
||||
"notes",
|
||||
]
|
||||
read_only_fields = [
|
||||
"id",
|
||||
"uuid",
|
||||
"fid",
|
||||
"submitter",
|
||||
"submitter_email",
|
||||
"creation_date",
|
||||
"handled_date",
|
||||
"target",
|
||||
"target_state",
|
||||
"target_owner",
|
||||
"summary",
|
||||
]
|
||||
|
||||
def get_notes(self, o):
|
||||
notes = getattr(o, "_prefetched_notes", [])
|
||||
return ManageBaseNoteSerializer(notes, many=True).data
|
||||
|
|
|
@ -17,6 +17,8 @@ moderation_router = routers.OptionalSlashRouter()
|
|||
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")
|
||||
|
|
|
@ -459,6 +459,60 @@ class ManageInstancePolicyViewSet(
|
|||
serializer.save(actor=self.request.user.actor)
|
||||
|
||||
|
||||
class ManageReportViewSet(
|
||||
mixins.ListModelMixin,
|
||||
mixins.RetrieveModelMixin,
|
||||
mixins.UpdateModelMixin,
|
||||
viewsets.GenericViewSet,
|
||||
):
|
||||
lookup_field = "uuid"
|
||||
queryset = (
|
||||
moderation_models.Report.objects.all()
|
||||
.order_by("-creation_date")
|
||||
.select_related(
|
||||
"submitter", "target_owner", "assigned_to", "target_content_type"
|
||||
)
|
||||
.prefetch_related("target")
|
||||
.prefetch_related(
|
||||
Prefetch(
|
||||
"notes",
|
||||
queryset=moderation_models.Note.objects.order_by(
|
||||
"creation_date"
|
||||
).select_related("author"),
|
||||
to_attr="_prefetched_notes",
|
||||
)
|
||||
)
|
||||
)
|
||||
serializer_class = serializers.ManageReportSerializer
|
||||
filterset_class = filters.ManageReportFilterSet
|
||||
required_scope = "instance:reports"
|
||||
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("author", "target_content_type")
|
||||
.prefetch_related("target")
|
||||
)
|
||||
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,
|
||||
|
|
|
@ -30,6 +30,22 @@ class InstancePolicyAdmin(admin.ModelAdmin):
|
|||
list_select_related = True
|
||||
|
||||
|
||||
@admin.register(models.Report)
|
||||
class ReportAdmin(admin.ModelAdmin):
|
||||
list_display = [
|
||||
"uuid",
|
||||
"submitter",
|
||||
"type",
|
||||
"assigned_to",
|
||||
"is_handled",
|
||||
"creation_date",
|
||||
"handled_date",
|
||||
]
|
||||
list_filter = ["type", "is_handled"]
|
||||
search_fields = ["summary"]
|
||||
list_select_related = True
|
||||
|
||||
|
||||
@admin.register(models.UserFilter)
|
||||
class UserFilterAdmin(admin.ModelAdmin):
|
||||
list_display = ["uuid", "user", "target_artist", "creation_date"]
|
||||
|
|
|
@ -5,6 +5,8 @@ from funkwhale_api.federation import factories as federation_factories
|
|||
from funkwhale_api.music import factories as music_factories
|
||||
from funkwhale_api.users import factories as users_factories
|
||||
|
||||
from . import serializers
|
||||
|
||||
|
||||
@registry.register
|
||||
class InstancePolicyFactory(NoUpdateOnCreate, factory.DjangoModelFactory):
|
||||
|
@ -39,10 +41,20 @@ 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)
|
||||
target = None
|
||||
target = factory.SubFactory(music_factories.ArtistFactory)
|
||||
summary = factory.Faker("paragraph")
|
||||
type = "other"
|
||||
|
||||
|
@ -51,3 +63,13 @@ class ReportFactory(NoUpdateOnCreate, factory.DjangoModelFactory):
|
|||
|
||||
class Params:
|
||||
anonymous = factory.Trait(actor=None, submitter_email=factory.Faker("email"))
|
||||
assigned = factory.Trait(
|
||||
assigned_to=factory.SubFactory(federation_factories.ActorFactory)
|
||||
)
|
||||
|
||||
@factory.post_generation
|
||||
def _set_target_owner(self, create, extracted, **kwargs):
|
||||
if not self.target:
|
||||
return
|
||||
|
||||
self.target_owner = serializers.get_target_owner(self.target)
|
||||
|
|
30
api/funkwhale_api/moderation/migrations/0004_note.py
Normal file
30
api/funkwhale_api/moderation/migrations/0004_note.py
Normal 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')),
|
||||
],
|
||||
),
|
||||
]
|
|
@ -1,11 +1,12 @@
|
|||
import urllib.parse
|
||||
import uuid
|
||||
|
||||
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.contrib.postgres.fields import JSONField
|
||||
from django.db import models
|
||||
from django.db.models.signals import pre_save
|
||||
from django.dispatch import receiver
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
|
||||
|
@ -147,6 +148,10 @@ class Report(federation_models.FederationMixin):
|
|||
# delete
|
||||
target_state = JSONField(null=True)
|
||||
|
||||
notes = GenericRelation(
|
||||
"Note", content_type_field="target_content_type", object_id_field="target_id"
|
||||
)
|
||||
|
||||
def get_federation_id(self):
|
||||
if self.fid:
|
||||
return self.fid
|
||||
|
@ -160,3 +165,26 @@ class Report(federation_models.FederationMixin):
|
|||
self.fid = self.get_federation_id()
|
||||
|
||||
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:
|
||||
instance.handled_date = timezone.now()
|
||||
elif not instance.is_handled:
|
||||
instance.handled_date = None
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
import persisting_theory
|
||||
import json
|
||||
import urllib.parse
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.serializers.json import DjangoJSONEncoder
|
||||
import persisting_theory
|
||||
from rest_framework import serializers
|
||||
|
||||
from funkwhale_api.common import fields as common_fields
|
||||
|
@ -117,7 +121,15 @@ class TrackStateSerializer(serializers.ModelSerializer):
|
|||
class LibraryStateSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = music_models.Library
|
||||
fields = ["id", "fid", "name", "description", "creation_date", "privacy_level"]
|
||||
fields = [
|
||||
"id",
|
||||
"uuid",
|
||||
"fid",
|
||||
"name",
|
||||
"description",
|
||||
"creation_date",
|
||||
"privacy_level",
|
||||
]
|
||||
|
||||
|
||||
@state_serializers.register(name="playlists.Playlist")
|
||||
|
@ -135,6 +147,7 @@ class ActorStateSerializer(serializers.ModelSerializer):
|
|||
"fid",
|
||||
"name",
|
||||
"preferred_username",
|
||||
"full_username",
|
||||
"summary",
|
||||
"domain",
|
||||
"type",
|
||||
|
@ -160,26 +173,28 @@ def get_target_owner(target):
|
|||
return mapping[target.__class__](target)
|
||||
|
||||
|
||||
TARGET_CONFIG = {
|
||||
"artist": {"queryset": music_models.Artist.objects.all()},
|
||||
"album": {"queryset": music_models.Album.objects.all()},
|
||||
"track": {"queryset": music_models.Track.objects.all()},
|
||||
"library": {
|
||||
"queryset": music_models.Library.objects.all(),
|
||||
"id_attr": "uuid",
|
||||
"id_field": serializers.UUIDField(),
|
||||
},
|
||||
"playlist": {"queryset": playlists_models.Playlist.objects.all()},
|
||||
"account": {
|
||||
"queryset": federation_models.Actor.objects.all(),
|
||||
"id_attr": "full_username",
|
||||
"id_field": serializers.EmailField(),
|
||||
"get_query": get_actor_query,
|
||||
},
|
||||
}
|
||||
TARGET_FIELD = common_fields.GenericRelation(TARGET_CONFIG)
|
||||
|
||||
|
||||
class ReportSerializer(serializers.ModelSerializer):
|
||||
target = common_fields.GenericRelation(
|
||||
{
|
||||
"artist": {"queryset": music_models.Artist.objects.all()},
|
||||
"album": {"queryset": music_models.Album.objects.all()},
|
||||
"track": {"queryset": music_models.Track.objects.all()},
|
||||
"library": {
|
||||
"queryset": music_models.Library.objects.all(),
|
||||
"id_attr": "uuid",
|
||||
"id_field": serializers.UUIDField(),
|
||||
},
|
||||
"playlist": {"queryset": playlists_models.Playlist.objects.all()},
|
||||
"account": {
|
||||
"queryset": federation_models.Actor.objects.all(),
|
||||
"id_attr": "full_username",
|
||||
"id_field": serializers.EmailField(),
|
||||
"get_query": get_actor_query,
|
||||
},
|
||||
}
|
||||
)
|
||||
target = TARGET_FIELD
|
||||
|
||||
class Meta:
|
||||
model = models.Report
|
||||
|
@ -225,5 +240,21 @@ class ReportSerializer(serializers.ModelSerializer):
|
|||
validated_data["target_state"] = target_state_serializer(
|
||||
validated_data["target"]
|
||||
).data
|
||||
# freeze target type/id in JSON so even if the corresponding object is deleted
|
||||
# we can have the info and display it in the frontend
|
||||
target_data = self.fields["target"].to_representation(validated_data["target"])
|
||||
validated_data["target_state"]["_target"] = json.loads(
|
||||
json.dumps(target_data, cls=DjangoJSONEncoder)
|
||||
)
|
||||
|
||||
if "fid" in validated_data["target_state"]:
|
||||
validated_data["target_state"]["domain"] = urllib.parse.urlparse(
|
||||
validated_data["target_state"]["fid"]
|
||||
).hostname
|
||||
|
||||
validated_data["target_state"]["is_local"] = (
|
||||
validated_data["target_state"].get("domain", settings.FEDERATION_HOSTNAME)
|
||||
== settings.FEDERATION_HOSTNAME
|
||||
)
|
||||
validated_data["target_owner"] = get_target_owner(validated_data["target"])
|
||||
return super().create(validated_data)
|
||||
|
|
21
api/funkwhale_api/moderation/utils.py
Normal file
21
api/funkwhale_api/moderation/utils.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
from funkwhale_api.federation import models as federation_models
|
||||
|
||||
from . import models
|
||||
from . import serializers as moderation_serializers
|
||||
|
||||
|
||||
NOTE_TARGET_FIELDS = {
|
||||
"report": {
|
||||
"queryset": 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,
|
||||
},
|
||||
}
|
|
@ -46,6 +46,10 @@ PERMISSIONS_CONFIGURATION = {
|
|||
"write:instance:accounts",
|
||||
"read:instance:domains",
|
||||
"write:instance:domains",
|
||||
"read:instance:reports",
|
||||
"write:instance:reports",
|
||||
"read:instance:notes",
|
||||
"write:instance:notes",
|
||||
},
|
||||
},
|
||||
"library": {
|
||||
|
|
|
@ -34,6 +34,8 @@ BASE_SCOPES = [
|
|||
Scope("instance:accounts", "Access instance federated accounts"),
|
||||
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]),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue