See #170: add a description field on tracks, albums, tracks

This commit is contained in:
Eliot Berriot 2020-01-14 14:00:08 +01:00
parent 424b9f133a
commit 2bc71eecfd
38 changed files with 653 additions and 59 deletions

View file

@ -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()

View file

@ -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

View file

@ -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![img](src)", "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

View file

@ -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.

View file

@ -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",

View file

@ -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
)

View file

@ -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

View file

@ -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.

View file

@ -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