mirror of
https://code.eliotberriot.com/funkwhale/funkwhale.git
synced 2025-10-06 01:39:55 +02:00
UI To manage artists, albums, tracks
This commit is contained in:
parent
ae390e5c1c
commit
b4731928fc
39 changed files with 2837 additions and 116 deletions
|
@ -49,6 +49,6 @@ class SmartSearchFilter(django_filters.CharFilter):
|
|||
return qs
|
||||
try:
|
||||
cleaned = self.config.clean(value)
|
||||
except forms.ValidationError:
|
||||
except (forms.ValidationError):
|
||||
return qs.none()
|
||||
return search.apply(qs, cleaned)
|
||||
|
|
|
@ -104,6 +104,31 @@ class MultipleQueryFilter(filters.TypedMultipleChoiceFilter):
|
|||
self.lookup_expr = "in"
|
||||
|
||||
|
||||
def filter_target(value):
|
||||
|
||||
config = {
|
||||
"artist": ["artist", "target_id", int],
|
||||
"album": ["album", "target_id", int],
|
||||
"track": ["track", "target_id", int],
|
||||
}
|
||||
parts = value.lower().split(" ")
|
||||
if parts[0].strip() not in config:
|
||||
raise forms.ValidationError("Improper target")
|
||||
|
||||
conf = config[parts[0].strip()]
|
||||
|
||||
query = Q(target_content_type__model=conf[0])
|
||||
if len(parts) > 1:
|
||||
_, lookup_field, validator = conf
|
||||
try:
|
||||
lookup_value = validator(parts[1].strip())
|
||||
except TypeError:
|
||||
raise forms.ValidationError("Imparsable target id")
|
||||
return query & Q(**{lookup_field: lookup_value})
|
||||
|
||||
return query
|
||||
|
||||
|
||||
class MutationFilter(filters.FilterSet):
|
||||
is_approved = NullBooleanFilter("is_approved")
|
||||
q = fields.SmartSearchFilter(
|
||||
|
@ -116,6 +141,7 @@ class MutationFilter(filters.FilterSet):
|
|||
filter_fields={
|
||||
"domain": {"to": "created_by__domain__name__iexact"},
|
||||
"is_approved": get_null_boolean_filter("is_approved"),
|
||||
"target": {"handler": filter_target},
|
||||
"is_applied": {"to": "is_applied"},
|
||||
},
|
||||
)
|
||||
|
|
|
@ -77,12 +77,15 @@ class SearchConfig:
|
|||
def clean(self, query):
|
||||
tokens = parse_query(query)
|
||||
cleaned_data = {}
|
||||
|
||||
cleaned_data["types"] = self.clean_types(filter_tokens(tokens, ["is"]))
|
||||
cleaned_data["search_query"] = self.clean_search_query(
|
||||
filter_tokens(tokens, [None, "in"])
|
||||
filter_tokens(tokens, [None, "in"] + list(self.search_fields.keys()))
|
||||
)
|
||||
unhandled_tokens = [t for t in tokens if t["key"] not in [None, "is", "in"]]
|
||||
unhandled_tokens = [
|
||||
t
|
||||
for t in tokens
|
||||
if t["key"] not in [None, "is", "in"] + list(self.search_fields.keys())
|
||||
]
|
||||
cleaned_data["filter_query"] = self.clean_filter_query(unhandled_tokens)
|
||||
return cleaned_data
|
||||
|
||||
|
@ -95,8 +98,33 @@ class SearchConfig:
|
|||
} or set(self.search_fields.keys())
|
||||
fields_subset = set(self.search_fields.keys()) & fields_subset
|
||||
to_fields = [self.search_fields[k]["to"] for k in fields_subset]
|
||||
|
||||
specific_field_query = None
|
||||
for token in tokens:
|
||||
if token["key"] not in self.search_fields:
|
||||
continue
|
||||
to = self.search_fields[token["key"]]["to"]
|
||||
try:
|
||||
field = token["field"]
|
||||
value = field.clean(token["value"])
|
||||
except KeyError:
|
||||
# no cleaning to apply
|
||||
value = token["value"]
|
||||
q = Q(**{"{}__icontains".format(to): value})
|
||||
if not specific_field_query:
|
||||
specific_field_query = q
|
||||
else:
|
||||
specific_field_query &= q
|
||||
query_string = " ".join([t["value"] for t in filter_tokens(tokens, [None])])
|
||||
return get_query(query_string, sorted(to_fields))
|
||||
unhandled_tokens_query = get_query(query_string, sorted(to_fields))
|
||||
|
||||
if specific_field_query and unhandled_tokens_query:
|
||||
return unhandled_tokens_query & specific_field_query
|
||||
elif specific_field_query:
|
||||
return specific_field_query
|
||||
elif unhandled_tokens_query:
|
||||
return unhandled_tokens_query
|
||||
return None
|
||||
|
||||
def clean_filter_query(self, tokens):
|
||||
if not self.filter_fields or not tokens:
|
||||
|
|
|
@ -36,6 +36,7 @@ class MutationViewSet(
|
|||
lookup_field = "uuid"
|
||||
queryset = (
|
||||
models.Mutation.objects.all()
|
||||
.exclude(target_id=None)
|
||||
.order_by("-creation_date")
|
||||
.select_related("created_by", "approved_by")
|
||||
.prefetch_related("target")
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import django_filters
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from . import models
|
||||
from . import utils
|
||||
|
||||
|
||||
class ActorRelatedField(serializers.EmailField):
|
||||
|
@ -16,3 +19,15 @@ class ActorRelatedField(serializers.EmailField):
|
|||
)
|
||||
except models.Actor.DoesNotExist:
|
||||
raise serializers.ValidationError("Invalid actor name")
|
||||
|
||||
|
||||
class DomainFromURLFilter(django_filters.CharFilter):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.url_field = kwargs.pop("url_field", "fid")
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def filter(self, qs, value):
|
||||
if not value:
|
||||
return qs
|
||||
query = utils.get_domain_query_from_url(value, self.url_field)
|
||||
return qs.filter(query)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import unicodedata
|
||||
import re
|
||||
from django.conf import settings
|
||||
from django.db.models import Q
|
||||
|
||||
from funkwhale_api.common import session
|
||||
from funkwhale_api.moderation import models as moderation_models
|
||||
|
@ -107,3 +108,16 @@ def retrieve_ap_object(
|
|||
serializer = serializer_class(data=data, context={"fetch_actor": actor})
|
||||
serializer.is_valid(raise_exception=True)
|
||||
return serializer.save()
|
||||
|
||||
|
||||
def get_domain_query_from_url(domain, url_field="fid"):
|
||||
"""
|
||||
Given a domain name and a field, will return a Q() object
|
||||
to match objects that have this domain in the given field.
|
||||
"""
|
||||
|
||||
query = Q(**{"{}__startswith".format(url_field): "http://{}/".format(domain)})
|
||||
query = query | Q(
|
||||
**{"{}__startswith".format(url_field): "https://{}/".format(domain)}
|
||||
)
|
||||
return query
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
from django import forms
|
||||
from django_filters import rest_framework as filters
|
||||
|
||||
from funkwhale_api.common import fields
|
||||
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.music import models as music_models
|
||||
from funkwhale_api.users import models as users_models
|
||||
|
@ -24,6 +26,82 @@ class ManageUploadFilterSet(filters.FilterSet):
|
|||
fields = ["q", "track__album", "track__artist", "track"]
|
||||
|
||||
|
||||
class ManageArtistFilterSet(filters.FilterSet):
|
||||
q = fields.SmartSearchFilter(
|
||||
config=search.SearchConfig(
|
||||
search_fields={
|
||||
"name": {"to": "name"},
|
||||
"fid": {"to": "fid"},
|
||||
"mbid": {"to": "mbid"},
|
||||
},
|
||||
filter_fields={
|
||||
"domain": {
|
||||
"handler": lambda v: federation_utils.get_domain_query_from_url(v)
|
||||
}
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = music_models.Artist
|
||||
fields = ["q", "name", "mbid", "fid"]
|
||||
|
||||
|
||||
class ManageAlbumFilterSet(filters.FilterSet):
|
||||
q = fields.SmartSearchFilter(
|
||||
config=search.SearchConfig(
|
||||
search_fields={
|
||||
"title": {"to": "title"},
|
||||
"fid": {"to": "fid"},
|
||||
"artist": {"to": "artist__name"},
|
||||
"mbid": {"to": "mbid"},
|
||||
},
|
||||
filter_fields={
|
||||
"artist_id": {"to": "artist_id", "field": forms.IntegerField()},
|
||||
"domain": {
|
||||
"handler": lambda v: federation_utils.get_domain_query_from_url(v)
|
||||
},
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = music_models.Album
|
||||
fields = ["q", "title", "mbid", "fid", "artist"]
|
||||
|
||||
|
||||
class ManageTrackFilterSet(filters.FilterSet):
|
||||
q = fields.SmartSearchFilter(
|
||||
config=search.SearchConfig(
|
||||
search_fields={
|
||||
"title": {"to": "title"},
|
||||
"fid": {"to": "fid"},
|
||||
"mbid": {"to": "mbid"},
|
||||
"artist": {"to": "artist__name"},
|
||||
"album": {"to": "album__title"},
|
||||
"album_artist": {"to": "album__artist__name"},
|
||||
"copyright": {"to": "copyright"},
|
||||
},
|
||||
filter_fields={
|
||||
"album_id": {"to": "album_id", "field": forms.IntegerField()},
|
||||
"album_artist_id": {
|
||||
"to": "album__artist_id",
|
||||
"field": forms.IntegerField(),
|
||||
},
|
||||
"artist_id": {"to": "artist_id", "field": forms.IntegerField()},
|
||||
"license": {"to": "license"},
|
||||
"domain": {
|
||||
"handler": lambda v: federation_utils.get_domain_query_from_url(v)
|
||||
},
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = music_models.Track
|
||||
fields = ["q", "title", "mbid", "fid", "artist", "album", "license"]
|
||||
|
||||
|
||||
class ManageDomainFilterSet(filters.FilterSet):
|
||||
q = fields.SearchFilter(search_fields=["name"])
|
||||
|
||||
|
@ -60,7 +138,15 @@ class ManageActorFilterSet(filters.FilterSet):
|
|||
|
||||
|
||||
class ManageUserFilterSet(filters.FilterSet):
|
||||
q = fields.SearchFilter(search_fields=["username", "email", "name"])
|
||||
q = fields.SmartSearchFilter(
|
||||
config=search.SearchConfig(
|
||||
search_fields={
|
||||
"name": {"to": "name"},
|
||||
"username": {"to": "username"},
|
||||
"email": {"to": "email"},
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = users_models.User
|
||||
|
|
|
@ -9,6 +9,7 @@ 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.music import models as music_models
|
||||
from funkwhale_api.music import serializers as music_serializers
|
||||
from funkwhale_api.users import models as users_models
|
||||
|
||||
from . import filters
|
||||
|
@ -216,10 +217,7 @@ class ManageDomainActionSerializer(common_serializers.ActionSerializer):
|
|||
common_utils.on_commit(federation_tasks.purge_actors.delay, domains=list(ids))
|
||||
|
||||
|
||||
class ManageActorSerializer(serializers.ModelSerializer):
|
||||
uploads_count = serializers.SerializerMethodField()
|
||||
user = ManageUserSerializer()
|
||||
|
||||
class ManageBaseActorSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = federation_models.Actor
|
||||
fields = [
|
||||
|
@ -238,6 +236,17 @@ class ManageActorSerializer(serializers.ModelSerializer):
|
|||
"outbox_url",
|
||||
"shared_inbox_url",
|
||||
"manually_approves_followers",
|
||||
]
|
||||
read_only_fields = ["creation_date", "instance_policy"]
|
||||
|
||||
|
||||
class ManageActorSerializer(ManageBaseActorSerializer):
|
||||
uploads_count = serializers.SerializerMethodField()
|
||||
user = ManageUserSerializer()
|
||||
|
||||
class Meta:
|
||||
model = federation_models.Actor
|
||||
fields = ManageBaseActorSerializer.Meta.fields + [
|
||||
"uploads_count",
|
||||
"user",
|
||||
"instance_policy",
|
||||
|
@ -339,3 +348,148 @@ class ManageInstancePolicySerializer(serializers.ModelSerializer):
|
|||
)
|
||||
|
||||
return instance
|
||||
|
||||
|
||||
class ManageBaseArtistSerializer(serializers.ModelSerializer):
|
||||
domain = serializers.CharField(source="domain_name")
|
||||
|
||||
class Meta:
|
||||
model = music_models.Artist
|
||||
fields = ["id", "fid", "mbid", "name", "creation_date", "domain", "is_local"]
|
||||
|
||||
|
||||
class ManageBaseAlbumSerializer(serializers.ModelSerializer):
|
||||
cover = music_serializers.cover_field
|
||||
domain = serializers.CharField(source="domain_name")
|
||||
|
||||
class Meta:
|
||||
model = music_models.Album
|
||||
fields = [
|
||||
"id",
|
||||
"fid",
|
||||
"mbid",
|
||||
"title",
|
||||
"creation_date",
|
||||
"release_date",
|
||||
"cover",
|
||||
"domain",
|
||||
"is_local",
|
||||
]
|
||||
|
||||
|
||||
class ManageNestedTrackSerializer(serializers.ModelSerializer):
|
||||
domain = serializers.CharField(source="domain_name")
|
||||
|
||||
class Meta:
|
||||
model = music_models.Track
|
||||
fields = [
|
||||
"id",
|
||||
"fid",
|
||||
"mbid",
|
||||
"title",
|
||||
"creation_date",
|
||||
"position",
|
||||
"disc_number",
|
||||
"domain",
|
||||
"is_local",
|
||||
"copyright",
|
||||
"license",
|
||||
]
|
||||
|
||||
|
||||
class ManageNestedAlbumSerializer(ManageBaseAlbumSerializer):
|
||||
|
||||
tracks_count = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = music_models.Album
|
||||
fields = ManageBaseAlbumSerializer.Meta.fields + ["tracks_count"]
|
||||
|
||||
def get_tracks_count(self, obj):
|
||||
return getattr(obj, "tracks_count", None)
|
||||
|
||||
|
||||
class ManageArtistSerializer(ManageBaseArtistSerializer):
|
||||
albums = ManageNestedAlbumSerializer(many=True)
|
||||
tracks = ManageNestedTrackSerializer(many=True)
|
||||
attributed_to = ManageBaseActorSerializer()
|
||||
|
||||
class Meta:
|
||||
model = music_models.Artist
|
||||
fields = ManageBaseArtistSerializer.Meta.fields + [
|
||||
"albums",
|
||||
"tracks",
|
||||
"attributed_to",
|
||||
]
|
||||
|
||||
|
||||
class ManageNestedArtistSerializer(ManageBaseArtistSerializer):
|
||||
pass
|
||||
|
||||
|
||||
class ManageAlbumSerializer(ManageBaseAlbumSerializer):
|
||||
tracks = ManageNestedTrackSerializer(many=True)
|
||||
attributed_to = ManageBaseActorSerializer()
|
||||
artist = ManageNestedArtistSerializer()
|
||||
|
||||
class Meta:
|
||||
model = music_models.Album
|
||||
fields = ManageBaseAlbumSerializer.Meta.fields + [
|
||||
"artist",
|
||||
"tracks",
|
||||
"attributed_to",
|
||||
]
|
||||
|
||||
|
||||
class ManageTrackAlbumSerializer(ManageBaseAlbumSerializer):
|
||||
artist = ManageNestedArtistSerializer()
|
||||
|
||||
class Meta:
|
||||
model = music_models.Album
|
||||
fields = ManageBaseAlbumSerializer.Meta.fields + ["artist"]
|
||||
|
||||
|
||||
class ManageTrackSerializer(ManageNestedTrackSerializer):
|
||||
artist = ManageNestedArtistSerializer()
|
||||
album = ManageTrackAlbumSerializer()
|
||||
attributed_to = ManageBaseActorSerializer()
|
||||
uploads_count = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = music_models.Track
|
||||
fields = ManageNestedTrackSerializer.Meta.fields + [
|
||||
"artist",
|
||||
"album",
|
||||
"attributed_to",
|
||||
"uploads_count",
|
||||
]
|
||||
|
||||
def get_uploads_count(self, obj):
|
||||
return getattr(obj, "uploads_count", None)
|
||||
|
||||
|
||||
class ManageTrackActionSerializer(common_serializers.ActionSerializer):
|
||||
actions = [common_serializers.Action("delete", allow_all=False)]
|
||||
filterset_class = filters.ManageTrackFilterSet
|
||||
|
||||
@transaction.atomic
|
||||
def handle_delete(self, objects):
|
||||
return objects.delete()
|
||||
|
||||
|
||||
class ManageAlbumActionSerializer(common_serializers.ActionSerializer):
|
||||
actions = [common_serializers.Action("delete", allow_all=False)]
|
||||
filterset_class = filters.ManageAlbumFilterSet
|
||||
|
||||
@transaction.atomic
|
||||
def handle_delete(self, objects):
|
||||
return objects.delete()
|
||||
|
||||
|
||||
class ManageArtistActionSerializer(common_serializers.ActionSerializer):
|
||||
actions = [common_serializers.Action("delete", allow_all=False)]
|
||||
filterset_class = filters.ManageArtistFilterSet
|
||||
|
||||
@transaction.atomic
|
||||
def handle_delete(self, objects):
|
||||
return objects.delete()
|
||||
|
|
|
@ -8,6 +8,9 @@ federation_router.register(r"domains", views.ManageDomainViewSet, "domains")
|
|||
|
||||
library_router = routers.SimpleRouter()
|
||||
library_router.register(r"uploads", views.ManageUploadViewSet, "uploads")
|
||||
library_router.register(r"artists", views.ManageArtistViewSet, "artists")
|
||||
library_router.register(r"albums", views.ManageAlbumViewSet, "albums")
|
||||
library_router.register(r"tracks", views.ManageTrackViewSet, "tracks")
|
||||
|
||||
moderation_router = routers.SimpleRouter()
|
||||
moderation_router.register(
|
||||
|
|
|
@ -1,12 +1,18 @@
|
|||
from rest_framework import mixins, response, viewsets
|
||||
from rest_framework import decorators as rest_decorators
|
||||
|
||||
from django.db.models import Count, Prefetch, Q, Sum
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from funkwhale_api.common import models as common_models
|
||||
from funkwhale_api.common import preferences, decorators
|
||||
from funkwhale_api.favorites import models as favorites_models
|
||||
from funkwhale_api.federation import models as federation_models
|
||||
from funkwhale_api.federation import tasks as federation_tasks
|
||||
from funkwhale_api.history import models as history_models
|
||||
from funkwhale_api.music import models as music_models
|
||||
from funkwhale_api.moderation import models as moderation_models
|
||||
from funkwhale_api.playlists import models as playlists_models
|
||||
from funkwhale_api.users import models as users_models
|
||||
|
||||
|
||||
|
@ -45,6 +51,151 @@ class ManageUploadViewSet(
|
|||
return response.Response(result, status=200)
|
||||
|
||||
|
||||
def get_stats(tracks, target):
|
||||
data = {}
|
||||
tracks = list(tracks.values_list("pk", flat=True))
|
||||
uploads = music_models.Upload.objects.filter(track__in=tracks)
|
||||
data["listenings"] = history_models.Listening.objects.filter(
|
||||
track__in=tracks
|
||||
).count()
|
||||
data["mutations"] = common_models.Mutation.objects.get_for_target(target).count()
|
||||
data["playlists"] = (
|
||||
playlists_models.PlaylistTrack.objects.filter(track__in=tracks)
|
||||
.values_list("playlist", flat=True)
|
||||
.distinct()
|
||||
.count()
|
||||
)
|
||||
data["track_favorites"] = favorites_models.TrackFavorite.objects.filter(
|
||||
track__in=tracks
|
||||
).count()
|
||||
data["libraries"] = uploads.values_list("library", flat=True).distinct().count()
|
||||
data["uploads"] = uploads.count()
|
||||
data["media_total_size"] = uploads.aggregate(v=Sum("size"))["v"] or 0
|
||||
data["media_downloaded_size"] = (
|
||||
uploads.with_file().aggregate(v=Sum("size"))["v"] or 0
|
||||
)
|
||||
return data
|
||||
|
||||
|
||||
class ManageArtistViewSet(
|
||||
mixins.ListModelMixin,
|
||||
mixins.RetrieveModelMixin,
|
||||
mixins.DestroyModelMixin,
|
||||
viewsets.GenericViewSet,
|
||||
):
|
||||
queryset = (
|
||||
music_models.Artist.objects.all()
|
||||
.order_by("-id")
|
||||
.select_related("attributed_to")
|
||||
.prefetch_related(
|
||||
"tracks",
|
||||
Prefetch(
|
||||
"albums",
|
||||
queryset=music_models.Album.objects.annotate(
|
||||
tracks_count=Count("tracks")
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
serializer_class = serializers.ManageArtistSerializer
|
||||
filterset_class = filters.ManageArtistFilterSet
|
||||
required_scope = "instance:libraries"
|
||||
ordering_fields = ["creation_date", "name"]
|
||||
|
||||
@rest_decorators.action(methods=["get"], detail=True)
|
||||
def stats(self, request, *args, **kwargs):
|
||||
artist = self.get_object()
|
||||
tracks = music_models.Track.objects.filter(
|
||||
Q(artist=artist) | Q(album__artist=artist)
|
||||
)
|
||||
data = get_stats(tracks, artist)
|
||||
return response.Response(data, status=200)
|
||||
|
||||
@rest_decorators.action(methods=["post"], detail=False)
|
||||
def action(self, request, *args, **kwargs):
|
||||
queryset = self.get_queryset()
|
||||
serializer = serializers.ManageArtistActionSerializer(
|
||||
request.data, queryset=queryset
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
result = serializer.save()
|
||||
return response.Response(result, status=200)
|
||||
|
||||
|
||||
class ManageAlbumViewSet(
|
||||
mixins.ListModelMixin,
|
||||
mixins.RetrieveModelMixin,
|
||||
mixins.DestroyModelMixin,
|
||||
viewsets.GenericViewSet,
|
||||
):
|
||||
queryset = (
|
||||
music_models.Album.objects.all()
|
||||
.order_by("-id")
|
||||
.select_related("attributed_to", "artist")
|
||||
.prefetch_related("tracks")
|
||||
)
|
||||
serializer_class = serializers.ManageAlbumSerializer
|
||||
filterset_class = filters.ManageAlbumFilterSet
|
||||
required_scope = "instance:libraries"
|
||||
ordering_fields = ["creation_date", "title", "release_date"]
|
||||
|
||||
@rest_decorators.action(methods=["get"], detail=True)
|
||||
def stats(self, request, *args, **kwargs):
|
||||
album = self.get_object()
|
||||
data = get_stats(album.tracks.all(), album)
|
||||
return response.Response(data, status=200)
|
||||
|
||||
@rest_decorators.action(methods=["post"], detail=False)
|
||||
def action(self, request, *args, **kwargs):
|
||||
queryset = self.get_queryset()
|
||||
serializer = serializers.ManageAlbumActionSerializer(
|
||||
request.data, queryset=queryset
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
result = serializer.save()
|
||||
return response.Response(result, status=200)
|
||||
|
||||
|
||||
class ManageTrackViewSet(
|
||||
mixins.ListModelMixin,
|
||||
mixins.RetrieveModelMixin,
|
||||
mixins.DestroyModelMixin,
|
||||
viewsets.GenericViewSet,
|
||||
):
|
||||
queryset = (
|
||||
music_models.Track.objects.all()
|
||||
.order_by("-id")
|
||||
.select_related("attributed_to", "artist", "album__artist")
|
||||
.annotate(uploads_count=Count("uploads"))
|
||||
)
|
||||
serializer_class = serializers.ManageTrackSerializer
|
||||
filterset_class = filters.ManageTrackFilterSet
|
||||
required_scope = "instance:libraries"
|
||||
ordering_fields = [
|
||||
"creation_date",
|
||||
"title",
|
||||
"album__release_date",
|
||||
"position",
|
||||
"disc_number",
|
||||
]
|
||||
|
||||
@rest_decorators.action(methods=["get"], detail=True)
|
||||
def stats(self, request, *args, **kwargs):
|
||||
track = self.get_object()
|
||||
data = get_stats(track.__class__.objects.filter(pk=track.pk), track)
|
||||
return response.Response(data, status=200)
|
||||
|
||||
@rest_decorators.action(methods=["post"], detail=False)
|
||||
def action(self, request, *args, **kwargs):
|
||||
queryset = self.get_queryset()
|
||||
serializer = serializers.ManageTrackActionSerializer(
|
||||
request.data, queryset=queryset
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
result = serializer.save()
|
||||
return response.Response(result, status=200)
|
||||
|
||||
|
||||
class ManageUserViewSet(
|
||||
mixins.ListModelMixin,
|
||||
mixins.RetrieveModelMixin,
|
||||
|
|
|
@ -3,6 +3,7 @@ import logging
|
|||
import mimetypes
|
||||
import os
|
||||
import tempfile
|
||||
import urllib.parse
|
||||
import uuid
|
||||
|
||||
import markdown
|
||||
|
@ -124,6 +125,14 @@ class APIModelMixin(models.Model):
|
|||
"https://{}/".format(d)
|
||||
)
|
||||
|
||||
@property
|
||||
def domain_name(self):
|
||||
if not self.fid:
|
||||
return
|
||||
|
||||
parsed = urllib.parse.urlparse(self.fid)
|
||||
return parsed.hostname
|
||||
|
||||
|
||||
class License(models.Model):
|
||||
code = models.CharField(primary_key=True, max_length=100)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue