Fix #747: Support embedding full artist discographies
This commit is contained in:
parent
1d787904f4
commit
8ae00b0698
|
@ -470,6 +470,36 @@ class OembedSerializer(serializers.Serializer):
|
||||||
"library_artist", kwargs={"pk": album.artist.pk}
|
"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:
|
else:
|
||||||
raise serializers.ValidationError(
|
raise serializers.ValidationError(
|
||||||
"Unsupported url: {}".format(validated_data["url"])
|
"Unsupported url: {}".format(validated_data["url"])
|
||||||
|
|
|
@ -2,6 +2,7 @@ import urllib.parse
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from django.db.models import Q
|
||||||
|
|
||||||
from funkwhale_api.common import utils
|
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
|
return metas
|
||||||
|
|
|
@ -184,6 +184,8 @@ class TrackViewSet(
|
||||||
"title",
|
"title",
|
||||||
"album__release_date",
|
"album__release_date",
|
||||||
"size",
|
"size",
|
||||||
|
"position",
|
||||||
|
"disc_number",
|
||||||
"artist__name",
|
"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):
|
def test_library_artist(spa_html, no_api_auth, client, factories, settings):
|
||||||
album = factories["music.Album"]()
|
album = factories["music.Album"]()
|
||||||
|
factories["music.Upload"](playable=True, track__album=album)
|
||||||
artist = album.artist
|
artist = album.artist
|
||||||
url = "/library/artists/{}".format(artist.pk)
|
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
|
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())
|
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"})
|
response = api_client.get(url, {"url": album_url, "format": "json"})
|
||||||
|
|
||||||
assert response.data == expected
|
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
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Support embedding full artist discographies (#747)
|
|
@ -139,7 +139,7 @@ export default {
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
time,
|
time,
|
||||||
supportedTypes: ['track', 'album'],
|
supportedTypes: ['track', 'album', 'artist'],
|
||||||
baseUrl: '',
|
baseUrl: '',
|
||||||
error: null,
|
error: null,
|
||||||
type: null,
|
type: null,
|
||||||
|
@ -158,6 +158,7 @@ export default {
|
||||||
},
|
},
|
||||||
created () {
|
created () {
|
||||||
let params = getURLParams()
|
let params = getURLParams()
|
||||||
|
this.baseUrl = params.b || ''
|
||||||
this.type = params.type
|
this.type = params.type
|
||||||
if (this.supportedTypes.indexOf(this.type) === -1) {
|
if (this.supportedTypes.indexOf(this.type) === -1) {
|
||||||
this.error = 'invalid_type'
|
this.error = 'invalid_type'
|
||||||
|
@ -229,7 +230,10 @@ export default {
|
||||||
this.fetchTrack(id)
|
this.fetchTrack(id)
|
||||||
}
|
}
|
||||||
if (type === 'album') {
|
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) {
|
play (index) {
|
||||||
|
|
|
@ -29,7 +29,11 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="preview">
|
<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>
|
<iframe :width="frameWidth" :height="height" scrolling="no" frameborder="no" :src="iframeSrc"></iframe>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -35,6 +35,30 @@
|
||||||
<i class="external icon"></i>
|
<i class="external icon"></i>
|
||||||
<translate :translate-context="'Content/*/Button.Label/Verb'">View on MusicBrainz</translate>
|
<translate :translate-context="'Content/*/Button.Label/Verb'">View on MusicBrainz</translate>
|
||||||
</a>
|
</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>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<div class="ui small text container" v-if="contentFilter">
|
<div class="ui small text container" v-if="contentFilter">
|
||||||
|
@ -72,7 +96,7 @@
|
||||||
<h2>
|
<h2>
|
||||||
<translate :translate-context="'Content/Artist/Title'">User libraries</translate>
|
<translate :translate-context="'Content/Artist/Title'">User libraries</translate>
|
||||||
</h2>
|
</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>
|
<translate :translate-context="'Content/Artist/Paragraph'" slot="subtitle">This artist is present in the following libraries:</translate>
|
||||||
</library-widget>
|
</library-widget>
|
||||||
</section>
|
</section>
|
||||||
|
@ -90,6 +114,8 @@ import RadioButton from "@/components/radios/Button"
|
||||||
import PlayButton from "@/components/audio/PlayButton"
|
import PlayButton from "@/components/audio/PlayButton"
|
||||||
import TrackTable from "@/components/audio/track/Table"
|
import TrackTable from "@/components/audio/track/Table"
|
||||||
import LibraryWidget from "@/components/federation/LibraryWidget"
|
import LibraryWidget from "@/components/federation/LibraryWidget"
|
||||||
|
import EmbedWizard from "@/components/audio/EmbedWizard"
|
||||||
|
import Modal from '@/components/semantic/Modal'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ["id"],
|
props: ["id"],
|
||||||
|
@ -98,7 +124,9 @@ export default {
|
||||||
RadioButton,
|
RadioButton,
|
||||||
PlayButton,
|
PlayButton,
|
||||||
TrackTable,
|
TrackTable,
|
||||||
LibraryWidget
|
LibraryWidget,
|
||||||
|
EmbedWizard,
|
||||||
|
Modal
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -108,7 +136,9 @@ export default {
|
||||||
albums: null,
|
albums: null,
|
||||||
totalTracks: 0,
|
totalTracks: 0,
|
||||||
totalAlbums: 0,
|
totalAlbums: 0,
|
||||||
tracks: []
|
tracks: [],
|
||||||
|
libraries: [],
|
||||||
|
showEmbedModal: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
|
@ -185,6 +215,12 @@ export default {
|
||||||
return album.cover
|
return album.cover
|
||||||
})[0]
|
})[0]
|
||||||
},
|
},
|
||||||
|
|
||||||
|
publicLibraries () {
|
||||||
|
return this.libraries.filter(l => {
|
||||||
|
return l.privacy_level === 'everyone'
|
||||||
|
})
|
||||||
|
},
|
||||||
headerStyle() {
|
headerStyle() {
|
||||||
if (!this.cover || !this.cover.original) {
|
if (!this.cover || !this.cover.original) {
|
||||||
return ""
|
return ""
|
||||||
|
|
Loading…
Reference in New Issue