Resolve "Hide an artist in the UI"
This commit is contained in:
parent
d4d4e60e39
commit
bdf83bd8ff
50 changed files with 1051 additions and 49 deletions
|
|
@ -28,3 +28,10 @@ class InstancePolicyAdmin(admin.ModelAdmin):
|
|||
"summary",
|
||||
]
|
||||
list_select_related = True
|
||||
|
||||
|
||||
@admin.register(models.UserFilter)
|
||||
class UserFilterAdmin(admin.ModelAdmin):
|
||||
list_display = ["uuid", "user", "target_artist", "creation_date"]
|
||||
search_fields = ["target_artist__name", "user__username", "user__email"]
|
||||
list_select_related = True
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ import factory
|
|||
|
||||
from funkwhale_api.factories import registry, NoUpdateOnCreate
|
||||
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
|
||||
|
||||
|
||||
@registry.register
|
||||
|
|
@ -21,3 +23,17 @@ class InstancePolicyFactory(NoUpdateOnCreate, factory.DjangoModelFactory):
|
|||
for_actor = factory.Trait(
|
||||
target_actor=factory.SubFactory(federation_factories.ActorFactory)
|
||||
)
|
||||
|
||||
|
||||
@registry.register
|
||||
class UserFilterFactory(NoUpdateOnCreate, factory.DjangoModelFactory):
|
||||
user = factory.SubFactory(users_factories.UserFactory)
|
||||
target_artist = None
|
||||
|
||||
class Meta:
|
||||
model = "moderation.UserFilter"
|
||||
|
||||
class Params:
|
||||
for_artist = factory.Trait(
|
||||
target_artist=factory.SubFactory(music_factories.ArtistFactory)
|
||||
)
|
||||
|
|
|
|||
69
api/funkwhale_api/moderation/filters.py
Normal file
69
api/funkwhale_api/moderation/filters.py
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
from django.db.models import Q
|
||||
|
||||
from django_filters import rest_framework as filters
|
||||
|
||||
|
||||
USER_FILTER_CONFIG = {
|
||||
"ARTIST": {"target_artist": ["pk"]},
|
||||
"ALBUM": {"target_artist": ["artist__pk"]},
|
||||
"TRACK": {"target_artist": ["artist__pk", "album__artist__pk"]},
|
||||
"LISTENING": {"target_artist": ["track__album__artist__pk", "track__artist__pk"]},
|
||||
"TRACK_FAVORITE": {
|
||||
"target_artist": ["track__album__artist__pk", "track__artist__pk"]
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def get_filtered_content_query(config, user):
|
||||
final_query = None
|
||||
for filter_field, model_fields in config.items():
|
||||
query = None
|
||||
ids = user.content_filters.values_list(filter_field, flat=True)
|
||||
for model_field in model_fields:
|
||||
q = Q(**{"{}__in".format(model_field): ids})
|
||||
if query:
|
||||
query |= q
|
||||
else:
|
||||
query = q
|
||||
|
||||
final_query = query
|
||||
return final_query
|
||||
|
||||
|
||||
class HiddenContentFilterSet(filters.FilterSet):
|
||||
"""
|
||||
A filterset that include a "hidden" param:
|
||||
- hidden=true : list user hidden/filtered objects
|
||||
- hidden=false : list all objects user hidden/filtered objects
|
||||
- not specified: hidden=false
|
||||
|
||||
Usage:
|
||||
|
||||
class MyFilterSet(HiddenContentFilterSet):
|
||||
class Meta:
|
||||
hidden_content_fields_mapping = {'target_artist': ['pk']}
|
||||
|
||||
Will map UserContentFilter.artist values to the pk field of the filtered model.
|
||||
|
||||
"""
|
||||
|
||||
hidden = filters.BooleanFilter(field_name="_", method="filter_hidden_content")
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.data = self.data.copy()
|
||||
self.data.setdefault("hidden", False)
|
||||
|
||||
def filter_hidden_content(self, queryset, name, value):
|
||||
user = self.request.user
|
||||
if not user.is_authenticated:
|
||||
# no filter to apply
|
||||
return queryset
|
||||
|
||||
config = self.__class__.Meta.hidden_content_fields_mapping
|
||||
final_query = get_filtered_content_query(config, user)
|
||||
|
||||
if value is True:
|
||||
return queryset.filter(final_query)
|
||||
else:
|
||||
return queryset.exclude(final_query)
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
# Generated by Django 2.1.5 on 2019-02-13 09:27
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
("music", "0037_auto_20190103_1757"),
|
||||
("moderation", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="UserFilter",
|
||||
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),
|
||||
),
|
||||
(
|
||||
"target_artist",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="user_filters",
|
||||
to="music.Artist",
|
||||
),
|
||||
),
|
||||
(
|
||||
"user",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="content_filters",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name="userfilter", unique_together={("user", "target_artist")}
|
||||
),
|
||||
]
|
||||
|
|
@ -73,3 +73,22 @@ class InstancePolicy(models.Model):
|
|||
return {"type": "actor", "obj": self.target_actor}
|
||||
if self.target_domain_id:
|
||||
return {"type": "domain", "obj": self.target_domain}
|
||||
|
||||
|
||||
class UserFilter(models.Model):
|
||||
uuid = models.UUIDField(default=uuid.uuid4, unique=True)
|
||||
creation_date = models.DateTimeField(default=timezone.now)
|
||||
target_artist = models.ForeignKey(
|
||||
"music.Artist", on_delete=models.CASCADE, related_name="user_filters"
|
||||
)
|
||||
user = models.ForeignKey(
|
||||
"users.User", on_delete=models.CASCADE, related_name="content_filters"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
unique_together = ("user", "target_artist")
|
||||
|
||||
@property
|
||||
def target(self):
|
||||
if self.target_artist:
|
||||
return {"type": "artist", "obj": self.target_artist}
|
||||
|
|
|
|||
45
api/funkwhale_api/moderation/serializers.py
Normal file
45
api/funkwhale_api/moderation/serializers.py
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
from funkwhale_api.music import models as music_models
|
||||
from . import models
|
||||
|
||||
|
||||
class FilteredArtistSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = music_models.Artist
|
||||
fields = ["id", "name"]
|
||||
|
||||
|
||||
class TargetSerializer(serializers.Serializer):
|
||||
type = serializers.ChoiceField(choices=["artist"])
|
||||
id = serializers.CharField()
|
||||
|
||||
def to_representation(self, value):
|
||||
if value["type"] == "artist":
|
||||
data = FilteredArtistSerializer(value["obj"]).data
|
||||
data.update({"type": "artist"})
|
||||
return data
|
||||
|
||||
def to_internal_value(self, value):
|
||||
if value["type"] == "artist":
|
||||
field = serializers.PrimaryKeyRelatedField(
|
||||
queryset=music_models.Artist.objects.all()
|
||||
)
|
||||
value["obj"] = field.to_internal_value(value["id"])
|
||||
return value
|
||||
|
||||
|
||||
class UserFilterSerializer(serializers.ModelSerializer):
|
||||
target = TargetSerializer()
|
||||
|
||||
class Meta:
|
||||
model = models.UserFilter
|
||||
fields = ["uuid", "target", "creation_date"]
|
||||
read_only_fields = ["uuid", "creation_date"]
|
||||
|
||||
def validate(self, data):
|
||||
target = data.pop("target")
|
||||
if target["type"] == "artist":
|
||||
data["target_artist"] = target["obj"]
|
||||
|
||||
return data
|
||||
8
api/funkwhale_api/moderation/urls.py
Normal file
8
api/funkwhale_api/moderation/urls.py
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
from rest_framework import routers
|
||||
|
||||
from . import views
|
||||
|
||||
router = routers.SimpleRouter()
|
||||
router.register(r"content-filters", views.UserFilterViewSet, "content-filters")
|
||||
|
||||
urlpatterns = router.urls
|
||||
42
api/funkwhale_api/moderation/views.py
Normal file
42
api/funkwhale_api/moderation/views.py
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
from django.db import IntegrityError
|
||||
|
||||
from rest_framework import mixins
|
||||
from rest_framework import permissions
|
||||
from rest_framework import response
|
||||
from rest_framework import status
|
||||
from rest_framework import viewsets
|
||||
|
||||
from . import models
|
||||
from . import serializers
|
||||
|
||||
|
||||
class UserFilterViewSet(
|
||||
mixins.ListModelMixin,
|
||||
mixins.CreateModelMixin,
|
||||
mixins.RetrieveModelMixin,
|
||||
mixins.DestroyModelMixin,
|
||||
viewsets.GenericViewSet,
|
||||
):
|
||||
lookup_field = "uuid"
|
||||
queryset = (
|
||||
models.UserFilter.objects.all()
|
||||
.order_by("-creation_date")
|
||||
.select_related("target_artist")
|
||||
)
|
||||
serializer_class = serializers.UserFilterSerializer
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
ordering_fields = ("creation_date",)
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
try:
|
||||
return super().create(request, *args, **kwargs)
|
||||
except IntegrityError:
|
||||
content = {"detail": "A content filter already exists for this object"}
|
||||
return response.Response(content, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def get_queryset(self):
|
||||
qs = super().get_queryset()
|
||||
return qs.filter(user=self.request.user)
|
||||
|
||||
def perform_create(self, serializer):
|
||||
serializer.save(user=self.request.user)
|
||||
Loading…
Add table
Add a link
Reference in a new issue