diff --git a/api/funkwhale_api/common/management/commands/load_test_data.py b/api/funkwhale_api/common/management/commands/load_test_data.py index 26f787f48..9eab5ff08 100644 --- a/api/funkwhale_api/common/management/commands/load_test_data.py +++ b/api/funkwhale_api/common/management/commands/load_test_data.py @@ -46,16 +46,28 @@ def create_local_accounts(factories, count, dependencies): return actors -def create_tagged_tracks(factories, count, dependencies): +def create_taggable_items(dependency): + def inner(factories, count, dependencies): - objs = [] - for track in dependencies["tracks"]: - tag = random.choice(dependencies["tags"]) - objs.append(factories["tags.TaggedItem"].build(content_object=track, tag=tag)) + objs = [] + tagged_objects = dependencies.get( + dependency, list(CONFIG_BY_ID[dependency]["model"].objects.all().only("pk")) + ) + tags = dependencies.get("tags", list(tags_models.Tag.objects.all().only("pk"))) + for i in range(count): + tag = random.choice(tags) + tagged_object = random.choice(tagged_objects) + objs.append( + factories["tags.TaggedItem"].build( + content_object=tagged_object, tag=tag + ) + ) - return tags_models.TaggedItem.objects.bulk_create( - objs, batch_size=BATCH_SIZE, ignore_conflicts=True - ) + return tags_models.TaggedItem.objects.bulk_create( + objs, batch_size=BATCH_SIZE, ignore_conflicts=True + ) + + return inner CONFIG = [ @@ -110,7 +122,10 @@ CONFIG = [ { "id": "track_tags", "model": tags_models.TaggedItem, - "handler": create_tagged_tracks, + "queryset": tags_models.TaggedItem.objects.filter( + content_type__app_label="music", content_type__model="track" + ), + "handler": create_taggable_items("tracks"), "depends_on": [ { "field": "tag", @@ -127,6 +142,52 @@ CONFIG = [ }, ], }, + { + "id": "album_tags", + "model": tags_models.TaggedItem, + "queryset": tags_models.TaggedItem.objects.filter( + content_type__app_label="music", content_type__model="album" + ), + "handler": create_taggable_items("albums"), + "depends_on": [ + { + "field": "tag", + "id": "tags", + "default_factor": 0.1, + "queryset": tags_models.Tag.objects.all(), + "set": False, + }, + { + "field": "content_object", + "id": "albums", + "default_factor": 1, + "set": False, + }, + ], + }, + { + "id": "artist_tags", + "model": tags_models.TaggedItem, + "queryset": tags_models.TaggedItem.objects.filter( + content_type__app_label="music", content_type__model="artist" + ), + "handler": create_taggable_items("artists"), + "depends_on": [ + { + "field": "tag", + "id": "tags", + "default_factor": 0.1, + "queryset": tags_models.Tag.objects.all(), + "set": False, + }, + { + "field": "content_object", + "id": "artists", + "default_factor": 1, + "set": False, + }, + ], + }, ] CONFIG_BY_ID = {c["id"]: c for c in CONFIG} @@ -194,8 +255,8 @@ class Command(BaseCommand): self.stdout.write("\nFinal state of database:\n\n") for row in CONFIG: - model = row["model"] - total = model.objects.all().count() + qs = row.get("queryset", row["model"].objects.all()) + total = qs.count() self.stdout.write("- {} {} objects".format(total, row["id"])) self.stdout.write("") diff --git a/api/funkwhale_api/tags/filters.py b/api/funkwhale_api/tags/filters.py index 4be4afeef..e0ac9675a 100644 --- a/api/funkwhale_api/tags/filters.py +++ b/api/funkwhale_api/tags/filters.py @@ -18,4 +18,4 @@ class TagFilter(filters.FilterSet): class Meta: model = models.Tag - fields = ["q"] + fields = {"q": ["exact"], "name": ["exact", "startswith"]} diff --git a/api/funkwhale_api/tags/views.py b/api/funkwhale_api/tags/views.py index d7b1d8aa5..1d052ca7b 100644 --- a/api/funkwhale_api/tags/views.py +++ b/api/funkwhale_api/tags/views.py @@ -1,6 +1,8 @@ from django.db.models import functions from rest_framework import viewsets +import django_filters.rest_framework + from funkwhale_api.users.oauth import permissions as oauth_permissions from . import filters @@ -20,3 +22,4 @@ class TagViewSet(viewsets.ReadOnlyModelViewSet): required_scope = "libraries" anonymous_policy = "setting" filterset_class = filters.TagFilter + filter_backends = [django_filters.rest_framework.DjangoFilterBackend] diff --git a/api/tests/common/test_commands.py b/api/tests/common/test_commands.py index e2755256e..a6fabfc06 100644 --- a/api/tests/common/test_commands.py +++ b/api/tests/common/test_commands.py @@ -81,11 +81,7 @@ def test_load_test_data_dry_run(factories, mocker): ), ( {"create_dependencies": True, "track_tags": 3}, - [ - (tags_models.Tag.objects.all(), 1), - (tags_models.TaggedItem.objects.all(), 3), - (music_models.Track.objects.all(), 3), - ], + [(tags_models.Tag.objects.all(), 1), (music_models.Track.objects.all(), 3)], ), ], ) diff --git a/api/tests/tags/test_views.py b/api/tests/tags/test_views.py index fd3246adb..b42e9ab37 100644 --- a/api/tests/tags/test_views.py +++ b/api/tests/tags/test_views.py @@ -23,18 +23,21 @@ def test_tags_list_ordering_length(factories, logged_in_api_client): url = reverse("api:v1:tags-list") tags = [ factories["tags.Tag"](name="iamareallylongtag"), - factories["tags.Tag"](name="reallylongtag"), factories["tags.Tag"](name="short"), + factories["tags.Tag"](name="reallylongtag"), factories["tags.Tag"](name="bar"), ] expected = { "count": 4, "next": None, "previous": None, - "results": [serializers.TagSerializer(tag).data for tag in tags], + "results": [ + serializers.TagSerializer(tag).data + for tag in [tags[3], tags[1], tags[2], tags[0]] + ], } - response = logged_in_api_client.get(url, {"ordering": "-length"}) + response = logged_in_api_client.get(url, {"ordering": "length"}) assert response.data == expected diff --git a/dev.yml b/dev.yml index b0ca7fc72..eaa7ca8d3 100644 --- a/dev.yml +++ b/dev.yml @@ -57,6 +57,7 @@ services: - "${MUSIC_DIRECTORY_SERVE_PATH-./data/music}:/music:ro" - "./data/plugins:/srv/funkwhale/plugins" - "./data/staticfiles:/staticfiles" + - "./data/media:/data/media" environment: - "FUNKWHALE_HOSTNAME=${FUNKWHALE_HOSTNAME-localhost}" - "FUNKWHALE_HOSTNAME_SUFFIX=funkwhale.test" @@ -65,6 +66,7 @@ services: - "DATABASE_URL=postgresql://postgres@postgres/postgres" - "CACHE_URL=redis://redis:6379/0" - "STATIC_ROOT=/staticfiles" + - "MEDIA_ROOT=/data/media" depends_on: - postgres @@ -92,10 +94,12 @@ services: - "FUNKWHALE_PROTOCOL=${FUNKWHALE_PROTOCOL-http}" - "DATABASE_URL=postgresql://postgres@postgres/postgres" - "CACHE_URL=redis://redis:6379/0" + - "MEDIA_ROOT=/data/media" volumes: - ./api:/app - "${MUSIC_DIRECTORY_SERVE_PATH-./data/music}:/music:ro" - "./data/plugins:/srv/funkwhale/plugins" + - "./data/media:/data/media" networks: - internal nginx: @@ -122,9 +126,9 @@ services: - ./docker/nginx/entrypoint.sh:/entrypoint.sh:ro - "${MUSIC_DIRECTORY_SERVE_PATH-./data/music}:/music:ro" - ./deploy/funkwhale_proxy.conf:/etc/nginx/funkwhale_proxy.conf:ro - - "${MEDIA_ROOT-./api/funkwhale_api/media}:/protected/media:ro" - "./front:/frontend:ro" - "./data/staticfiles:/staticfiles:ro" + - "./data/media:/protected/media:ro" networks: - federation - internal diff --git a/front/package.json b/front/package.json index 54cf3c8e1..2c8f041f0 100644 --- a/front/package.json +++ b/front/package.json @@ -22,6 +22,7 @@ "masonry-layout": "^4.2.2", "moment": "^2.22.2", "fomantic-ui-css": "^2.7", + "qs": "^6.7.0", "showdown": "^1.8.6", "vue": "^2.5.17", "vue-gettext": "^2.1.0", diff --git a/front/src/components/audio/album/Widget.vue b/front/src/components/audio/album/Widget.vue index ecc0a280e..609ef6ebc 100644 --- a/front/src/components/audio/album/Widget.vue +++ b/front/src/components/audio/album/Widget.vue @@ -2,10 +2,11 @@

+ {{ count }}

- - - + + +
@@ -33,6 +34,7 @@
+
No results matching your query.
@@ -43,7 +45,9 @@ import PlayButton from '@/components/audio/PlayButton' export default { props: { - filters: {type: Object, required: true} + filters: {type: Object, required: true}, + controls: {type: Boolean, default: true}, + showCount: {type: Boolean, default: false}, }, components: { PlayButton @@ -52,6 +56,7 @@ export default { return { albums: [], limit: 12, + count: 0, isLoading: false, errors: null, previousPage: null, @@ -76,6 +81,7 @@ export default { self.nextPage = response.data.next self.isLoading = false self.albums = response.data.results + self.count = response.data.count }, error => { self.isLoading = false self.errors = error.backendErrors diff --git a/front/src/components/audio/artist/Widget.vue b/front/src/components/audio/artist/Widget.vue new file mode 100644 index 000000000..1b88feae7 --- /dev/null +++ b/front/src/components/audio/artist/Widget.vue @@ -0,0 +1,172 @@ + + + + + diff --git a/front/src/components/audio/track/Widget.vue b/front/src/components/audio/track/Widget.vue index b8ad3c639..ecb967ab8 100644 --- a/front/src/components/audio/track/Widget.vue +++ b/front/src/components/audio/track/Widget.vue @@ -2,12 +2,13 @@

+ {{ count }}

-
+
@@ -28,7 +29,9 @@
-
+ + +
@{{ object.user.username }}
@@ -50,19 +53,25 @@ import _ from '@/lodash' import axios from 'axios' import PlayButton from '@/components/audio/PlayButton' +import TagsList from "@/components/tags/List" export default { props: { filters: {type: Object, required: true}, - url: {type: String, required: true} + url: {type: String, required: true}, + isActivity: {type: Boolean, default: true}, + showCount: {type: Boolean, default: false}, + limit: {type: Number, default: 5}, + itemClasses: {type: String, default: ''}, }, components: { - PlayButton + PlayButton, + TagsList }, data () { return { objects: [], - limit: 5, + count: 0, isLoading: false, errors: null, previousPage: null, @@ -86,7 +95,15 @@ export default { self.previousPage = response.data.previous self.nextPage = response.data.next self.isLoading = false - self.objects = response.data.results + self.count = response.data.count + if (self.isActivity) { + // we have listening/favorites objects, not directly tracks + self.objects = response.data.results + } else { + self.objects = response.data.results.map((r) => { + return {track: r} + }) + } }, error => { self.isLoading = false self.errors = error.backendErrors @@ -129,4 +146,18 @@ export default { .ui.divided.items > .item:last-child { padding-bottom: 1em !important; } + +@include media(">tablet") { + .divided.items > .track-item.inline { + width: 25em; + float: left; + border-top: none; + &, + &:first-child { + margin-top: 0.5em !important; + margin-right: 0.5em !important; + padding: 1em 0 !important; + } + } +} diff --git a/front/src/components/library/AlbumBase.vue b/front/src/components/library/AlbumBase.vue index 016be2c37..083e5547b 100644 --- a/front/src/components/library/AlbumBase.vue +++ b/front/src/components/library/AlbumBase.vue @@ -13,6 +13,7 @@
+
@@ -103,6 +104,7 @@ import backend from "@/audio/backend" import PlayButton from "@/components/audio/PlayButton" import EmbedWizard from "@/components/audio/EmbedWizard" import Modal from '@/components/semantic/Modal' +import TagsList from "@/components/tags/List" const FETCH_URL = "albums/" @@ -123,7 +125,8 @@ export default { components: { PlayButton, EmbedWizard, - Modal + Modal, + TagsList, }, data() { return { diff --git a/front/src/components/library/Albums.vue b/front/src/components/library/Albums.vue index 9817af830..ed97f7a4e 100644 --- a/front/src/components/library/Albums.vue +++ b/front/src/components/library/Albums.vue @@ -20,6 +20,10 @@
+
+ + +