Commit 95c2e018 authored by Eliot Berriot's avatar Eliot Berriot 💬

Merge branch '747-full-artist-embed' into 'develop'

Fix #747: Support embedding full artist discographies

Closes #747

See merge request funkwhale/funkwhale!653
parents 1d787904 8ae00b06
......@@ -470,6 +470,36 @@ class OembedSerializer(serializers.Serializer):
"library_artist", kwargs={"pk": album.artist.pk}
)
)
elif match.url_name == "library_artist":
qs = models.Artist.objects.filter(pk=int(match.kwargs["pk"]))
try:
artist = qs.get()
except models.Artist.DoesNotExist:
raise serializers.ValidationError(
"No artist matching id {}".format(match.kwargs["pk"])
)
embed_type = "artist"
embed_id = artist.pk
album = (
artist.albums.filter(cover__isnull=False)
.exclude(cover="")
.order_by("-id")
.first()
)
if album and album.cover:
data["thumbnail_url"] = federation_utils.full_url(
album.cover.crop["400x400"].url
)
data["thumbnail_width"] = 400
data["thumbnail_height"] = 400
data["title"] = artist.name
data["description"] = artist.name
data["author_name"] = artist.name
data["height"] = 400
data["author_url"] = federation_utils.full_url(
common_utils.spa_reverse("library_artist", kwargs={"pk": artist.pk})
)
else:
raise serializers.ValidationError(
"Unsupported url: {}".format(validated_data["url"])
......
......@@ -2,6 +2,7 @@ import urllib.parse
from django.conf import settings
from django.urls import reverse
from django.db.models import Q
from funkwhale_api.common import utils
......@@ -183,4 +184,22 @@ def library_artist(request, pk):
}
)
if (
models.Upload.objects.filter(Q(track__artist=obj) | Q(track__album__artist=obj))
.playable_by(None)
.exists()
):
metas.append(
{
"tag": "link",
"rel": "alternate",
"type": "application/json+oembed",
"href": (
utils.join_url(settings.FUNKWHALE_URL, reverse("api:v1:oembed"))
+ "?format=json&url={}".format(urllib.parse.quote_plus(artist_url))
),
}
)
# twitter player is also supported in various software
metas += get_twitter_card_metas(type="artist", id=obj.pk)
return metas
......@@ -184,6 +184,8 @@ class TrackViewSet(
"title",
"album__release_date",
"size",
"position",
"disc_number",
"artist__name",
)
......
......@@ -149,6 +149,7 @@ def test_library_album(spa_html, no_api_auth, client, factories, settings):
def test_library_artist(spa_html, no_api_auth, client, factories, settings):
album = factories["music.Album"]()
factories["music.Upload"](playable=True, track__album=album)
artist = album.artist
url = "/library/artists/{}".format(artist.pk)
......@@ -169,6 +170,25 @@ def test_library_artist(spa_html, no_api_auth, client, factories, settings):
settings.FUNKWHALE_URL, album.cover.crop["400x400"].url
),
},
{
"tag": "link",
"rel": "alternate",
"type": "application/json+oembed",
"href": (
utils.join_url(settings.FUNKWHALE_URL, reverse("api:v1:oembed"))
+ "?format=json&url={}".format(
urllib.parse.quote_plus(utils.join_url(settings.FUNKWHALE_URL, url))
)
),
},
{"tag": "meta", "property": "twitter:card", "content": "player"},
{
"tag": "meta",
"property": "twitter:player",
"content": serializers.get_embed_url("artist", id=artist.id),
},
{"tag": "meta", "property": "twitter:player:width", "content": "600"},
{"tag": "meta", "property": "twitter:player:height", "content": "400"},
]
metas = utils.parse_meta(response.content.decode())
......
......@@ -701,3 +701,38 @@ def test_oembed_album(factories, no_api_auth, api_client, settings):
response = api_client.get(url, {"url": album_url, "format": "json"})
assert response.data == expected
def test_oembed_artist(factories, no_api_auth, api_client, settings):
settings.FUNKWHALE_URL = "http://test"
settings.FUNKWHALE_EMBED_URL = "http://embed"
track = factories["music.Track"]()
album = track.album
artist = track.artist
url = reverse("api:v1:oembed")
artist_url = "https://test.com/library/artists/{}".format(artist.pk)
iframe_src = "http://embed?type=artist&id={}".format(artist.pk)
expected = {
"version": "1.0",
"type": "rich",
"provider_name": settings.APP_NAME,
"provider_url": settings.FUNKWHALE_URL,
"height": 400,
"width": 600,
"title": artist.name,
"description": artist.name,
"thumbnail_url": federation_utils.full_url(album.cover.crop["400x400"].url),
"thumbnail_height": 400,
"thumbnail_width": 400,
"html": '<iframe width="600" height="400" scrolling="no" frameborder="no" src="{}"></iframe>'.format(
iframe_src
),
"author_name": artist.name,
"author_url": federation_utils.full_url(
utils.spa_reverse("library_artist", kwargs={"pk": artist.pk})
),
}
response = api_client.get(url, {"url": artist_url, "format": "json"})
assert response.data == expected
Support embedding full artist discographies (#747)
......@@ -139,7 +139,7 @@ export default {
data () {
return {
time,
supportedTypes: ['track', 'album'],
supportedTypes: ['track', 'album', 'artist'],
baseUrl: '',
error: null,
type: null,
......@@ -158,6 +158,7 @@ export default {
},
created () {
let params = getURLParams()
this.baseUrl = params.b || ''
this.type = params.type
if (this.supportedTypes.indexOf(this.type) === -1) {
this.error = 'invalid_type'
......@@ -229,7 +230,10 @@ export default {
this.fetchTrack(id)
}
if (type === 'album') {
this.fetchTracks({album: id, playable: true})
this.fetchTracks({album: id, playable: true, ordering: ",disc_number,position"})
}
if (type === 'artist') {
this.fetchTracks({artist: id, playable: true, ordering: "-release_date,disc_number,position"})
}
},
play (index) {
......
......@@ -29,7 +29,11 @@
</div>
</div>
<div class="preview">
<h3><translate :translate-context="'Popup/Embed/Title/Noun'">Preview</translate></h3>
<h3>
<a :href="iframeSrc" target="_blank">
<translate :translate-context="'Popup/Embed/Title/Noun'">Preview</translate>
</a>
</h3>
<iframe :width="frameWidth" :height="height" scrolling="no" frameborder="no" :src="iframeSrc"></iframe>
</div>
</div>
......
......@@ -35,6 +35,30 @@
<i class="external icon"></i>
<translate :translate-context="'Content/*/Button.Label/Verb'">View on MusicBrainz</translate>
</a>
<template v-if="publicLibraries.length > 0">
<button
@click="showEmbedModal = !showEmbedModal"
class="ui button icon labeled">
<i class="code icon"></i>
<translate :translate-context="'Content/*/Button.Label/Verb'">Embed</translate>
</button>
<modal :show.sync="showEmbedModal">
<div class="header">
<translate :translate-context="'Popup/Artist/Title/Verb'">Embed this artist work on your website</translate>
</div>
<div class="content">
<div class="description">
<embed-wizard type="artist" :id="artist.id" />
</div>
</div>
<div class="actions">
<div class="ui deny button">
<translate :translate-context="'Popup/*/Button.Label/Verb'">Cancel</translate>
</div>
</div>
</modal>
</template>
</div>
</section>
<div class="ui small text container" v-if="contentFilter">
......@@ -72,7 +96,7 @@
<h2>
<translate :translate-context="'Content/Artist/Title'">User libraries</translate>
</h2>
<library-widget :url="'artists/' + id + '/libraries/'">
<library-widget @loaded="libraries = $event" :url="'artists/' + id + '/libraries/'">
<translate :translate-context="'Content/Artist/Paragraph'" slot="subtitle">This artist is present in the following libraries:</translate>
</library-widget>
</section>
......@@ -90,6 +114,8 @@ import RadioButton from "@/components/radios/Button"
import PlayButton from "@/components/audio/PlayButton"
import TrackTable from "@/components/audio/track/Table"
import LibraryWidget from "@/components/federation/LibraryWidget"
import EmbedWizard from "@/components/audio/EmbedWizard"
import Modal from '@/components/semantic/Modal'
export default {
props: ["id"],
......@@ -98,7 +124,9 @@ export default {
RadioButton,
PlayButton,
TrackTable,
LibraryWidget
LibraryWidget,
EmbedWizard,
Modal
},
data() {
return {
......@@ -108,7 +136,9 @@ export default {
albums: null,
totalTracks: 0,
totalAlbums: 0,
tracks: []
tracks: [],
libraries: [],
showEmbedModal: false
}
},
created() {
......@@ -185,6 +215,12 @@ export default {
return album.cover
})[0]
},
publicLibraries () {
return this.libraries.filter(l => {
return l.privacy_level === 'everyone'
})
},
headerStyle() {
if (!this.cover || !this.cover.original) {
return ""
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment