diff --git a/api/funkwhale_api/audio/models.py b/api/funkwhale_api/audio/models.py index f97442d74..37800962c 100644 --- a/api/funkwhale_api/audio/models.py +++ b/api/funkwhale_api/audio/models.py @@ -69,6 +69,15 @@ class Channel(models.Model): objects = ChannelQuerySet.as_manager() + @property + def fid(self): + if not self.is_external_rss: + return self.actor.fid + + @property + def is_external_rss(self): + return self.actor.preferred_username.startswith("rssfeed-") + def get_absolute_url(self): suffix = self.uuid if self.actor.is_local: @@ -78,9 +87,7 @@ class Channel(models.Model): return federation_utils.full_url("/channels/{}".format(suffix)) def get_rss_url(self): - if not self.artist.is_local or self.actor.preferred_username.startswith( - "rssfeed-" - ): + if not self.artist.is_local or self.is_external_rss: return self.rss_url return federation_utils.full_url( @@ -90,10 +97,6 @@ class Channel(models.Model): ) ) - @property - def fid(self): - return self.actor.fid - def generate_actor(username, **kwargs): actor_data = user_models.get_actor_data(username, **kwargs) diff --git a/api/funkwhale_api/federation/models.py b/api/funkwhale_api/federation/models.py index 50e8eef1a..7f8121641 100644 --- a/api/funkwhale_api/federation/models.py +++ b/api/funkwhale_api/federation/models.py @@ -145,6 +145,7 @@ class Domain(models.Model): actors=models.Count("actors", distinct=True), outbox_activities=models.Count("actors__outbox_activities", distinct=True), libraries=models.Count("actors__libraries", distinct=True), + channels=models.Count("actors__owned_channels", distinct=True), received_library_follows=models.Count( "actors__libraries__received_follows", distinct=True ), @@ -283,6 +284,7 @@ class Actor(models.Model): data = Actor.objects.filter(pk=self.pk).aggregate( outbox_activities=models.Count("outbox_activities", distinct=True), libraries=models.Count("libraries", distinct=True), + channels=models.Count("owned_channels", distinct=True), received_library_follows=models.Count( "libraries__received_follows", distinct=True ), diff --git a/api/funkwhale_api/federation/routes.py b/api/funkwhale_api/federation/routes.py index 56eae7f12..4177f76d5 100644 --- a/api/funkwhale_api/federation/routes.py +++ b/api/funkwhale_api/federation/routes.py @@ -482,6 +482,8 @@ def inbox_flag(payload, context): @outbox.register({"type": "Flag"}) def outbox_flag(context): report = context["report"] + if not report.target or not report.target.fid: + return actor = actors.get_service_actor() serializer = serializers.FlagSerializer(report) yield { diff --git a/api/funkwhale_api/federation/utils.py b/api/funkwhale_api/federation/utils.py index b1f3fdb06..7f2f346e1 100644 --- a/api/funkwhale_api/federation/utils.py +++ b/api/funkwhale_api/federation/utils.py @@ -266,5 +266,11 @@ def get_object_by_fid(fid, local=None): if not result: raise ObjectDoesNotExist() + model = apps.get_model(*result["__type"].split(".")) + instance = model.objects.get(fid=fid) + if model._meta.label == "federation.Actor": + channel = instance.get_channel() + if channel: + return channel - return apps.get_model(*result["__type"].split(".")).objects.get(fid=fid) + return instance diff --git a/api/funkwhale_api/manage/filters.py b/api/funkwhale_api/manage/filters.py index eb86a59f0..727911995 100644 --- a/api/funkwhale_api/manage/filters.py +++ b/api/funkwhale_api/manage/filters.py @@ -8,6 +8,7 @@ from funkwhale_api.common import fields from funkwhale_api.common import filters as common_filters from funkwhale_api.common import search +from funkwhale_api.audio import models as audio_models from funkwhale_api.federation import models as federation_models from funkwhale_api.federation import utils as federation_utils from funkwhale_api.moderation import models as moderation_models @@ -34,6 +35,34 @@ def get_actor_filter(actor_field): return {"field": ActorField(), "handler": handler} +class ManageChannelFilterSet(filters.FilterSet): + q = fields.SmartSearchFilter( + config=search.SearchConfig( + search_fields={ + "name": {"to": "artist__name"}, + "username": {"to": "artist__name"}, + "fid": {"to": "artist__fid"}, + "rss": {"to": "rss_url"}, + }, + filter_fields={ + "uuid": {"to": "uuid"}, + "category": {"to": "artist__content_category"}, + "domain": { + "handler": lambda v: federation_utils.get_domain_query_from_url( + v, url_field="attributed_to__fid" + ) + }, + "tag": {"to": "artist__tagged_items__tag__name", "distinct": True}, + "account": get_actor_filter("attributed_to"), + }, + ) + ) + + class Meta: + model = audio_models.Channel + fields = ["q"] + + class ManageArtistFilterSet(filters.FilterSet): q = fields.SmartSearchFilter( config=search.SearchConfig( @@ -52,6 +81,7 @@ class ManageArtistFilterSet(filters.FilterSet): "field": forms.IntegerField(), "distinct": True, }, + "category": {"to": "content_category"}, "tag": {"to": "tagged_items__tag__name", "distinct": True}, }, ) @@ -59,7 +89,7 @@ class ManageArtistFilterSet(filters.FilterSet): class Meta: model = music_models.Artist - fields = ["q", "name", "mbid", "fid"] + fields = ["q", "name", "mbid", "fid", "content_category"] class ManageAlbumFilterSet(filters.FilterSet): diff --git a/api/funkwhale_api/manage/serializers.py b/api/funkwhale_api/manage/serializers.py index c6966f108..d29433e56 100644 --- a/api/funkwhale_api/manage/serializers.py +++ b/api/funkwhale_api/manage/serializers.py @@ -3,6 +3,7 @@ from django.db import transaction from rest_framework import serializers +from funkwhale_api.audio import models as audio_models from funkwhale_api.common import fields as common_fields from funkwhale_api.common import serializers as common_serializers from funkwhale_api.common import utils as common_utils @@ -386,26 +387,39 @@ class ManageNestedAlbumSerializer(ManageBaseAlbumSerializer): class ManageArtistSerializer( music_serializers.OptionalDescriptionMixin, ManageBaseArtistSerializer ): - albums = ManageNestedAlbumSerializer(many=True) - tracks = ManageNestedTrackSerializer(many=True) attributed_to = ManageBaseActorSerializer() tags = serializers.SerializerMethodField() + tracks_count = serializers.SerializerMethodField() + albums_count = serializers.SerializerMethodField() + channel = serializers.SerializerMethodField() cover = music_serializers.cover_field class Meta: model = music_models.Artist fields = ManageBaseArtistSerializer.Meta.fields + [ - "albums", - "tracks", + "tracks_count", + "albums_count", "attributed_to", "tags", "cover", + "channel", + "content_category", ] + def get_tracks_count(self, obj): + return getattr(obj, "_tracks_count", None) + + def get_albums_count(self, obj): + return getattr(obj, "_albums_count", None) + def get_tags(self, obj): tagged_items = getattr(obj, "_prefetched_tagged_items", []) return [ti.tag.name for ti in tagged_items] + def get_channel(self, obj): + if "channel" in obj._state.fields_cache and obj.get_channel(): + return str(obj.channel.uuid) + class ManageNestedArtistSerializer(ManageBaseArtistSerializer): pass @@ -743,3 +757,23 @@ class ManageUserRequestSerializer(serializers.ModelSerializer): def get_notes(self, o): notes = getattr(o, "_prefetched_notes", []) return ManageBaseNoteSerializer(notes, many=True).data + + +class ManageChannelSerializer(serializers.ModelSerializer): + attributed_to = ManageBaseActorSerializer() + actor = ManageBaseActorSerializer() + artist = ManageArtistSerializer() + + class Meta: + model = audio_models.Channel + fields = [ + "id", + "uuid", + "creation_date", + "artist", + "attributed_to", + "actor", + "rss_url", + "metadata", + ] + read_only_fields = fields diff --git a/api/funkwhale_api/manage/urls.py b/api/funkwhale_api/manage/urls.py index 8af692d7a..760e24c8d 100644 --- a/api/funkwhale_api/manage/urls.py +++ b/api/funkwhale_api/manage/urls.py @@ -27,6 +27,7 @@ users_router.register(r"invitations", views.ManageInvitationViewSet, "invitation other_router = routers.OptionalSlashRouter() other_router.register(r"accounts", views.ManageActorViewSet, "accounts") +other_router.register(r"channels", views.ManageChannelViewSet, "channels") other_router.register(r"tags", views.ManageTagViewSet, "tags") urlpatterns = [ diff --git a/api/funkwhale_api/manage/views.py b/api/funkwhale_api/manage/views.py index 5a0f81a39..0f0f16ce0 100644 --- a/api/funkwhale_api/manage/views.py +++ b/api/funkwhale_api/manage/views.py @@ -6,12 +6,15 @@ from django.db.models import Count, Prefetch, Q, Sum, OuterRef, Subquery from django.db.models.functions import Coalesce, Length from django.shortcuts import get_object_or_404 +from funkwhale_api.audio import models as audio_models +from funkwhale_api.common.mixins import MultipleLookupDetailMixin from funkwhale_api.common import models as common_models from funkwhale_api.common import preferences, decorators from funkwhale_api.common import utils as common_utils from funkwhale_api.favorites import models as favorites_models from funkwhale_api.federation import models as federation_models from funkwhale_api.federation import tasks as federation_tasks +from funkwhale_api.federation import utils as federation_utils from funkwhale_api.history import models as history_models from funkwhale_api.music import models as music_models from funkwhale_api.music import views as music_views @@ -25,37 +28,39 @@ from funkwhale_api.users import models as users_models from . import filters, serializers -def get_stats(tracks, target): - data = {} +def get_stats(tracks, target, ignore_fields=[]): tracks = list(tracks.values_list("pk", flat=True)) uploads = music_models.Upload.objects.filter(track__in=tracks) - data["listenings"] = history_models.Listening.objects.filter( - track__in=tracks - ).count() - data["mutations"] = common_models.Mutation.objects.get_for_target(target).count() - data["playlists"] = ( - playlists_models.PlaylistTrack.objects.filter(track__in=tracks) - .values_list("playlist", flat=True) - .distinct() - .count() - ) - data["track_favorites"] = favorites_models.TrackFavorite.objects.filter( - track__in=tracks - ).count() - data["libraries"] = ( - uploads.filter(library__channel=None) - .values_list("library", flat=True) - .distinct() - .count() - ) - data["channels"] = ( - uploads.exclude(library__channel=None) - .values_list("library", flat=True) - .distinct() - .count() - ) - data["uploads"] = uploads.count() - data["reports"] = moderation_models.Report.objects.get_for_target(target).count() + fields = { + "listenings": history_models.Listening.objects.filter(track__in=tracks), + "mutations": common_models.Mutation.objects.get_for_target(target), + "playlists": ( + playlists_models.PlaylistTrack.objects.filter(track__in=tracks) + .values_list("playlist", flat=True) + .distinct() + ), + "track_favorites": ( + favorites_models.TrackFavorite.objects.filter(track__in=tracks) + ), + "libraries": ( + uploads.filter(library__channel=None) + .values_list("library", flat=True) + .distinct() + ), + "channels": ( + uploads.exclude(library__channel=None) + .values_list("library", flat=True) + .distinct() + ), + "uploads": uploads, + "reports": moderation_models.Report.objects.get_for_target(target), + } + data = {} + for key, qs in fields.items(): + if key in ignore_fields: + continue + data[key] = qs.count() + data.update(get_media_stats(uploads)) return data @@ -78,17 +83,10 @@ class ManageArtistViewSet( queryset = ( music_models.Artist.objects.all() .order_by("-id") - .select_related("attributed_to", "attachment_cover",) - .prefetch_related( - "tracks", - Prefetch( - "albums", - queryset=music_models.Album.objects.select_related( - "attachment_cover" - ).annotate(tracks_count=Count("tracks")), - ), - music_views.TAG_PREFETCH, - ) + .select_related("attributed_to", "attachment_cover", "channel") + .annotate(_tracks_count=Count("tracks")) + .annotate(_albums_count=Count("albums")) + .prefetch_related(music_views.TAG_PREFETCH) ) serializer_class = serializers.ManageArtistSerializer filterset_class = filters.ManageArtistFilterSet @@ -661,3 +659,64 @@ class ManageUserRequestViewSet( ) else: serializer.save() + + +class ManageChannelViewSet( + MultipleLookupDetailMixin, + mixins.ListModelMixin, + mixins.RetrieveModelMixin, + mixins.DestroyModelMixin, + viewsets.GenericViewSet, +): + + url_lookups = [ + { + "lookup_field": "uuid", + "validator": serializers.serializers.UUIDField().to_internal_value, + }, + { + "lookup_field": "username", + "validator": federation_utils.get_actor_data_from_username, + "get_query": lambda v: Q( + actor__domain=v["domain"], + actor__preferred_username__iexact=v["username"], + ), + }, + ] + queryset = ( + audio_models.Channel.objects.all() + .order_by("-id") + .select_related("attributed_to", "actor",) + .prefetch_related( + Prefetch( + "artist", + queryset=( + music_models.Artist.objects.all() + .order_by("-id") + .select_related("attributed_to", "attachment_cover", "channel") + .annotate(_tracks_count=Count("tracks")) + .annotate(_albums_count=Count("albums")) + .prefetch_related(music_views.TAG_PREFETCH) + ), + ) + ) + ) + serializer_class = serializers.ManageChannelSerializer + filterset_class = filters.ManageChannelFilterSet + required_scope = "instance:libraries" + ordering_fields = ["creation_date", "name"] + + @rest_decorators.action(methods=["get"], detail=True) + def stats(self, request, *args, **kwargs): + channel = self.get_object() + tracks = music_models.Track.objects.filter( + Q(artist=channel.artist) | Q(album__artist=channel.artist) + ) + data = get_stats(tracks, channel, ignore_fields=["libraries", "channels"]) + data["follows"] = channel.actor.received_follows.count() + return response.Response(data, status=200) + + def get_serializer_context(self): + context = super().get_serializer_context() + context["description"] = self.action in ["retrieve", "create", "update"] + return context diff --git a/api/funkwhale_api/moderation/serializers.py b/api/funkwhale_api/moderation/serializers.py index 7d772d39e..4b099a1b7 100644 --- a/api/funkwhale_api/moderation/serializers.py +++ b/api/funkwhale_api/moderation/serializers.py @@ -6,6 +6,7 @@ from django.core.serializers.json import DjangoJSONEncoder import persisting_theory from rest_framework import serializers +from funkwhale_api.audio import models as audio_models from funkwhale_api.common import fields as common_fields from funkwhale_api.common import preferences from funkwhale_api.federation import models as federation_models @@ -61,20 +62,36 @@ class UserFilterSerializer(serializers.ModelSerializer): state_serializers = persisting_theory.Registry() +class DescriptionStateMixin(object): + def get_description(self, o): + if o.description: + return o.description.text + + TAGS_FIELD = serializers.ListField(source="get_tags") @state_serializers.register(name="music.Artist") -class ArtistStateSerializer(serializers.ModelSerializer): +class ArtistStateSerializer(DescriptionStateMixin, serializers.ModelSerializer): tags = TAGS_FIELD class Meta: model = music_models.Artist - fields = ["id", "name", "mbid", "fid", "creation_date", "uuid", "tags"] + fields = [ + "id", + "name", + "mbid", + "fid", + "creation_date", + "uuid", + "tags", + "content_category", + "description", + ] @state_serializers.register(name="music.Album") -class AlbumStateSerializer(serializers.ModelSerializer): +class AlbumStateSerializer(DescriptionStateMixin, serializers.ModelSerializer): tags = TAGS_FIELD artist = ArtistStateSerializer() @@ -90,11 +107,12 @@ class AlbumStateSerializer(serializers.ModelSerializer): "artist", "release_date", "tags", + "description", ] @state_serializers.register(name="music.Track") -class TrackStateSerializer(serializers.ModelSerializer): +class TrackStateSerializer(DescriptionStateMixin, serializers.ModelSerializer): tags = TAGS_FIELD artist = ArtistStateSerializer() album = AlbumStateSerializer() @@ -115,6 +133,7 @@ class TrackStateSerializer(serializers.ModelSerializer): "license", "copyright", "tags", + "description", ] @@ -156,6 +175,36 @@ class ActorStateSerializer(serializers.ModelSerializer): ] +@state_serializers.register(name="audio.Channel") +class ChannelStateSerializer(serializers.ModelSerializer): + rss_url = serializers.CharField(source="get_rss_url") + name = serializers.CharField(source="artist.name") + full_username = serializers.CharField(source="actor.full_username") + domain = serializers.CharField(source="actor.domain_id") + description = serializers.SerializerMethodField() + tags = serializers.ListField(source="artist.get_tags") + content_category = serializers.CharField(source="artist.content_category") + + class Meta: + model = audio_models.Channel + fields = [ + "uuid", + "name", + "rss_url", + "metadata", + "full_username", + "description", + "domain", + "creation_date", + "tags", + "content_category", + ] + + def get_description(self, o): + if o.artist.description: + return o.artist.description.text + + def get_actor_query(attr, value): data = federation_utils.get_actor_data_from_username(value) return federation_utils.get_actor_from_username_data_query(None, data) @@ -163,6 +212,7 @@ def get_actor_query(attr, value): def get_target_owner(target): mapping = { + audio_models.Channel: lambda t: t.attributed_to, music_models.Artist: lambda t: t.attributed_to, music_models.Album: lambda t: t.attributed_to, music_models.Track: lambda t: t.attributed_to, @@ -175,6 +225,11 @@ def get_target_owner(target): TARGET_CONFIG = { + "channel": { + "queryset": audio_models.Channel.objects.all(), + "id_attr": "uuid", + "id_field": serializers.UUIDField(), + }, "artist": {"queryset": music_models.Artist.objects.all()}, "album": {"queryset": music_models.Album.objects.all()}, "track": {"queryset": music_models.Track.objects.all()}, diff --git a/api/tests/federation/test_models.py b/api/tests/federation/test_models.py index a0895d44a..b6c8dc0ad 100644 --- a/api/tests/federation/test_models.py +++ b/api/tests/federation/test_models.py @@ -126,6 +126,7 @@ def test_domain_stats(factories): "libraries": 0, "tracks": 0, "albums": 0, + "channels": 0, "uploads": 0, "artists": 0, "outbox_activities": 0, @@ -148,6 +149,7 @@ def test_actor_stats(factories): "uploads": 0, "artists": 0, "reports": 0, + "channels": 0, "requests": 0, "outbox_activities": 0, "received_library_follows": 0, diff --git a/api/tests/federation/test_routes.py b/api/tests/federation/test_routes.py index 0ae18bd15..63d8905ca 100644 --- a/api/tests/federation/test_routes.py +++ b/api/tests/federation/test_routes.py @@ -844,6 +844,7 @@ def test_inbox_delete_actor_doesnt_delete_local_actor(factories): @pytest.mark.parametrize( "factory_name, factory_kwargs", [ + ("audio.Channel", {"local": True}), ("federation.Actor", {"local": True}), ("music.Artist", {"local": True}), ("music.Album", {"local": True}), @@ -885,6 +886,7 @@ def test_inbox_flag(factory_name, factory_kwargs, factories, mocker): @pytest.mark.parametrize( "factory_name, factory_kwargs", [ + ("audio.Channel", {"local": True}), ("federation.Actor", {"local": True}), ("music.Artist", {"local": True}), ("music.Album", {"local": True}), diff --git a/api/tests/federation/test_utils.py b/api/tests/federation/test_utils.py index 6ba9ccfae..ea3b43f51 100644 --- a/api/tests/federation/test_utils.py +++ b/api/tests/federation/test_utils.py @@ -207,3 +207,9 @@ def test_get_obj_by_fid(factory_name, factories): obj = factories[factory_name]() factories[factory_name]() assert utils.get_object_by_fid(obj.fid) == obj + + +def test_get_channel_by_fid(factories): + obj = factories["audio.Channel"]() + factories["audio.Channel"]() + assert utils.get_object_by_fid(obj.actor.fid) == obj diff --git a/api/tests/manage/test_serializers.py b/api/tests/manage/test_serializers.py index d2d00b058..c4dbaa45e 100644 --- a/api/tests/manage/test_serializers.py +++ b/api/tests/manage/test_serializers.py @@ -287,8 +287,11 @@ def test_instance_policy_serializer_purges_target_actor( def test_manage_artist_serializer(factories, now, to_api_date): artist = factories["music.Artist"](attributed=True, with_cover=True) - track = factories["music.Track"](artist=artist) - album = factories["music.Album"](artist=artist) + channel = factories["audio.Channel"](artist=artist) + # put channel in cache + artist.get_channel() + setattr(artist, "_tracks_count", 12) + setattr(artist, "_albums_count", 13) expected = { "id": artist.id, "domain": artist.domain_name, @@ -297,12 +300,14 @@ def test_manage_artist_serializer(factories, now, to_api_date): "name": artist.name, "mbid": artist.mbid, "creation_date": to_api_date(artist.creation_date), - "albums": [serializers.ManageNestedAlbumSerializer(album).data], - "tracks": [serializers.ManageNestedTrackSerializer(track).data], + "tracks_count": 12, + "albums_count": 13, "attributed_to": serializers.ManageBaseActorSerializer( artist.attributed_to ).data, "tags": [], + "channel": str(channel.uuid), + "content_category": artist.content_category, "cover": common_serializers.AttachmentSerializer(artist.attachment_cover).data, } s = serializers.ManageArtistSerializer(artist) @@ -585,3 +590,22 @@ def test_manage_user_request_serializer(factories, to_api_date): s = serializers.ManageUserRequestSerializer(user_request) assert s.data == expected + + +def test_manage_channel_serializer(factories, now, to_api_date): + channel = factories["audio.Channel"]() + expected = { + "id": channel.id, + "uuid": channel.uuid, + "artist": serializers.ManageArtistSerializer(channel.artist).data, + "actor": serializers.ManageBaseActorSerializer(channel.actor).data, + "attributed_to": serializers.ManageBaseActorSerializer( + channel.attributed_to + ).data, + "creation_date": to_api_date(channel.creation_date), + "rss_url": channel.get_rss_url(), + "metadata": channel.metadata, + } + s = serializers.ManageChannelSerializer(channel) + + assert s.data == expected diff --git a/api/tests/manage/test_views.py b/api/tests/manage/test_views.py index 2482e0d80..bf4f62bb9 100644 --- a/api/tests/manage/test_views.py +++ b/api/tests/manage/test_views.py @@ -599,3 +599,50 @@ def test_user_request_update_status_assigns(factories, superuser_api_client, moc new_status="refused", old_status="pending", ) + + +def test_channel_list(factories, superuser_api_client, settings): + channel = factories["audio.Channel"]() + url = reverse("api:v1:manage:channels-list") + response = superuser_api_client.get(url) + + assert response.status_code == 200 + + assert response.data["count"] == 1 + assert response.data["results"][0]["id"] == channel.id + + +def test_channel_detail(factories, superuser_api_client): + channel = factories["audio.Channel"]() + url = reverse("api:v1:manage:channels-detail", kwargs={"composite": channel.uuid}) + response = superuser_api_client.get(url) + + assert response.status_code == 200 + assert response.data["id"] == channel.id + + +def test_channel_delete(factories, superuser_api_client, mocker): + channel = factories["audio.Channel"]() + url = reverse("api:v1:manage:channels-detail", kwargs={"composite": channel.uuid}) + response = superuser_api_client.delete(url) + + assert response.status_code == 204 + + +def test_channel_detail_stats(factories, superuser_api_client): + channel = factories["audio.Channel"]() + url = reverse("api:v1:manage:channels-stats", kwargs={"composite": channel.uuid}) + response = superuser_api_client.get(url) + expected = { + "uploads": 0, + "playlists": 0, + "listenings": 0, + "mutations": 0, + "reports": 0, + "follows": 0, + "track_favorites": 0, + "media_total_size": 0, + "media_downloaded_size": 0, + } + assert response.status_code == 200 + assert response.data == expected diff --git a/api/tests/moderation/test_serializers.py b/api/tests/moderation/test_serializers.py index 01cb323ee..9089dc371 100644 --- a/api/tests/moderation/test_serializers.py +++ b/api/tests/moderation/test_serializers.py @@ -52,6 +52,7 @@ def test_user_filter_serializer_save(factories): "full_username", serializers.ActorStateSerializer, ), + ("audio.Channel", "channel", "uuid", serializers.ChannelStateSerializer), ], ) def test_report_federated_entity_serializer_save( @@ -161,6 +162,7 @@ def test_report_serializer_save_anonymous(factories, mocker): ("music.Library", {}, "actor"), ("playlists.Playlist", {"user__with_actor": True}, "user.actor"), ("federation.Actor", {}, "self"), + ("audio.Channel", {}, "attributed_to"), ], ) def test_get_target_owner(factory_name, factory_kwargs, owner_field, factories): diff --git a/front/src/App.vue b/front/src/App.vue index 6e8f34dbb..f85d8e920 100644 --- a/front/src/App.vue +++ b/front/src/App.vue @@ -406,9 +406,9 @@ export default { }, 'serviceWorker.updateAvailable': { handler (v) { - // if (!v) { - // return - // } + if (!v) { + return + } let self = this this.$store.commit('ui/addMessage', { content: this.$pgettext("App/Message/Paragraph", "A new version of the app is available."), diff --git a/front/src/components/audio/ChannelCard.vue b/front/src/components/audio/ChannelCard.vue index 37db4805e..c7490023e 100644 --- a/front/src/components/audio/ChannelCard.vue +++ b/front/src/components/audio/ChannelCard.vue @@ -30,7 +30,11 @@ :title="updatedTitle"> {{ object.artist.modification_date | fromNow }} - + diff --git a/front/src/components/audio/PlayButton.vue b/front/src/components/audio/PlayButton.vue index 62f72bc95..906dafee3 100644 --- a/front/src/components/audio/PlayButton.vue +++ b/front/src/components/audio/PlayButton.vue @@ -35,7 +35,7 @@ Hide content from this artist