Merge branch '170-track-description' into 'develop'
See #170: add a description field on tracks, albums, tracks See merge request funkwhale/funkwhale!993
This commit is contained in:
commit
e6f8b6e406
|
@ -26,3 +26,12 @@ class AttachmentFactory(NoUpdateOnCreate, factory.django.DjangoModelFactory):
|
|||
|
||||
class Meta:
|
||||
model = "common.Attachment"
|
||||
|
||||
|
||||
@registry.register
|
||||
class CommonFactory(NoUpdateOnCreate, factory.django.DjangoModelFactory):
|
||||
text = factory.Faker("paragraph")
|
||||
content_type = "text/plain"
|
||||
|
||||
class Meta:
|
||||
model = "common.Content"
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
# Generated by Django 2.2.7 on 2020-01-13 10:14
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('common', '0005_auto_20191125_1421'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Content',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('text', models.CharField(blank=True, max_length=5000, null=True)),
|
||||
('content_type', models.CharField(max_length=100)),
|
||||
],
|
||||
),
|
||||
]
|
|
@ -24,6 +24,14 @@ from . import utils
|
|||
from . import validators
|
||||
|
||||
|
||||
CONTENT_TEXT_MAX_LENGTH = 5000
|
||||
CONTENT_TEXT_SUPPORTED_TYPES = [
|
||||
"text/html",
|
||||
"text/markdown",
|
||||
"text/plain",
|
||||
]
|
||||
|
||||
|
||||
@Field.register_lookup
|
||||
class NotEqual(Lookup):
|
||||
lookup_name = "ne"
|
||||
|
@ -273,6 +281,15 @@ class MutationAttachment(models.Model):
|
|||
unique_together = ("attachment", "mutation")
|
||||
|
||||
|
||||
class Content(models.Model):
|
||||
"""
|
||||
A text content that can be associated to other models, like a description, a summary, etc.
|
||||
"""
|
||||
|
||||
text = models.CharField(max_length=CONTENT_TEXT_MAX_LENGTH, blank=True, null=True)
|
||||
content_type = models.CharField(max_length=100)
|
||||
|
||||
|
||||
@receiver(models.signals.post_save, sender=Attachment)
|
||||
def warm_attachment_thumbnails(sender, instance, **kwargs):
|
||||
if not instance.file or not settings.CREATE_IMAGE_THUMBNAILS:
|
||||
|
@ -302,3 +319,18 @@ def trigger_mutation_post_init(sender, instance, created, **kwargs):
|
|||
except AttributeError:
|
||||
return
|
||||
handler(instance)
|
||||
|
||||
|
||||
CONTENT_FKS = {
|
||||
"music.Track": ["description"],
|
||||
"music.Album": ["description"],
|
||||
"music.Artist": ["description"],
|
||||
}
|
||||
|
||||
|
||||
@receiver(models.signals.post_delete, sender=None)
|
||||
def remove_attached_content(sender, instance, **kwargs):
|
||||
fk_fields = CONTENT_FKS.get(instance._meta.label, [])
|
||||
for field in fk_fields:
|
||||
if getattr(instance, "{}_id".format(field)):
|
||||
getattr(instance, field).delete()
|
||||
|
|
|
@ -86,7 +86,6 @@ class MutationSerializer(serializers.Serializer):
|
|||
|
||||
class UpdateMutationSerializer(serializers.ModelSerializer, MutationSerializer):
|
||||
serialized_relations = {}
|
||||
previous_state_handlers = {}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
# we force partial mode, because update mutations are partial
|
||||
|
@ -141,9 +140,12 @@ class UpdateMutationSerializer(serializers.ModelSerializer, MutationSerializer):
|
|||
obj,
|
||||
*list(validated_data.keys()),
|
||||
serialized_relations=self.serialized_relations,
|
||||
handlers=self.previous_state_handlers,
|
||||
handlers=self.get_previous_state_handlers(),
|
||||
)
|
||||
|
||||
def get_previous_state_handlers(self):
|
||||
return {}
|
||||
|
||||
|
||||
def get_update_previous_state(obj, *fields, serialized_relations={}, handlers={}):
|
||||
if not fields:
|
||||
|
|
|
@ -11,6 +11,7 @@ from django.utils.encoding import smart_text
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from . import models
|
||||
from . import utils
|
||||
|
||||
|
||||
class RelatedField(serializers.RelatedField):
|
||||
|
@ -308,3 +309,12 @@ class AttachmentSerializer(serializers.Serializer):
|
|||
return models.Attachment.objects.create(
|
||||
file=validated_data["file"], actor=validated_data["actor"]
|
||||
)
|
||||
|
||||
|
||||
class ContentSerializer(serializers.Serializer):
|
||||
text = serializers.CharField(max_length=models.CONTENT_TEXT_MAX_LENGTH)
|
||||
content_type = serializers.ChoiceField(choices=models.CONTENT_TEXT_SUPPORTED_TYPES,)
|
||||
html = serializers.SerializerMethodField()
|
||||
|
||||
def get_html(self, o):
|
||||
return utils.render_html(o.text, o.content_type)
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
from django.utils.deconstruct import deconstructible
|
||||
|
||||
import bleach.sanitizer
|
||||
import markdown
|
||||
import os
|
||||
import shutil
|
||||
import uuid
|
||||
|
@ -241,3 +243,65 @@ def join_queries_or(left, right):
|
|||
return left | right
|
||||
else:
|
||||
return right
|
||||
|
||||
|
||||
def render_markdown(text):
|
||||
return markdown.markdown(text, extensions=["nl2br"])
|
||||
|
||||
|
||||
HTMl_CLEANER = bleach.sanitizer.Cleaner(
|
||||
strip=True,
|
||||
tags=[
|
||||
"p",
|
||||
"a",
|
||||
"abbr",
|
||||
"acronym",
|
||||
"b",
|
||||
"blockquote",
|
||||
"code",
|
||||
"em",
|
||||
"i",
|
||||
"li",
|
||||
"ol",
|
||||
"strong",
|
||||
"ul",
|
||||
],
|
||||
)
|
||||
|
||||
HTML_LINKER = bleach.linkifier.Linker()
|
||||
|
||||
|
||||
def clean_html(html):
|
||||
return HTMl_CLEANER.clean(html)
|
||||
|
||||
|
||||
def render_html(text, content_type):
|
||||
rendered = render_markdown(text)
|
||||
if content_type == "text/html":
|
||||
rendered = text
|
||||
elif content_type == "text/markdown":
|
||||
rendered = render_markdown(text)
|
||||
else:
|
||||
rendered = render_markdown(text)
|
||||
rendered = HTML_LINKER.linkify(rendered)
|
||||
return clean_html(rendered).strip().replace("\n", "")
|
||||
|
||||
|
||||
@transaction.atomic
|
||||
def attach_content(obj, field, content_data):
|
||||
from . import models
|
||||
|
||||
existing = getattr(obj, "{}_id".format(field))
|
||||
|
||||
if existing:
|
||||
getattr(obj, field).delete()
|
||||
|
||||
if not content_data:
|
||||
return
|
||||
|
||||
content_obj = models.Content.objects.create(
|
||||
text=content_data["text"][: models.CONTENT_TEXT_MAX_LENGTH],
|
||||
content_type=content_data["content_type"],
|
||||
)
|
||||
setattr(obj, field, content_obj)
|
||||
obj.save(update_fields=[field])
|
||||
|
|
|
@ -9,7 +9,7 @@ from django.db import transaction
|
|||
|
||||
from rest_framework import serializers
|
||||
|
||||
from funkwhale_api.common import utils as funkwhale_utils
|
||||
from funkwhale_api.common import utils as common_utils
|
||||
from funkwhale_api.common import models as common_models
|
||||
from funkwhale_api.music import licenses
|
||||
from funkwhale_api.music import models as music_models
|
||||
|
@ -611,9 +611,9 @@ class PaginatedCollectionSerializer(jsonld.JsonLdSerializer):
|
|||
|
||||
def to_representation(self, conf):
|
||||
paginator = Paginator(conf["items"], conf.get("page_size", 20))
|
||||
first = funkwhale_utils.set_query_parameter(conf["id"], page=1)
|
||||
first = common_utils.set_query_parameter(conf["id"], page=1)
|
||||
current = first
|
||||
last = funkwhale_utils.set_query_parameter(conf["id"], page=paginator.num_pages)
|
||||
last = common_utils.set_query_parameter(conf["id"], page=paginator.num_pages)
|
||||
d = {
|
||||
"id": conf["id"],
|
||||
# XXX Stable release: remove the obsolete actor field
|
||||
|
@ -646,7 +646,7 @@ class LibrarySerializer(PaginatedCollectionSerializer):
|
|||
)
|
||||
|
||||
class Meta:
|
||||
jsonld_mapping = funkwhale_utils.concat_dicts(
|
||||
jsonld_mapping = common_utils.concat_dicts(
|
||||
PAGINATED_COLLECTION_JSONLD_MAPPING,
|
||||
{
|
||||
"name": jsonld.first_val(contexts.AS.name),
|
||||
|
@ -740,11 +740,11 @@ class CollectionPageSerializer(jsonld.JsonLdSerializer):
|
|||
|
||||
def to_representation(self, conf):
|
||||
page = conf["page"]
|
||||
first = funkwhale_utils.set_query_parameter(conf["id"], page=1)
|
||||
last = funkwhale_utils.set_query_parameter(
|
||||
first = common_utils.set_query_parameter(conf["id"], page=1)
|
||||
last = common_utils.set_query_parameter(
|
||||
conf["id"], page=page.paginator.num_pages
|
||||
)
|
||||
id = funkwhale_utils.set_query_parameter(conf["id"], page=page.number)
|
||||
id = common_utils.set_query_parameter(conf["id"], page=page.number)
|
||||
d = {
|
||||
"id": id,
|
||||
"partOf": conf["id"],
|
||||
|
@ -764,12 +764,12 @@ class CollectionPageSerializer(jsonld.JsonLdSerializer):
|
|||
}
|
||||
|
||||
if page.has_previous():
|
||||
d["prev"] = funkwhale_utils.set_query_parameter(
|
||||
d["prev"] = common_utils.set_query_parameter(
|
||||
conf["id"], page=page.previous_page_number()
|
||||
)
|
||||
|
||||
if page.has_next():
|
||||
d["next"] = funkwhale_utils.set_query_parameter(
|
||||
d["next"] = common_utils.set_query_parameter(
|
||||
conf["id"], page=page.next_page_number()
|
||||
)
|
||||
d.update(get_additional_fields(conf))
|
||||
|
@ -784,6 +784,8 @@ MUSIC_ENTITY_JSONLD_MAPPING = {
|
|||
"musicbrainzId": jsonld.first_val(contexts.FW.musicbrainzId),
|
||||
"attributedTo": jsonld.first_id(contexts.AS.attributedTo),
|
||||
"tags": jsonld.raw(contexts.AS.tag),
|
||||
"mediaType": jsonld.first_val(contexts.AS.mediaType),
|
||||
"content": jsonld.first_val(contexts.AS.content),
|
||||
}
|
||||
|
||||
|
||||
|
@ -805,6 +807,28 @@ def repr_tag(tag_name):
|
|||
return {"type": "Hashtag", "name": "#{}".format(tag_name)}
|
||||
|
||||
|
||||
def include_content(repr, content_obj):
|
||||
if not content_obj:
|
||||
return
|
||||
|
||||
repr["content"] = common_utils.render_html(
|
||||
content_obj.text, content_obj.content_type
|
||||
)
|
||||
repr["mediaType"] = "text/html"
|
||||
|
||||
|
||||
class TruncatedCharField(serializers.CharField):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.truncate_length = kwargs.pop("truncate_length")
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def to_internal_value(self, v):
|
||||
v = super().to_internal_value(v)
|
||||
if v:
|
||||
v = v[: self.truncate_length]
|
||||
return v
|
||||
|
||||
|
||||
class MusicEntitySerializer(jsonld.JsonLdSerializer):
|
||||
id = serializers.URLField(max_length=500)
|
||||
published = serializers.DateTimeField()
|
||||
|
@ -815,13 +839,23 @@ class MusicEntitySerializer(jsonld.JsonLdSerializer):
|
|||
tags = serializers.ListField(
|
||||
child=TagSerializer(), min_length=0, required=False, allow_null=True
|
||||
)
|
||||
mediaType = serializers.ChoiceField(
|
||||
choices=common_models.CONTENT_TEXT_SUPPORTED_TYPES,
|
||||
default="text/html",
|
||||
required=False,
|
||||
)
|
||||
content = TruncatedCharField(
|
||||
truncate_length=common_models.CONTENT_TEXT_MAX_LENGTH,
|
||||
required=False,
|
||||
allow_null=True,
|
||||
)
|
||||
|
||||
@transaction.atomic
|
||||
def update(self, instance, validated_data):
|
||||
attributed_to_fid = validated_data.get("attributedTo")
|
||||
if attributed_to_fid:
|
||||
validated_data["attributedTo"] = actors.get_actor(attributed_to_fid)
|
||||
updated_fields = funkwhale_utils.get_updated_fields(
|
||||
updated_fields = common_utils.get_updated_fields(
|
||||
self.updateable_fields, validated_data, instance
|
||||
)
|
||||
updated_fields = self.validate_updated_data(instance, updated_fields)
|
||||
|
@ -831,6 +865,9 @@ class MusicEntitySerializer(jsonld.JsonLdSerializer):
|
|||
|
||||
tags = [t["name"] for t in validated_data.get("tags", []) or []]
|
||||
tags_models.set_tags(instance, *tags)
|
||||
common_utils.attach_content(
|
||||
instance, "description", validated_data.get("description")
|
||||
)
|
||||
return instance
|
||||
|
||||
def get_tags_repr(self, instance):
|
||||
|
@ -842,6 +879,15 @@ class MusicEntitySerializer(jsonld.JsonLdSerializer):
|
|||
def validate_updated_data(self, instance, validated_data):
|
||||
return validated_data
|
||||
|
||||
def validate(self, data):
|
||||
validated_data = super().validate(data)
|
||||
if data.get("content"):
|
||||
validated_data["description"] = {
|
||||
"content_type": data["mediaType"],
|
||||
"text": data["content"],
|
||||
}
|
||||
return validated_data
|
||||
|
||||
|
||||
class ArtistSerializer(MusicEntitySerializer):
|
||||
updateable_fields = [
|
||||
|
@ -866,7 +912,7 @@ class ArtistSerializer(MusicEntitySerializer):
|
|||
else None,
|
||||
"tag": self.get_tags_repr(instance),
|
||||
}
|
||||
|
||||
include_content(d, instance.description)
|
||||
if self.context.get("include_ap_context", self.parent is None):
|
||||
d["@context"] = jsonld.get_default_context()
|
||||
return d
|
||||
|
@ -888,7 +934,7 @@ class AlbumSerializer(MusicEntitySerializer):
|
|||
|
||||
class Meta:
|
||||
model = music_models.Album
|
||||
jsonld_mapping = funkwhale_utils.concat_dicts(
|
||||
jsonld_mapping = common_utils.concat_dicts(
|
||||
MUSIC_ENTITY_JSONLD_MAPPING,
|
||||
{
|
||||
"released": jsonld.first_val(contexts.FW.released),
|
||||
|
@ -917,6 +963,7 @@ class AlbumSerializer(MusicEntitySerializer):
|
|||
else None,
|
||||
"tag": self.get_tags_repr(instance),
|
||||
}
|
||||
include_content(d, instance.description)
|
||||
if instance.attachment_cover:
|
||||
d["cover"] = {
|
||||
"type": "Link",
|
||||
|
@ -968,7 +1015,7 @@ class TrackSerializer(MusicEntitySerializer):
|
|||
|
||||
class Meta:
|
||||
model = music_models.Track
|
||||
jsonld_mapping = funkwhale_utils.concat_dicts(
|
||||
jsonld_mapping = common_utils.concat_dicts(
|
||||
MUSIC_ENTITY_JSONLD_MAPPING,
|
||||
{
|
||||
"album": jsonld.first_obj(contexts.FW.album),
|
||||
|
@ -1006,7 +1053,7 @@ class TrackSerializer(MusicEntitySerializer):
|
|||
else None,
|
||||
"tag": self.get_tags_repr(instance),
|
||||
}
|
||||
|
||||
include_content(d, instance.description)
|
||||
if self.context.get("include_ap_context", self.parent is None):
|
||||
d["@context"] = jsonld.get_default_context()
|
||||
return d
|
||||
|
@ -1017,23 +1064,21 @@ class TrackSerializer(MusicEntitySerializer):
|
|||
references = {}
|
||||
actors_to_fetch = set()
|
||||
actors_to_fetch.add(
|
||||
funkwhale_utils.recursive_getattr(
|
||||
common_utils.recursive_getattr(
|
||||
validated_data, "attributedTo", permissive=True
|
||||
)
|
||||
)
|
||||
actors_to_fetch.add(
|
||||
funkwhale_utils.recursive_getattr(
|
||||
common_utils.recursive_getattr(
|
||||
validated_data, "album.attributedTo", permissive=True
|
||||
)
|
||||
)
|
||||
artists = (
|
||||
funkwhale_utils.recursive_getattr(
|
||||
validated_data, "artists", permissive=True
|
||||
)
|
||||
common_utils.recursive_getattr(validated_data, "artists", permissive=True)
|
||||
or []
|
||||
)
|
||||
album_artists = (
|
||||
funkwhale_utils.recursive_getattr(
|
||||
common_utils.recursive_getattr(
|
||||
validated_data, "album.artists", permissive=True
|
||||
)
|
||||
or []
|
||||
|
@ -1244,6 +1289,7 @@ class ChannelUploadSerializer(serializers.Serializer):
|
|||
},
|
||||
],
|
||||
}
|
||||
include_content(data, upload.track.description)
|
||||
tags = [item.tag.name for item in upload.get_all_tagged_items()]
|
||||
if tags:
|
||||
data["tag"] = [repr_tag(name) for name in tags]
|
||||
|
|
|
@ -225,11 +225,14 @@ class MusicLibraryViewSet(
|
|||
"album__attributed_to",
|
||||
"attributed_to",
|
||||
"album__attachment_cover",
|
||||
"description",
|
||||
).prefetch_related(
|
||||
"tagged_items__tag",
|
||||
"album__tagged_items__tag",
|
||||
"album__artist__tagged_items__tag",
|
||||
"artist__tagged_items__tag",
|
||||
"artist__description",
|
||||
"album__description",
|
||||
),
|
||||
)
|
||||
),
|
||||
|
@ -278,6 +281,7 @@ class MusicUploadViewSet(
|
|||
"library__actor",
|
||||
"track__artist",
|
||||
"track__album__artist",
|
||||
"track__description",
|
||||
"track__album__attachment_cover",
|
||||
)
|
||||
serializer_class = serializers.UploadSerializer
|
||||
|
@ -299,7 +303,7 @@ class MusicArtistViewSet(
|
|||
):
|
||||
authentication_classes = [authentication.SignatureAuthentication]
|
||||
renderer_classes = renderers.get_ap_renderers()
|
||||
queryset = music_models.Artist.objects.local()
|
||||
queryset = music_models.Artist.objects.local().select_related("description")
|
||||
serializer_class = serializers.ArtistSerializer
|
||||
lookup_field = "uuid"
|
||||
|
||||
|
@ -309,7 +313,9 @@ class MusicAlbumViewSet(
|
|||
):
|
||||
authentication_classes = [authentication.SignatureAuthentication]
|
||||
renderer_classes = renderers.get_ap_renderers()
|
||||
queryset = music_models.Album.objects.local().select_related("artist")
|
||||
queryset = music_models.Album.objects.local().select_related(
|
||||
"artist__description", "description"
|
||||
)
|
||||
serializer_class = serializers.AlbumSerializer
|
||||
lookup_field = "uuid"
|
||||
|
||||
|
@ -320,7 +326,7 @@ class MusicTrackViewSet(
|
|||
authentication_classes = [authentication.SignatureAuthentication]
|
||||
renderer_classes = renderers.get_ap_renderers()
|
||||
queryset = music_models.Track.objects.local().select_related(
|
||||
"album__artist", "artist"
|
||||
"album__artist", "album__description", "artist__description", "description"
|
||||
)
|
||||
serializer_class = serializers.TrackSerializer
|
||||
lookup_field = "uuid"
|
||||
|
|
|
@ -383,7 +383,9 @@ class ManageNestedAlbumSerializer(ManageBaseAlbumSerializer):
|
|||
return getattr(obj, "tracks_count", None)
|
||||
|
||||
|
||||
class ManageArtistSerializer(ManageBaseArtistSerializer):
|
||||
class ManageArtistSerializer(
|
||||
music_serializers.OptionalDescriptionMixin, ManageBaseArtistSerializer
|
||||
):
|
||||
albums = ManageNestedAlbumSerializer(many=True)
|
||||
tracks = ManageNestedTrackSerializer(many=True)
|
||||
attributed_to = ManageBaseActorSerializer()
|
||||
|
@ -407,7 +409,9 @@ class ManageNestedArtistSerializer(ManageBaseArtistSerializer):
|
|||
pass
|
||||
|
||||
|
||||
class ManageAlbumSerializer(ManageBaseAlbumSerializer):
|
||||
class ManageAlbumSerializer(
|
||||
music_serializers.OptionalDescriptionMixin, ManageBaseAlbumSerializer
|
||||
):
|
||||
tracks = ManageNestedTrackSerializer(many=True)
|
||||
attributed_to = ManageBaseActorSerializer()
|
||||
artist = ManageNestedArtistSerializer()
|
||||
|
@ -435,7 +439,9 @@ class ManageTrackAlbumSerializer(ManageBaseAlbumSerializer):
|
|||
fields = ManageBaseAlbumSerializer.Meta.fields + ["artist"]
|
||||
|
||||
|
||||
class ManageTrackSerializer(ManageNestedTrackSerializer):
|
||||
class ManageTrackSerializer(
|
||||
music_serializers.OptionalDescriptionMixin, ManageNestedTrackSerializer
|
||||
):
|
||||
artist = ManageNestedArtistSerializer()
|
||||
album = ManageTrackAlbumSerializer()
|
||||
attributed_to = ManageBaseActorSerializer()
|
||||
|
|
|
@ -100,6 +100,11 @@ class ManageArtistViewSet(
|
|||
result = serializer.save()
|
||||
return response.Response(result, status=200)
|
||||
|
||||
def get_serializer_context(self):
|
||||
context = super().get_serializer_context()
|
||||
context["description"] = self.action in ["retrieve", "create", "update"]
|
||||
return context
|
||||
|
||||
|
||||
class ManageAlbumViewSet(
|
||||
mixins.ListModelMixin,
|
||||
|
@ -134,6 +139,11 @@ class ManageAlbumViewSet(
|
|||
result = serializer.save()
|
||||
return response.Response(result, status=200)
|
||||
|
||||
def get_serializer_context(self):
|
||||
context = super().get_serializer_context()
|
||||
context["description"] = self.action in ["retrieve", "create", "update"]
|
||||
return context
|
||||
|
||||
|
||||
uploads_subquery = (
|
||||
music_models.Upload.objects.filter(track_id=OuterRef("pk"))
|
||||
|
@ -186,6 +196,11 @@ class ManageTrackViewSet(
|
|||
result = serializer.save()
|
||||
return response.Response(result, status=200)
|
||||
|
||||
def get_serializer_context(self):
|
||||
context = super().get_serializer_context()
|
||||
context["description"] = self.action in ["retrieve", "create", "update"]
|
||||
return context
|
||||
|
||||
|
||||
uploads_subquery = (
|
||||
music_models.Upload.objects.filter(library_id=OuterRef("pk"))
|
||||
|
|
|
@ -168,6 +168,17 @@ def get_mp3_recording_id(f, k):
|
|||
raise TagNotFound(k)
|
||||
|
||||
|
||||
def get_mp3_comment(f, k):
|
||||
keys_to_try = ["COMM", "COMM::eng"]
|
||||
for key in keys_to_try:
|
||||
try:
|
||||
return get_id3_tag(f, key)
|
||||
except TagNotFound:
|
||||
pass
|
||||
|
||||
raise TagNotFound("COMM")
|
||||
|
||||
|
||||
VALIDATION = {}
|
||||
|
||||
CONF = {
|
||||
|
@ -192,6 +203,7 @@ CONF = {
|
|||
"field": "metadata_block_picture",
|
||||
"to_application": clean_ogg_pictures,
|
||||
},
|
||||
"comment": {"field": "comment"},
|
||||
},
|
||||
},
|
||||
"OggVorbis": {
|
||||
|
@ -215,6 +227,7 @@ CONF = {
|
|||
"field": "metadata_block_picture",
|
||||
"to_application": clean_ogg_pictures,
|
||||
},
|
||||
"comment": {"field": "comment"},
|
||||
},
|
||||
},
|
||||
"OggTheora": {
|
||||
|
@ -234,6 +247,7 @@ CONF = {
|
|||
"license": {},
|
||||
"copyright": {},
|
||||
"genre": {},
|
||||
"comment": {"field": "comment"},
|
||||
},
|
||||
},
|
||||
"MP3": {
|
||||
|
@ -255,6 +269,7 @@ CONF = {
|
|||
"pictures": {},
|
||||
"license": {"field": "WCOP"},
|
||||
"copyright": {"field": "TCOP"},
|
||||
"comment": {"field": "COMM", "getter": get_mp3_comment},
|
||||
},
|
||||
},
|
||||
"MP4": {
|
||||
|
@ -282,6 +297,7 @@ CONF = {
|
|||
"pictures": {},
|
||||
"license": {"field": "----:com.apple.iTunes:LICENSE"},
|
||||
"copyright": {"field": "cprt"},
|
||||
"comment": {"field": "©cmt"},
|
||||
},
|
||||
},
|
||||
"FLAC": {
|
||||
|
@ -304,6 +320,7 @@ CONF = {
|
|||
"pictures": {},
|
||||
"license": {},
|
||||
"copyright": {},
|
||||
"comment": {},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -322,6 +339,7 @@ ALL_FIELDS = [
|
|||
"mbid",
|
||||
"license",
|
||||
"copyright",
|
||||
"comment",
|
||||
]
|
||||
|
||||
|
||||
|
@ -657,6 +675,21 @@ class PositionField(serializers.CharField):
|
|||
pass
|
||||
|
||||
|
||||
class DescriptionField(serializers.CharField):
|
||||
def get_value(self, data):
|
||||
return data
|
||||
|
||||
def to_internal_value(self, data):
|
||||
try:
|
||||
value = data.get("comment") or None
|
||||
except TagNotFound:
|
||||
return None
|
||||
if not value:
|
||||
return None
|
||||
value = super().to_internal_value(value)
|
||||
return {"text": value, "content_type": "text/plain"}
|
||||
|
||||
|
||||
class TrackMetadataSerializer(serializers.Serializer):
|
||||
title = serializers.CharField()
|
||||
position = PositionField(allow_blank=True, allow_null=True, required=False)
|
||||
|
@ -665,6 +698,7 @@ class TrackMetadataSerializer(serializers.Serializer):
|
|||
license = serializers.CharField(allow_blank=True, allow_null=True, required=False)
|
||||
mbid = MBIDField()
|
||||
tags = TagsField(allow_blank=True, allow_null=True, required=False)
|
||||
description = DescriptionField(allow_null=True, allow_blank=True, required=False)
|
||||
|
||||
album = AlbumField()
|
||||
artists = ArtistField()
|
||||
|
@ -672,6 +706,7 @@ class TrackMetadataSerializer(serializers.Serializer):
|
|||
|
||||
remove_blank_null_fields = [
|
||||
"copyright",
|
||||
"description",
|
||||
"license",
|
||||
"position",
|
||||
"disc_number",
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
# Generated by Django 2.2.7 on 2020-01-13 10:18
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('common', '0006_content'),
|
||||
('music', '0045_full_text_search_stop_words'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='album',
|
||||
name='description',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='common.Content'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='artist',
|
||||
name='description',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='common.Content'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='track',
|
||||
name='description',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='common.Content'),
|
||||
),
|
||||
]
|
|
@ -227,6 +227,9 @@ class Artist(APIModelMixin):
|
|||
content_type_field="object_content_type",
|
||||
object_id_field="object_id",
|
||||
)
|
||||
description = models.ForeignKey(
|
||||
"common.Content", null=True, blank=True, on_delete=models.SET_NULL
|
||||
)
|
||||
|
||||
api = musicbrainz.api.artists
|
||||
objects = ArtistQuerySet.as_manager()
|
||||
|
@ -327,6 +330,10 @@ class Album(APIModelMixin):
|
|||
object_id_field="object_id",
|
||||
)
|
||||
|
||||
description = models.ForeignKey(
|
||||
"common.Content", null=True, blank=True, on_delete=models.SET_NULL
|
||||
)
|
||||
|
||||
api_includes = ["artist-credits", "recordings", "media", "release-groups"]
|
||||
api = musicbrainz.api.releases
|
||||
federation_namespace = "albums"
|
||||
|
@ -508,6 +515,10 @@ class Track(APIModelMixin):
|
|||
copyright = models.CharField(
|
||||
max_length=MAX_LENGTHS["COPYRIGHT"], null=True, blank=True
|
||||
)
|
||||
description = models.ForeignKey(
|
||||
"common.Content", null=True, blank=True, on_delete=models.SET_NULL
|
||||
)
|
||||
|
||||
federation_namespace = "tracks"
|
||||
musicbrainz_model = "recording"
|
||||
api = musicbrainz.api.recordings
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
from funkwhale_api.common import models as common_models
|
||||
from funkwhale_api.common import mutations
|
||||
from funkwhale_api.common import serializers as common_serializers
|
||||
from funkwhale_api.common import utils as common_utils
|
||||
|
||||
from funkwhale_api.federation import routes
|
||||
from funkwhale_api.tags import models as tags_models
|
||||
from funkwhale_api.tags import serializers as tags_serializers
|
||||
|
@ -23,11 +25,13 @@ def can_approve(obj, actor):
|
|||
|
||||
class TagMutation(mutations.UpdateMutationSerializer):
|
||||
tags = tags_serializers.TagsListField()
|
||||
previous_state_handlers = {
|
||||
"tags": lambda obj: list(
|
||||
|
||||
def get_previous_state_handlers(self):
|
||||
handlers = super().get_previous_state_handlers()
|
||||
handlers["tags"] = lambda obj: list(
|
||||
sorted(obj.tagged_items.values_list("tag__name", flat=True))
|
||||
)
|
||||
}
|
||||
return handlers
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
tags = validated_data.pop("tags", [])
|
||||
|
@ -36,17 +40,36 @@ class TagMutation(mutations.UpdateMutationSerializer):
|
|||
return r
|
||||
|
||||
|
||||
class DescriptionMutation(mutations.UpdateMutationSerializer):
|
||||
description = common_serializers.ContentSerializer()
|
||||
|
||||
def get_previous_state_handlers(self):
|
||||
handlers = super().get_previous_state_handlers()
|
||||
handlers["description"] = (
|
||||
lambda obj: common_serializers.ContentSerializer(obj.description).data
|
||||
if obj.description_id
|
||||
else None
|
||||
)
|
||||
return handlers
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
description = validated_data.pop("description", None)
|
||||
r = super().update(instance, validated_data)
|
||||
common_utils.attach_content(instance, "description", description)
|
||||
return r
|
||||
|
||||
|
||||
@mutations.registry.connect(
|
||||
"update",
|
||||
models.Track,
|
||||
perm_checkers={"suggest": can_suggest, "approve": can_approve},
|
||||
)
|
||||
class TrackMutationSerializer(TagMutation):
|
||||
class TrackMutationSerializer(TagMutation, DescriptionMutation):
|
||||
serialized_relations = {"license": "code"}
|
||||
|
||||
class Meta:
|
||||
model = models.Track
|
||||
fields = ["license", "title", "position", "copyright", "tags"]
|
||||
fields = ["license", "title", "position", "copyright", "tags", "description"]
|
||||
|
||||
def post_apply(self, obj, validated_data):
|
||||
routes.outbox.dispatch(
|
||||
|
@ -59,10 +82,10 @@ class TrackMutationSerializer(TagMutation):
|
|||
models.Artist,
|
||||
perm_checkers={"suggest": can_suggest, "approve": can_approve},
|
||||
)
|
||||
class ArtistMutationSerializer(TagMutation):
|
||||
class ArtistMutationSerializer(TagMutation, DescriptionMutation):
|
||||
class Meta:
|
||||
model = models.Artist
|
||||
fields = ["name", "tags"]
|
||||
fields = ["name", "tags", "description"]
|
||||
|
||||
def post_apply(self, obj, validated_data):
|
||||
routes.outbox.dispatch(
|
||||
|
@ -75,27 +98,23 @@ class ArtistMutationSerializer(TagMutation):
|
|||
models.Album,
|
||||
perm_checkers={"suggest": can_suggest, "approve": can_approve},
|
||||
)
|
||||
class AlbumMutationSerializer(TagMutation):
|
||||
class AlbumMutationSerializer(TagMutation, DescriptionMutation):
|
||||
cover = common_serializers.RelatedField(
|
||||
"uuid", queryset=common_models.Attachment.objects.all().local(), serializer=None
|
||||
)
|
||||
|
||||
serialized_relations = {"cover": "uuid"}
|
||||
previous_state_handlers = dict(
|
||||
list(TagMutation.previous_state_handlers.items())
|
||||
+ [
|
||||
(
|
||||
"cover",
|
||||
lambda obj: str(obj.attachment_cover.uuid)
|
||||
if obj.attachment_cover
|
||||
else None,
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = models.Album
|
||||
fields = ["title", "release_date", "tags", "cover"]
|
||||
fields = ["title", "release_date", "tags", "cover", "description"]
|
||||
|
||||
def get_previous_state_handlers(self):
|
||||
handlers = super().get_previous_state_handlers()
|
||||
handlers["cover"] = (
|
||||
lambda obj: str(obj.attachment_cover.uuid) if obj.attachment_cover else None
|
||||
)
|
||||
return handlers
|
||||
|
||||
def post_apply(self, obj, validated_data):
|
||||
routes.outbox.dispatch(
|
||||
|
|
|
@ -49,6 +49,20 @@ def serialize_attributed_to(self, obj):
|
|||
return federation_serializers.APIActorSerializer(obj.attributed_to).data
|
||||
|
||||
|
||||
class OptionalDescriptionMixin(object):
|
||||
def to_representation(self, obj):
|
||||
repr = super().to_representation(obj)
|
||||
if self.context.get("description", False):
|
||||
description = obj.description
|
||||
repr["description"] = (
|
||||
common_serializers.ContentSerializer(description).data
|
||||
if description
|
||||
else None
|
||||
)
|
||||
|
||||
return repr
|
||||
|
||||
|
||||
class LicenseSerializer(serializers.Serializer):
|
||||
id = serializers.SerializerMethodField()
|
||||
url = serializers.URLField()
|
||||
|
@ -96,7 +110,7 @@ class ArtistAlbumSerializer(serializers.Serializer):
|
|||
DATETIME_FIELD = serializers.DateTimeField()
|
||||
|
||||
|
||||
class ArtistWithAlbumsSerializer(serializers.Serializer):
|
||||
class ArtistWithAlbumsSerializer(OptionalDescriptionMixin, serializers.Serializer):
|
||||
albums = ArtistAlbumSerializer(many=True)
|
||||
tags = serializers.SerializerMethodField()
|
||||
attributed_to = serializers.SerializerMethodField()
|
||||
|
@ -152,7 +166,7 @@ def serialize_album_track(track):
|
|||
}
|
||||
|
||||
|
||||
class AlbumSerializer(serializers.Serializer):
|
||||
class AlbumSerializer(OptionalDescriptionMixin, serializers.Serializer):
|
||||
tracks = serializers.SerializerMethodField()
|
||||
artist = serializers.SerializerMethodField()
|
||||
cover = cover_field
|
||||
|
@ -225,7 +239,7 @@ def serialize_upload(upload):
|
|||
}
|
||||
|
||||
|
||||
class TrackSerializer(serializers.Serializer):
|
||||
class TrackSerializer(OptionalDescriptionMixin, serializers.Serializer):
|
||||
artist = serializers.SerializerMethodField()
|
||||
album = TrackAlbumSerializer(read_only=True)
|
||||
uploads = serializers.SerializerMethodField()
|
||||
|
|
|
@ -12,6 +12,7 @@ from musicbrainzngs import ResponseError
|
|||
from requests.exceptions import RequestException
|
||||
|
||||
from funkwhale_api.common import channels, preferences
|
||||
from funkwhale_api.common import utils as common_utils
|
||||
from funkwhale_api.federation import routes
|
||||
from funkwhale_api.federation import library as lb
|
||||
from funkwhale_api.federation import utils as federation_utils
|
||||
|
@ -309,6 +310,7 @@ def federation_audio_track_to_metadata(payload, references):
|
|||
"disc_number": payload.get("disc"),
|
||||
"license": payload.get("license"),
|
||||
"copyright": payload.get("copyright"),
|
||||
"description": payload.get("description"),
|
||||
"attributed_to": references.get(payload.get("attributedTo")),
|
||||
"mbid": str(payload.get("musicbrainzId"))
|
||||
if payload.get("musicbrainzId")
|
||||
|
@ -317,6 +319,7 @@ def federation_audio_track_to_metadata(payload, references):
|
|||
"title": payload["album"]["name"],
|
||||
"fdate": payload["album"]["published"],
|
||||
"fid": payload["album"]["id"],
|
||||
"description": payload["album"].get("description"),
|
||||
"attributed_to": references.get(payload["album"].get("attributedTo")),
|
||||
"mbid": str(payload["album"]["musicbrainzId"])
|
||||
if payload["album"].get("musicbrainzId")
|
||||
|
@ -328,6 +331,7 @@ def federation_audio_track_to_metadata(payload, references):
|
|||
"fid": a["id"],
|
||||
"name": a["name"],
|
||||
"fdate": a["published"],
|
||||
"description": a.get("description"),
|
||||
"attributed_to": references.get(a.get("attributedTo")),
|
||||
"mbid": str(a["musicbrainzId"]) if a.get("musicbrainzId") else None,
|
||||
"tags": [t["name"] for t in a.get("tags", []) or []],
|
||||
|
@ -340,6 +344,7 @@ def federation_audio_track_to_metadata(payload, references):
|
|||
"fid": a["id"],
|
||||
"name": a["name"],
|
||||
"fdate": a["published"],
|
||||
"description": a.get("description"),
|
||||
"attributed_to": references.get(a.get("attributedTo")),
|
||||
"mbid": str(a["musicbrainzId"]) if a.get("musicbrainzId") else None,
|
||||
"tags": [t["name"] for t in a.get("tags", []) or []],
|
||||
|
@ -505,6 +510,9 @@ def _get_track(data, attributed_to=None, **forced_values):
|
|||
)
|
||||
if created:
|
||||
tags_models.add_tags(artist, *artist_data.get("tags", []))
|
||||
common_utils.attach_content(
|
||||
artist, "description", artist_data.get("description")
|
||||
)
|
||||
|
||||
if "album" in forced_values:
|
||||
album = forced_values["album"]
|
||||
|
@ -539,6 +547,9 @@ def _get_track(data, attributed_to=None, **forced_values):
|
|||
)
|
||||
if created:
|
||||
tags_models.add_tags(album_artist, *album_artist_data.get("tags", []))
|
||||
common_utils.attach_content(
|
||||
album_artist, "description", album_artist_data.get("description")
|
||||
)
|
||||
|
||||
# get / create album
|
||||
album_data = data["album"]
|
||||
|
@ -569,6 +580,9 @@ def _get_track(data, attributed_to=None, **forced_values):
|
|||
)
|
||||
if created:
|
||||
tags_models.add_tags(album, *album_data.get("tags", []))
|
||||
common_utils.attach_content(
|
||||
album, "description", album_data.get("description")
|
||||
)
|
||||
|
||||
# get / create track
|
||||
track_title = (
|
||||
|
@ -602,6 +616,7 @@ def _get_track(data, attributed_to=None, **forced_values):
|
|||
query |= Q(mbid=track_mbid)
|
||||
if track_fid:
|
||||
query |= Q(fid=track_fid)
|
||||
|
||||
defaults = {
|
||||
"title": track_title,
|
||||
"album": album,
|
||||
|
@ -627,6 +642,8 @@ def _get_track(data, attributed_to=None, **forced_values):
|
|||
forced_values["tags"] if "tags" in forced_values else data.get("tags", [])
|
||||
)
|
||||
tags_models.add_tags(track, *tags)
|
||||
common_utils.attach_content(track, "description", data.get("description"))
|
||||
|
||||
return track
|
||||
|
||||
|
||||
|
|
|
@ -143,6 +143,11 @@ class ArtistViewSet(
|
|||
obj = refetch_obj(obj, self.get_queryset())
|
||||
return obj
|
||||
|
||||
def get_serializer_context(self):
|
||||
context = super().get_serializer_context()
|
||||
context["description"] = self.action in ["retrieve", "create", "update"]
|
||||
return context
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
albums = models.Album.objects.with_tracks_count().select_related(
|
||||
|
@ -194,6 +199,11 @@ class AlbumViewSet(
|
|||
obj = refetch_obj(obj, self.get_queryset())
|
||||
return obj
|
||||
|
||||
def get_serializer_context(self):
|
||||
context = super().get_serializer_context()
|
||||
context["description"] = self.action in ["retrieve", "create", "update"]
|
||||
return context
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
tracks = (
|
||||
|
@ -332,6 +342,11 @@ class TrackViewSet(
|
|||
get_libraries(filter_uploads=lambda o, uploads: uploads.filter(track=o))
|
||||
)
|
||||
|
||||
def get_serializer_context(self):
|
||||
context = super().get_serializer_context()
|
||||
context["description"] = self.action in ["retrieve", "create", "update"]
|
||||
return context
|
||||
|
||||
|
||||
def strip_absolute_media_url(path):
|
||||
if (
|
||||
|
|
|
@ -76,3 +76,5 @@ django-cacheops==4.2
|
|||
|
||||
click>=7,<8
|
||||
service_identity==18.1.0
|
||||
markdown>=3,<4
|
||||
bleach>=3,<4
|
||||
|
|
|
@ -71,3 +71,17 @@ def test_attachment_queryset_attached(args, expected, factories, queryset_equal_
|
|||
queryset = attachments[0].__class__.objects.attached(*args).order_by("id")
|
||||
expected_objs = [attachments[i] for i in expected]
|
||||
assert queryset == expected_objs
|
||||
|
||||
|
||||
def test_removing_obj_removes_content(factories):
|
||||
kept_content = factories["common.Content"]()
|
||||
removed_content = factories["common.Content"]()
|
||||
track1 = factories["music.Track"](description=removed_content)
|
||||
factories["music.Track"](description=kept_content)
|
||||
|
||||
track1.delete()
|
||||
|
||||
with pytest.raises(removed_content.DoesNotExist):
|
||||
removed_content.refresh_from_db()
|
||||
|
||||
kept_content.refresh_from_db()
|
||||
|
|
|
@ -7,6 +7,7 @@ from django.urls import reverse
|
|||
import django_filters
|
||||
|
||||
from funkwhale_api.common import serializers
|
||||
from funkwhale_api.common import utils
|
||||
from funkwhale_api.users import models
|
||||
from funkwhale_api.federation import utils as federation_utils
|
||||
|
||||
|
@ -252,3 +253,17 @@ def test_attachment_serializer_remote_file(factories, to_api_date):
|
|||
serializer = serializers.AttachmentSerializer(attachment)
|
||||
|
||||
assert serializer.data == expected
|
||||
|
||||
|
||||
def test_content_serializer(factories):
|
||||
content = factories["common.Content"]()
|
||||
|
||||
expected = {
|
||||
"text": content.text,
|
||||
"content_type": content.content_type,
|
||||
"html": utils.render_html(content.text, content.content_type),
|
||||
}
|
||||
|
||||
serializer = serializers.ContentSerializer(content)
|
||||
|
||||
assert serializer.data == expected
|
||||
|
|
|
@ -99,3 +99,28 @@ def test_get_updated_fields(conf, mock_args, data, expected, mocker):
|
|||
)
|
||||
def test_join_url(start, end, expected):
|
||||
assert utils.join_url(start, end) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"text, content_type, expected",
|
||||
[
|
||||
("hello world", "text/markdown", "<p>hello world</p>"),
|
||||
("hello world", "text/plain", "<p>hello world</p>"),
|
||||
("<strong>hello world</strong>", "text/html", "<strong>hello world</strong>"),
|
||||
# images and other non whitelisted html should be removed
|
||||
("hello world\n", "text/markdown", "<p>hello world</p>"),
|
||||
(
|
||||
"hello world\n\n<script></script>\n\n<style></style>",
|
||||
"text/markdown",
|
||||
"<p>hello world</p>",
|
||||
),
|
||||
(
|
||||
"<p>hello world</p><script></script>\n\n<style></style>",
|
||||
"text/html",
|
||||
"<p>hello world</p>",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_render_html(text, content_type, expected):
|
||||
result = utils.render_html(text, content_type)
|
||||
assert result == expected
|
||||
|
|
|
@ -5,6 +5,7 @@ import uuid
|
|||
from django.core.paginator import Paginator
|
||||
from django.utils import timezone
|
||||
|
||||
from funkwhale_api.common import utils as common_utils
|
||||
from funkwhale_api.federation import contexts
|
||||
from funkwhale_api.federation import keys
|
||||
from funkwhale_api.federation import jsonld
|
||||
|
@ -560,7 +561,10 @@ def test_music_library_serializer_from_private(factories, mocker):
|
|||
|
||||
|
||||
def test_activity_pub_artist_serializer_to_ap(factories):
|
||||
artist = factories["music.Artist"](attributed=True, set_tags=["Punk", "Rock"])
|
||||
content = factories["common.Content"]()
|
||||
artist = factories["music.Artist"](
|
||||
description=content, attributed=True, set_tags=["Punk", "Rock"]
|
||||
)
|
||||
expected = {
|
||||
"@context": jsonld.get_default_context(),
|
||||
"type": "Artist",
|
||||
|
@ -569,6 +573,8 @@ def test_activity_pub_artist_serializer_to_ap(factories):
|
|||
"musicbrainzId": artist.mbid,
|
||||
"published": artist.creation_date.isoformat(),
|
||||
"attributedTo": artist.attributed_to.fid,
|
||||
"mediaType": "text/html",
|
||||
"content": common_utils.render_html(content.text, content.content_type),
|
||||
"tag": [
|
||||
{"type": "Hashtag", "name": "#Punk"},
|
||||
{"type": "Hashtag", "name": "#Rock"},
|
||||
|
@ -580,7 +586,10 @@ def test_activity_pub_artist_serializer_to_ap(factories):
|
|||
|
||||
|
||||
def test_activity_pub_album_serializer_to_ap(factories):
|
||||
album = factories["music.Album"](attributed=True, set_tags=["Punk", "Rock"])
|
||||
content = factories["common.Content"]()
|
||||
album = factories["music.Album"](
|
||||
description=content, attributed=True, set_tags=["Punk", "Rock"]
|
||||
)
|
||||
|
||||
expected = {
|
||||
"@context": jsonld.get_default_context(),
|
||||
|
@ -601,6 +610,8 @@ def test_activity_pub_album_serializer_to_ap(factories):
|
|||
).data
|
||||
],
|
||||
"attributedTo": album.attributed_to.fid,
|
||||
"mediaType": "text/html",
|
||||
"content": common_utils.render_html(content.text, content.content_type),
|
||||
"tag": [
|
||||
{"type": "Hashtag", "name": "#Punk"},
|
||||
{"type": "Hashtag", "name": "#Rock"},
|
||||
|
@ -653,7 +664,9 @@ def test_activity_pub_album_serializer_from_ap_update(factories, faker):
|
|||
|
||||
|
||||
def test_activity_pub_track_serializer_to_ap(factories):
|
||||
content = factories["common.Content"]()
|
||||
track = factories["music.Track"](
|
||||
description=content,
|
||||
license="cc-by-4.0",
|
||||
copyright="test",
|
||||
disc_number=3,
|
||||
|
@ -680,6 +693,8 @@ def test_activity_pub_track_serializer_to_ap(factories):
|
|||
track.album, context={"include_ap_context": False}
|
||||
).data,
|
||||
"attributedTo": track.attributed_to.fid,
|
||||
"mediaType": "text/html",
|
||||
"content": common_utils.render_html(content.text, content.content_type),
|
||||
"tag": [
|
||||
{"type": "Hashtag", "name": "#Punk"},
|
||||
{"type": "Hashtag", "name": "#Rock"},
|
||||
|
@ -709,6 +724,7 @@ def test_activity_pub_track_serializer_from_ap(factories, r_mock, mocker):
|
|||
"name": "Black in back",
|
||||
"position": 5,
|
||||
"disc": 1,
|
||||
"content": "Hello there",
|
||||
"attributedTo": track_attributed_to.fid,
|
||||
"album": {
|
||||
"type": "Album",
|
||||
|
@ -717,6 +733,8 @@ def test_activity_pub_track_serializer_from_ap(factories, r_mock, mocker):
|
|||
"musicbrainzId": str(uuid.uuid4()),
|
||||
"published": published.isoformat(),
|
||||
"released": released.isoformat(),
|
||||
"content": "Album summary",
|
||||
"mediaType": "text/markdown",
|
||||
"attributedTo": album_attributed_to.fid,
|
||||
"cover": {
|
||||
"type": "Link",
|
||||
|
@ -727,6 +745,8 @@ def test_activity_pub_track_serializer_from_ap(factories, r_mock, mocker):
|
|||
"artists": [
|
||||
{
|
||||
"type": "Artist",
|
||||
"mediaType": "text/plain",
|
||||
"content": "Artist summary",
|
||||
"id": "http://hello.artist",
|
||||
"name": "John Smith",
|
||||
"musicbrainzId": str(uuid.uuid4()),
|
||||
|
@ -741,6 +761,8 @@ def test_activity_pub_track_serializer_from_ap(factories, r_mock, mocker):
|
|||
"type": "Artist",
|
||||
"id": "http://hello.trackartist",
|
||||
"name": "Bob Smith",
|
||||
"mediaType": "text/plain",
|
||||
"content": "Other artist summary",
|
||||
"musicbrainzId": str(uuid.uuid4()),
|
||||
"attributedTo": artist_attributed_to.fid,
|
||||
"published": published.isoformat(),
|
||||
|
@ -769,6 +791,8 @@ def test_activity_pub_track_serializer_from_ap(factories, r_mock, mocker):
|
|||
assert track.creation_date == published
|
||||
assert track.attributed_to == track_attributed_to
|
||||
assert str(track.mbid) == data["musicbrainzId"]
|
||||
assert track.description.text == data["content"]
|
||||
assert track.description.content_type == "text/html"
|
||||
|
||||
assert album.from_activity == activity
|
||||
assert album.attachment_cover.file.read() == b"coucou"
|
||||
|
@ -779,6 +803,8 @@ def test_activity_pub_track_serializer_from_ap(factories, r_mock, mocker):
|
|||
assert album.creation_date == published
|
||||
assert album.release_date == released
|
||||
assert album.attributed_to == album_attributed_to
|
||||
assert album.description.text == data["album"]["content"]
|
||||
assert album.description.content_type == data["album"]["mediaType"]
|
||||
|
||||
assert artist.from_activity == activity
|
||||
assert artist.name == data["artists"][0]["name"]
|
||||
|
@ -786,6 +812,8 @@ def test_activity_pub_track_serializer_from_ap(factories, r_mock, mocker):
|
|||
assert str(artist.mbid) == data["artists"][0]["musicbrainzId"]
|
||||
assert artist.creation_date == published
|
||||
assert artist.attributed_to == artist_attributed_to
|
||||
assert artist.description.text == data["artists"][0]["content"]
|
||||
assert artist.description.content_type == data["artists"][0]["mediaType"]
|
||||
|
||||
assert album_artist.from_activity == activity
|
||||
assert album_artist.name == data["album"]["artists"][0]["name"]
|
||||
|
@ -793,6 +821,11 @@ def test_activity_pub_track_serializer_from_ap(factories, r_mock, mocker):
|
|||
assert str(album_artist.mbid) == data["album"]["artists"][0]["musicbrainzId"]
|
||||
assert album_artist.creation_date == published
|
||||
assert album_artist.attributed_to == album_artist_attributed_to
|
||||
assert album_artist.description.text == data["album"]["artists"][0]["content"]
|
||||
assert (
|
||||
album_artist.description.content_type
|
||||
== data["album"]["artists"][0]["mediaType"]
|
||||
)
|
||||
|
||||
add_tags.assert_any_call(track, *["Hello", "World"])
|
||||
add_tags.assert_any_call(album, *["AlbumTag"])
|
||||
|
@ -802,8 +835,9 @@ def test_activity_pub_track_serializer_from_ap(factories, r_mock, mocker):
|
|||
|
||||
def test_activity_pub_track_serializer_from_ap_update(factories, r_mock, mocker):
|
||||
set_tags = mocker.patch("funkwhale_api.tags.models.set_tags")
|
||||
content = factories["common.Content"]()
|
||||
track_attributed_to = factories["federation.Actor"]()
|
||||
track = factories["music.Track"]()
|
||||
track = factories["music.Track"](description=content)
|
||||
|
||||
published = timezone.now()
|
||||
data = {
|
||||
|
@ -815,6 +849,7 @@ def test_activity_pub_track_serializer_from_ap_update(factories, r_mock, mocker)
|
|||
"name": "Black in back",
|
||||
"position": 5,
|
||||
"disc": 2,
|
||||
"content": "hello there",
|
||||
"attributedTo": track_attributed_to.fid,
|
||||
"album": serializers.AlbumSerializer(track.album).data,
|
||||
"artists": [serializers.ArtistSerializer(track.artist).data],
|
||||
|
@ -835,10 +870,15 @@ def test_activity_pub_track_serializer_from_ap_update(factories, r_mock, mocker)
|
|||
assert track.position == data["position"]
|
||||
assert track.disc_number == data["disc"]
|
||||
assert track.attributed_to == track_attributed_to
|
||||
assert track.description.content_type == "text/html"
|
||||
assert track.description.text == "hello there"
|
||||
assert str(track.mbid) == data["musicbrainzId"]
|
||||
|
||||
set_tags.assert_called_once_with(track, *["Hello", "World"])
|
||||
|
||||
with pytest.raises(content.DoesNotExist):
|
||||
content.refresh_from_db()
|
||||
|
||||
|
||||
def test_activity_pub_upload_serializer_from_ap(factories, mocker, r_mock):
|
||||
activity = factories["federation.Activity"]()
|
||||
|
@ -1083,11 +1123,13 @@ def test_channel_actor_outbox_serializer(factories):
|
|||
|
||||
def test_channel_upload_serializer(factories):
|
||||
channel = factories["audio.Channel"]()
|
||||
content = factories["common.Content"]()
|
||||
upload = factories["music.Upload"](
|
||||
playable=True,
|
||||
library=channel.library,
|
||||
import_status="finished",
|
||||
track__set_tags=["Punk"],
|
||||
track__description=content,
|
||||
track__album__set_tags=["Rock"],
|
||||
track__artist__set_tags=["Indie"],
|
||||
)
|
||||
|
@ -1100,6 +1142,8 @@ def test_channel_upload_serializer(factories):
|
|||
"summary": "#Indie #Punk #Rock",
|
||||
"attributedTo": channel.actor.fid,
|
||||
"published": upload.creation_date.isoformat(),
|
||||
"mediaType": "text/html",
|
||||
"content": common_utils.render_html(content.text, content.content_type),
|
||||
"to": "https://www.w3.org/ns/activitystreams#Public",
|
||||
"url": [
|
||||
{
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -30,6 +30,7 @@ DATA_DIR = os.path.dirname(os.path.abspath(__file__))
|
|||
),
|
||||
("license", "Dummy license: http://creativecommons.org/licenses/by-sa/4.0/"),
|
||||
("copyright", "Someone"),
|
||||
("comment", "Hello there"),
|
||||
],
|
||||
)
|
||||
def test_can_get_metadata_from_ogg_file(field, value):
|
||||
|
@ -58,6 +59,7 @@ def test_can_get_metadata_all():
|
|||
"license": "Dummy license: http://creativecommons.org/licenses/by-sa/4.0/",
|
||||
"copyright": "Someone",
|
||||
"genre": "Classical",
|
||||
"comment": "Hello there",
|
||||
}
|
||||
assert data.all() == expected
|
||||
|
||||
|
@ -81,6 +83,7 @@ def test_can_get_metadata_all():
|
|||
),
|
||||
("license", "Dummy license: http://creativecommons.org/licenses/by-sa/4.0/"),
|
||||
("copyright", "Someone"),
|
||||
("comment", "Hello there"),
|
||||
],
|
||||
)
|
||||
def test_can_get_metadata_from_opus_file(field, value):
|
||||
|
@ -104,6 +107,7 @@ def test_can_get_metadata_from_opus_file(field, value):
|
|||
("mbid", "124d0150-8627-46bc-bc14-789a3bc960c8"),
|
||||
("musicbrainz_artistid", "c3bc80a6-1f4a-4e17-8cf0-6b1efe8302f1"),
|
||||
("musicbrainz_albumartistid", "c3bc80a6-1f4a-4e17-8cf0-6b1efe8302f1"),
|
||||
("comment", "Hello there"),
|
||||
# somehow, I cannot successfully create an ogg theora file
|
||||
# with the proper license field
|
||||
# ("license", "Dummy license: http://creativecommons.org/licenses/by-sa/4.0/"),
|
||||
|
@ -132,6 +136,7 @@ def test_can_get_metadata_from_ogg_theora_file(field, value):
|
|||
("musicbrainz_albumartistid", "9c6bddde-6228-4d9f-ad0d-03f6fcb19e13"),
|
||||
("license", "https://creativecommons.org/licenses/by-nc-nd/2.5/"),
|
||||
("copyright", "Someone"),
|
||||
("comment", "Hello there"),
|
||||
],
|
||||
)
|
||||
def test_can_get_metadata_from_id3_mp3_file(field, value):
|
||||
|
@ -181,6 +186,7 @@ def test_can_get_pictures(name):
|
|||
("musicbrainz_albumartistid", "b7ffd2af-418f-4be2-bdd1-22f8b48613da"),
|
||||
("license", "http://creativecommons.org/licenses/by-nc-sa/3.0/us/"),
|
||||
("copyright", "2008 nin"),
|
||||
("comment", "Hello there"),
|
||||
],
|
||||
)
|
||||
def test_can_get_metadata_from_flac_file(field, value):
|
||||
|
@ -210,6 +216,7 @@ def test_can_get_metadata_from_flac_file(field, value):
|
|||
("license", "Dummy license: http://creativecommons.org/licenses/by-sa/4.0/"),
|
||||
("copyright", "Someone"),
|
||||
("genre", "Dubstep"),
|
||||
("comment", "Hello there"),
|
||||
],
|
||||
)
|
||||
def test_can_get_metadata_from_m4a_file(field, value):
|
||||
|
@ -294,6 +301,7 @@ def test_metadata_fallback_ogg_theora(mocker):
|
|||
"license": "https://creativecommons.org/licenses/by-nc-nd/2.5/",
|
||||
"copyright": "Someone",
|
||||
"tags": ["Funk"],
|
||||
"description": {"text": "Hello there", "content_type": "text/plain"},
|
||||
},
|
||||
),
|
||||
(
|
||||
|
@ -327,6 +335,7 @@ def test_metadata_fallback_ogg_theora(mocker):
|
|||
"license": "Dummy license: http://creativecommons.org/licenses/by-sa/4.0/",
|
||||
"copyright": "Someone",
|
||||
"tags": ["Classical"],
|
||||
"description": {"text": "Hello there", "content_type": "text/plain"},
|
||||
},
|
||||
),
|
||||
(
|
||||
|
@ -360,6 +369,7 @@ def test_metadata_fallback_ogg_theora(mocker):
|
|||
"license": "Dummy license: http://creativecommons.org/licenses/by-sa/4.0/",
|
||||
"copyright": "Someone",
|
||||
"tags": ["Classical"],
|
||||
"description": {"text": "Hello there", "content_type": "text/plain"},
|
||||
},
|
||||
),
|
||||
(
|
||||
|
@ -391,6 +401,7 @@ def test_metadata_fallback_ogg_theora(mocker):
|
|||
# with the proper license field
|
||||
# ("license", "Dummy license: http://creativecommons.org/licenses/by-sa/4.0/"),
|
||||
"copyright": "℗ 2012 JKP GmbH & Co. KG",
|
||||
"description": {"text": "Hello there", "content_type": "text/plain"},
|
||||
},
|
||||
),
|
||||
(
|
||||
|
@ -420,6 +431,7 @@ def test_metadata_fallback_ogg_theora(mocker):
|
|||
"license": "http://creativecommons.org/licenses/by-nc-sa/3.0/us/",
|
||||
"copyright": "2008 nin",
|
||||
"tags": ["Industrial"],
|
||||
"description": {"text": "Hello there", "content_type": "text/plain"},
|
||||
},
|
||||
),
|
||||
],
|
||||
|
@ -528,10 +540,12 @@ def test_fake_metadata_with_serializer():
|
|||
"musicbrainz_albumartistid": "013c8e5b-d72a-4cd3-8dee-6c64d6125823;5b4d7d2d-36df-4b38-95e3-a964234f520f",
|
||||
"license": "Dummy license: http://creativecommons.org/licenses/by-sa/4.0/",
|
||||
"copyright": "Someone",
|
||||
"comment": "hello there",
|
||||
}
|
||||
|
||||
expected = {
|
||||
"title": "Peer Gynt Suite no. 1, op. 46: I. Morning",
|
||||
"description": {"text": "hello there", "content_type": "text/plain"},
|
||||
"artists": [
|
||||
{
|
||||
"name": "Edvard Grieg",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import datetime
|
||||
import pytest
|
||||
|
||||
from funkwhale_api.common import serializers as common_serializers
|
||||
from funkwhale_api.music import licenses
|
||||
from funkwhale_api.music import mutations
|
||||
|
||||
|
@ -195,3 +196,26 @@ def test_mutation_set_attachment_cover(factories, now, mocker):
|
|||
|
||||
assert obj.attachment_cover == new_attachment
|
||||
assert mutation.previous_state["cover"] == old_attachment.uuid
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"factory_name", ["music.Track", "music.Album", "music.Artist"],
|
||||
)
|
||||
def test_album_mutation_description(factory_name, factories, mocker):
|
||||
mocker.patch("funkwhale_api.federation.routes.outbox.dispatch")
|
||||
content = factories["common.Content"]()
|
||||
obj = factories[factory_name](description=content)
|
||||
mutation = factories["common.Mutation"](
|
||||
type="update",
|
||||
target=obj,
|
||||
payload={"description": {"content_type": "text/plain", "text": "hello there"}},
|
||||
)
|
||||
mutation.apply()
|
||||
obj.refresh_from_db()
|
||||
|
||||
assert obj.description.content_type == "text/plain"
|
||||
assert obj.description.text == "hello there"
|
||||
assert (
|
||||
mutation.previous_state["description"]
|
||||
== common_serializers.ContentSerializer(content).data
|
||||
)
|
||||
|
|
|
@ -501,3 +501,21 @@ def test_upload_with_channel_validates_import_metadata(factories, uploaded_audio
|
|||
)
|
||||
with pytest.raises(serializers.serializers.ValidationError):
|
||||
assert serializer.is_valid(raise_exception=True)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"factory_name, serializer_class",
|
||||
[
|
||||
("music.Artist", serializers.ArtistWithAlbumsSerializer),
|
||||
("music.Album", serializers.AlbumSerializer),
|
||||
("music.Track", serializers.TrackSerializer),
|
||||
],
|
||||
)
|
||||
def test_detail_serializers_with_description_description(
|
||||
factory_name, serializer_class, factories
|
||||
):
|
||||
content = factories["common.Content"]()
|
||||
obj = factories[factory_name](description=content)
|
||||
expected = common_serializers.ContentSerializer(content).data
|
||||
serializer = serializer_class(obj, context={"description": True})
|
||||
assert serializer.data["description"] == expected
|
||||
|
|
|
@ -128,6 +128,21 @@ def test_can_create_track_from_file_metadata_featuring(factories):
|
|||
assert track.artist.name == "Santana feat. Chris Cornell"
|
||||
|
||||
|
||||
def test_can_create_track_from_file_metadata_description(factories):
|
||||
metadata = {
|
||||
"title": "Whole Lotta Love",
|
||||
"position": 1,
|
||||
"disc_number": 1,
|
||||
"description": {"text": "hello there", "content_type": "text/plain"},
|
||||
"album": {"title": "Test album"},
|
||||
"artists": [{"name": "Santana"}],
|
||||
}
|
||||
track = tasks.get_track_from_import_metadata(metadata)
|
||||
|
||||
assert track.description.text == "hello there"
|
||||
assert track.description.content_type == "text/plain"
|
||||
|
||||
|
||||
def test_can_create_track_from_file_metadata_mbid(factories, mocker):
|
||||
metadata = {
|
||||
"title": "Test track",
|
||||
|
@ -607,6 +622,7 @@ def test_federation_audio_track_to_metadata(now, mocker):
|
|||
"copyright": "2018 Someone",
|
||||
"attributedTo": "http://track.attributed",
|
||||
"tag": [{"type": "Hashtag", "name": "TrackTag"}],
|
||||
"content": "hello there",
|
||||
"album": {
|
||||
"published": published.isoformat(),
|
||||
"type": "Album",
|
||||
|
@ -616,12 +632,16 @@ def test_federation_audio_track_to_metadata(now, mocker):
|
|||
"released": released.isoformat(),
|
||||
"tag": [{"type": "Hashtag", "name": "AlbumTag"}],
|
||||
"attributedTo": "http://album.attributed",
|
||||
"content": "album desc",
|
||||
"mediaType": "text/plain",
|
||||
"artists": [
|
||||
{
|
||||
"type": "Artist",
|
||||
"published": published.isoformat(),
|
||||
"id": "http://hello.artist",
|
||||
"name": "John Smith",
|
||||
"content": "album artist desc",
|
||||
"mediaType": "text/markdown",
|
||||
"musicbrainzId": str(uuid.uuid4()),
|
||||
"attributedTo": "http://album-artist.attributed",
|
||||
"tag": [{"type": "Hashtag", "name": "AlbumArtistTag"}],
|
||||
|
@ -639,6 +659,8 @@ def test_federation_audio_track_to_metadata(now, mocker):
|
|||
"type": "Artist",
|
||||
"id": "http://hello.trackartist",
|
||||
"name": "Bob Smith",
|
||||
"content": "artist desc",
|
||||
"mediaType": "text/html",
|
||||
"musicbrainzId": str(uuid.uuid4()),
|
||||
"attributedTo": "http://artist.attributed",
|
||||
"tag": [{"type": "Hashtag", "name": "ArtistTag"}],
|
||||
|
@ -658,6 +680,7 @@ def test_federation_audio_track_to_metadata(now, mocker):
|
|||
"fid": payload["id"],
|
||||
"attributed_to": references["http://track.attributed"],
|
||||
"tags": ["TrackTag"],
|
||||
"description": {"content_type": "text/html", "text": "hello there"},
|
||||
"album": {
|
||||
"title": payload["album"]["name"],
|
||||
"attributed_to": references["http://album.attributed"],
|
||||
|
@ -666,6 +689,7 @@ def test_federation_audio_track_to_metadata(now, mocker):
|
|||
"fid": payload["album"]["id"],
|
||||
"fdate": serializer.validated_data["album"]["published"],
|
||||
"tags": ["AlbumTag"],
|
||||
"description": {"content_type": "text/plain", "text": "album desc"},
|
||||
"artists": [
|
||||
{
|
||||
"name": a["name"],
|
||||
|
@ -675,6 +699,10 @@ def test_federation_audio_track_to_metadata(now, mocker):
|
|||
"fdate": serializer.validated_data["album"]["artists"][i][
|
||||
"published"
|
||||
],
|
||||
"description": {
|
||||
"content_type": "text/markdown",
|
||||
"text": "album artist desc",
|
||||
},
|
||||
"tags": ["AlbumArtistTag"],
|
||||
}
|
||||
for i, a in enumerate(payload["album"]["artists"])
|
||||
|
@ -690,6 +718,7 @@ def test_federation_audio_track_to_metadata(now, mocker):
|
|||
"fdate": serializer.validated_data["artists"][i]["published"],
|
||||
"attributed_to": references["http://artist.attributed"],
|
||||
"tags": ["ArtistTag"],
|
||||
"description": {"content_type": "text/html", "text": "artist desc"},
|
||||
}
|
||||
for i, a in enumerate(payload["artists"])
|
||||
],
|
||||
|
|
Binary file not shown.
|
@ -1256,3 +1256,22 @@ def test_search_get_fts_stop_words(settings, logged_in_api_client, factories):
|
|||
|
||||
assert response.status_code == 200
|
||||
assert response.data == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"route, factory_name",
|
||||
[
|
||||
("api:v1:artists-detail", "music.Artist"),
|
||||
("api:v1:albums-detail", "music.Album"),
|
||||
("api:v1:tracks-detail", "music.Track"),
|
||||
],
|
||||
)
|
||||
def test_detail_includes_description_key(
|
||||
route, factory_name, logged_in_api_client, factories
|
||||
):
|
||||
obj = factories[factory_name]()
|
||||
url = reverse(route, kwargs={"pk": obj.pk})
|
||||
|
||||
response = logged_in_api_client.get(url)
|
||||
|
||||
assert response.data["description"] is None
|
||||
|
|
|
@ -77,6 +77,10 @@
|
|||
</button>
|
||||
|
||||
</template>
|
||||
<template v-else-if="fieldConfig.type === 'content'">
|
||||
<label :for="fieldConfig.id">{{ fieldConfig.label }}</label>
|
||||
<textarea v-model="values[fieldConfig.id].text" :name="fieldConfig.id" :id="fieldConfig.id" rows="3"></textarea>
|
||||
</template>
|
||||
<template v-else-if="fieldConfig.type === 'attachment'">
|
||||
<label :for="fieldConfig.id">{{ fieldConfig.label }}</label>
|
||||
<attachment-input
|
||||
|
@ -100,8 +104,8 @@
|
|||
<translate translate-context="Content/Library/Button.Label">Clear</translate>
|
||||
</button>
|
||||
</template>
|
||||
<div v-if="values[fieldConfig.id] != initialValues[fieldConfig.id]">
|
||||
<button class="ui tiny basic right floated reset button" form="noop" @click.prevent="values[fieldConfig.id] = initialValues[fieldConfig.id]">
|
||||
<div v-if="!lodash.isEqual(values[fieldConfig.id], initialValues[fieldConfig.id])">
|
||||
<button class="ui tiny basic right floated reset button" form="noop" @click.prevent="values[fieldConfig.id] = lodash.clone(initialValues[fieldConfig.id])">
|
||||
<i class="undo icon"></i>
|
||||
<translate translate-context="Content/Library/Button.Label">Reset to initial value</translate>
|
||||
</button>
|
||||
|
@ -156,6 +160,7 @@ export default {
|
|||
summary: '',
|
||||
submittedMutation: null,
|
||||
showPendingReview: true,
|
||||
lodash,
|
||||
}
|
||||
},
|
||||
created () {
|
||||
|
@ -216,8 +221,8 @@ export default {
|
|||
setValues () {
|
||||
let self = this
|
||||
this.config.fields.forEach(f => {
|
||||
self.$set(self.values, f.id, f.getValue(self.object))
|
||||
self.$set(self.initialValues, f.id, self.values[f.id])
|
||||
self.$set(self.values, f.id, lodash.clone(f.getValue(self.object)))
|
||||
self.$set(self.initialValues, f.id, lodash.clone(self.values[f.id]))
|
||||
})
|
||||
},
|
||||
submit() {
|
||||
|
|
|
@ -5,8 +5,20 @@ function getTagsValueRepr (val) {
|
|||
return val.slice().sort().join('\n')
|
||||
}
|
||||
|
||||
function getContentValueRepr (val) {
|
||||
return val.text
|
||||
}
|
||||
|
||||
export default {
|
||||
getConfigs () {
|
||||
const description = {
|
||||
id: 'description',
|
||||
type: 'content',
|
||||
required: true,
|
||||
label: this.$pgettext('*/*/*/Noun', 'Description'),
|
||||
getValue: (obj) => { return obj.description || {text: null, content_type: 'text/markdown'}},
|
||||
getValueRepr: getContentValueRepr
|
||||
}
|
||||
return {
|
||||
artist: {
|
||||
fields: [
|
||||
|
@ -17,6 +29,7 @@ export default {
|
|||
label: this.$pgettext('*/*/*/Noun', 'Name'),
|
||||
getValue: (obj) => { return obj.name }
|
||||
},
|
||||
description,
|
||||
{
|
||||
id: 'tags',
|
||||
type: 'tags',
|
||||
|
@ -24,7 +37,7 @@ export default {
|
|||
label: this.$pgettext('*/*/*/Noun', 'Tags'),
|
||||
getValue: (obj) => { return obj.tags },
|
||||
getValueRepr: getTagsValueRepr
|
||||
}
|
||||
},
|
||||
]
|
||||
},
|
||||
album: {
|
||||
|
@ -36,6 +49,7 @@ export default {
|
|||
label: this.$pgettext('*/*/*/Noun', 'Title'),
|
||||
getValue: (obj) => { return obj.title }
|
||||
},
|
||||
description,
|
||||
{
|
||||
id: 'release_date',
|
||||
type: 'text',
|
||||
|
@ -75,6 +89,7 @@ export default {
|
|||
label: this.$pgettext('*/*/*/Noun', 'Title'),
|
||||
getValue: (obj) => { return obj.title }
|
||||
},
|
||||
description,
|
||||
{
|
||||
id: 'position',
|
||||
type: 'text',
|
||||
|
|
|
@ -129,6 +129,12 @@
|
|||
{{ object.domain }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="object.description">
|
||||
<td>
|
||||
<translate translate-context="'*/*/*/Noun">Description</translate>
|
||||
</td>
|
||||
<td v-html="object.description.html"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
|
|
@ -117,6 +117,12 @@
|
|||
{{ object.domain }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="object.description">
|
||||
<td>
|
||||
<translate translate-context="'*/*/*/Noun">Description</translate>
|
||||
</td>
|
||||
<td v-html="object.description.html"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
|
|
@ -181,6 +181,12 @@
|
|||
{{ object.domain }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="object.description">
|
||||
<td>
|
||||
<translate translate-context="'*/*/*/Noun">Description</translate>
|
||||
</td>
|
||||
<td v-html="object.description.html"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
|
Loading…
Reference in New Issue