First round of improvements to channel management:
- use modals - less proeminent button - field styling/labels
This commit is contained in:
parent
f8675c6080
commit
e59cc33378
103 changed files with 3201 additions and 447 deletions
|
|
@ -6,6 +6,8 @@ from django.core.serializers.json import DjangoJSONEncoder
|
|||
from django.db import models
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from django.db.models.signals import post_delete
|
||||
from django.dispatch import receiver
|
||||
|
||||
from funkwhale_api.federation import keys
|
||||
from funkwhale_api.federation import models as federation_models
|
||||
|
|
@ -44,14 +46,22 @@ class Channel(models.Model):
|
|||
)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return federation_utils.full_url("/channels/{}".format(self.uuid))
|
||||
suffix = self.uuid
|
||||
if self.actor.is_local:
|
||||
suffix = self.actor.preferred_username
|
||||
else:
|
||||
suffix = self.actor.full_username
|
||||
return federation_utils.full_url("/channels/{}".format(suffix))
|
||||
|
||||
def get_rss_url(self):
|
||||
if not self.artist.is_local:
|
||||
return self.rss_url
|
||||
|
||||
return federation_utils.full_url(
|
||||
reverse("api:v1:channels-rss", kwargs={"uuid": self.uuid})
|
||||
reverse(
|
||||
"api:v1:channels-rss",
|
||||
kwargs={"composite": self.actor.preferred_username},
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -62,3 +72,10 @@ def generate_actor(username, **kwargs):
|
|||
actor_data["public_key"] = public.decode("utf-8")
|
||||
|
||||
return federation_models.Actor.objects.create(**actor_data)
|
||||
|
||||
|
||||
@receiver(post_delete, sender=Channel)
|
||||
def delete_channel_related_objs(instance, **kwargs):
|
||||
instance.library.delete()
|
||||
instance.actor.delete()
|
||||
instance.artist.delete()
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ from django.db import transaction
|
|||
|
||||
from rest_framework import serializers
|
||||
|
||||
from django.contrib.staticfiles.templatetags.staticfiles import static
|
||||
|
||||
from funkwhale_api.common import serializers as common_serializers
|
||||
from funkwhale_api.common import utils as common_utils
|
||||
from funkwhale_api.common import locales
|
||||
|
|
@ -24,7 +26,7 @@ class ChannelMetadataSerializer(serializers.Serializer):
|
|||
itunes_category = serializers.ChoiceField(
|
||||
choices=categories.ITUNES_CATEGORIES, required=True
|
||||
)
|
||||
itunes_subcategory = serializers.CharField(required=False)
|
||||
itunes_subcategory = serializers.CharField(required=False, allow_null=True)
|
||||
language = serializers.ChoiceField(required=True, choices=locales.ISO_639_CHOICES)
|
||||
copyright = serializers.CharField(required=False, allow_null=True, max_length=255)
|
||||
owner_name = serializers.CharField(required=False, allow_null=True, max_length=255)
|
||||
|
|
@ -64,6 +66,7 @@ class ChannelCreateSerializer(serializers.Serializer):
|
|||
choices=music_models.ARTIST_CONTENT_CATEGORY_CHOICES
|
||||
)
|
||||
metadata = serializers.DictField(required=False)
|
||||
cover = music_serializers.COVER_WRITE_FIELD
|
||||
|
||||
def validate(self, validated_data):
|
||||
existing_channels = self.context["actor"].owned_channels.count()
|
||||
|
|
@ -95,15 +98,15 @@ class ChannelCreateSerializer(serializers.Serializer):
|
|||
def create(self, validated_data):
|
||||
from . import views
|
||||
|
||||
cover = validated_data.pop("cover", None)
|
||||
description = validated_data.get("description")
|
||||
artist = music_models.Artist.objects.create(
|
||||
attributed_to=validated_data["attributed_to"],
|
||||
name=validated_data["name"],
|
||||
content_category=validated_data["content_category"],
|
||||
attachment_cover=cover,
|
||||
)
|
||||
description_obj = common_utils.attach_content(
|
||||
artist, "description", description
|
||||
)
|
||||
common_utils.attach_content(artist, "description", description)
|
||||
|
||||
if validated_data.get("tags", []):
|
||||
tags_models.set_tags(artist, *validated_data["tags"])
|
||||
|
|
@ -113,9 +116,8 @@ class ChannelCreateSerializer(serializers.Serializer):
|
|||
attributed_to=validated_data["attributed_to"],
|
||||
metadata=validated_data["metadata"],
|
||||
)
|
||||
summary = description_obj.rendered if description_obj else None
|
||||
channel.actor = models.generate_actor(
|
||||
validated_data["username"], summary=summary, name=validated_data["name"],
|
||||
validated_data["username"], name=validated_data["name"],
|
||||
)
|
||||
|
||||
channel.library = music_models.Library.objects.create(
|
||||
|
|
@ -142,6 +144,7 @@ class ChannelUpdateSerializer(serializers.Serializer):
|
|||
choices=music_models.ARTIST_CONTENT_CATEGORY_CHOICES
|
||||
)
|
||||
metadata = serializers.DictField(required=False)
|
||||
cover = music_serializers.COVER_WRITE_FIELD
|
||||
|
||||
def validate(self, validated_data):
|
||||
validated_data = super().validate(validated_data)
|
||||
|
|
@ -194,6 +197,9 @@ class ChannelUpdateSerializer(serializers.Serializer):
|
|||
("content_category", validated_data["content_category"])
|
||||
)
|
||||
|
||||
if "cover" in validated_data:
|
||||
artist_update_fields.append(("attachment_cover", validated_data["cover"]))
|
||||
|
||||
if actor_update_fields:
|
||||
for field, value in actor_update_fields:
|
||||
setattr(obj.actor, field, value)
|
||||
|
|
@ -292,7 +298,7 @@ def rss_serialize_item(upload):
|
|||
# we enforce MP3, since it's the only format supported everywhere
|
||||
"url": federation_utils.full_url(upload.get_listen_url(to="mp3")),
|
||||
"length": upload.size or 0,
|
||||
"type": upload.mimetype or "audio/mpeg",
|
||||
"type": "audio/mpeg",
|
||||
}
|
||||
],
|
||||
}
|
||||
|
|
@ -362,6 +368,11 @@ def rss_serialize_channel(channel):
|
|||
data["itunes:image"] = [
|
||||
{"href": channel.artist.attachment_cover.download_url_original}
|
||||
]
|
||||
else:
|
||||
placeholder_url = federation_utils.full_url(
|
||||
static("images/podcasts-cover-placeholder.png")
|
||||
)
|
||||
data["itunes:image"] = [{"href": placeholder_url}]
|
||||
|
||||
tagged_items = getattr(channel.artist, "_prefetched_tagged_items", [])
|
||||
|
||||
|
|
|
|||
|
|
@ -1,26 +1,33 @@
|
|||
import urllib.parse
|
||||
|
||||
from django.conf import settings
|
||||
from django.db.models import Q
|
||||
from django.urls import reverse
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from funkwhale_api.common import preferences
|
||||
from funkwhale_api.common import utils
|
||||
from funkwhale_api.federation import utils as federation_utils
|
||||
from funkwhale_api.music import spa_views
|
||||
|
||||
from . import models
|
||||
|
||||
|
||||
def channel_detail(request, uuid):
|
||||
queryset = models.Channel.objects.filter(uuid=uuid).select_related(
|
||||
def channel_detail(query):
|
||||
queryset = models.Channel.objects.filter(query).select_related(
|
||||
"artist__attachment_cover", "actor", "library"
|
||||
)
|
||||
try:
|
||||
obj = queryset.get()
|
||||
except models.Channel.DoesNotExist:
|
||||
return []
|
||||
|
||||
obj_url = utils.join_url(
|
||||
settings.FUNKWHALE_URL,
|
||||
utils.spa_reverse("channel_detail", kwargs={"uuid": obj.uuid}),
|
||||
utils.spa_reverse(
|
||||
"channel_detail", kwargs={"username": obj.actor.full_username}
|
||||
),
|
||||
)
|
||||
metas = [
|
||||
{"tag": "meta", "property": "og:url", "content": obj_url},
|
||||
|
|
@ -72,3 +79,25 @@ def channel_detail(request, uuid):
|
|||
# twitter player is also supported in various software
|
||||
metas += spa_views.get_twitter_card_metas(type="channel", id=obj.uuid)
|
||||
return metas
|
||||
|
||||
|
||||
def channel_detail_uuid(request, uuid):
|
||||
validator = serializers.UUIDField().to_internal_value
|
||||
try:
|
||||
uuid = validator(uuid)
|
||||
except serializers.ValidationError:
|
||||
return []
|
||||
return channel_detail(Q(uuid=uuid))
|
||||
|
||||
|
||||
def channel_detail_username(request, username):
|
||||
validator = federation_utils.get_actor_data_from_username
|
||||
try:
|
||||
username_data = validator(username)
|
||||
except serializers.ValidationError:
|
||||
return []
|
||||
query = Q(
|
||||
actor__domain=username_data["domain"],
|
||||
actor__preferred_username__iexact=username_data["username"],
|
||||
)
|
||||
return channel_detail(query)
|
||||
|
|
|
|||
|
|
@ -7,18 +7,21 @@ from rest_framework import viewsets
|
|||
|
||||
from django import http
|
||||
from django.db import transaction
|
||||
from django.db.models import Count, Prefetch
|
||||
from django.db.models import Count, Prefetch, Q
|
||||
from django.db.utils import IntegrityError
|
||||
|
||||
from funkwhale_api.common import locales
|
||||
from funkwhale_api.common import permissions
|
||||
from funkwhale_api.common import preferences
|
||||
from funkwhale_api.common.mixins import MultipleLookupDetailMixin
|
||||
from funkwhale_api.federation import models as federation_models
|
||||
from funkwhale_api.federation import routes
|
||||
from funkwhale_api.federation import utils as federation_utils
|
||||
from funkwhale_api.music import models as music_models
|
||||
from funkwhale_api.music import views as music_views
|
||||
from funkwhale_api.users.oauth import permissions as oauth_permissions
|
||||
|
||||
from . import filters, models, renderers, serializers
|
||||
from . import categories, filters, models, renderers, serializers
|
||||
|
||||
ARTIST_PREFETCH_QS = (
|
||||
music_models.Artist.objects.select_related("description", "attachment_cover",)
|
||||
|
|
@ -36,6 +39,7 @@ class ChannelsMixin(object):
|
|||
|
||||
class ChannelViewSet(
|
||||
ChannelsMixin,
|
||||
MultipleLookupDetailMixin,
|
||||
mixins.CreateModelMixin,
|
||||
mixins.RetrieveModelMixin,
|
||||
mixins.UpdateModelMixin,
|
||||
|
|
@ -43,7 +47,20 @@ class ChannelViewSet(
|
|||
mixins.DestroyModelMixin,
|
||||
viewsets.GenericViewSet,
|
||||
):
|
||||
lookup_field = "uuid"
|
||||
url_lookups = [
|
||||
{
|
||||
"lookup_field": "uuid",
|
||||
"validator": serializers.serializers.UUIDField().to_internal_value,
|
||||
},
|
||||
{
|
||||
"lookup_field": "username",
|
||||
"validator": federation_utils.get_actor_data_from_username,
|
||||
"get_query": lambda v: Q(
|
||||
actor__domain=v["domain"],
|
||||
actor__preferred_username__iexact=v["username"],
|
||||
),
|
||||
},
|
||||
]
|
||||
filterset_class = filters.ChannelFilter
|
||||
serializer_class = serializers.ChannelSerializer
|
||||
queryset = (
|
||||
|
|
@ -134,6 +151,25 @@ class ChannelViewSet(
|
|||
data = serializers.rss_serialize_channel_full(channel=object, uploads=uploads)
|
||||
return response.Response(data, status=200)
|
||||
|
||||
@decorators.action(
|
||||
methods=["get"],
|
||||
detail=False,
|
||||
url_path="metadata-choices",
|
||||
url_name="metadata_choices",
|
||||
permission_classes=[],
|
||||
)
|
||||
def metedata_choices(self, request, *args, **kwargs):
|
||||
data = {
|
||||
"language": [
|
||||
{"value": code, "label": name} for code, name in locales.ISO_639_CHOICES
|
||||
],
|
||||
"itunes_category": [
|
||||
{"value": code, "label": code, "children": children}
|
||||
for code, children in categories.ITUNES_CATEGORIES.items()
|
||||
],
|
||||
}
|
||||
return response.Response(data)
|
||||
|
||||
def get_serializer_context(self):
|
||||
context = super().get_serializer_context()
|
||||
context["subscriptions_count"] = self.action in [
|
||||
|
|
@ -152,7 +188,7 @@ class ChannelViewSet(
|
|||
{"type": "Delete", "object": {"type": instance.actor.type}},
|
||||
context={"actor": instance.actor},
|
||||
)
|
||||
instance.delete()
|
||||
instance.__class__.objects.filter(pk=instance.pk).delete()
|
||||
|
||||
|
||||
class SubscriptionsViewSet(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue