From 7f25c3f2e5bad84669521cabb345031752b8b373 Mon Sep 17 00:00:00 2001 From: Eliot Berriot Date: Tue, 27 Feb 2018 13:18:49 +0100 Subject: [PATCH 01/14] Fixed #85: broken documentation --- docs/changelog.rst | 39 +-------------------------------------- docs/conf.py | 4 +--- 2 files changed, 2 insertions(+), 41 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 0e6872f4b..491ea7340 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,38 +1 @@ -Changelog -========= - -0.2.1 ------ - -2017-07-17 - -* Now return media files with absolute URL -* Now display CLI instructions to download a set of tracks -* Fixed #33: sort by track position in album in API by default, also reuse that information on frontend side -* More robust audio player and queue in various situations: -* upgrade to latest dynamic_preferences and use redis as cache even locally - - -0.2 -------- - -2017-07-09 - -* [feature] can now import artist and releases from youtube and musicbrainz. - This requires a YouTube API key for the search -* [breaking] we now check for user permission before serving audio files, which requires - a specific configuration block in your reverse proxy configuration:: - - location /_protected/media { - internal; - alias /srv/funkwhale/data/media; - } - - - -0.1 -------- - -2017-06-26 - -Initial release +.. include:: ../CHANGELOG diff --git a/docs/conf.py b/docs/conf.py index 3a0c8f6f1..01da9bc05 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -57,9 +57,7 @@ author = 'Eliot Berriot' # built documents. # # The short X.Y version. -# version = funkwhale_api.__version__ -# @TODO use real version here -version = 'feature/22-debian-installation' +version = funkwhale_api.__version__ # The full version, including alpha/beta/rc tags. release = version From fbb256bc9baa495d299e02f13db8eb5e89713c01 Mon Sep 17 00:00:00 2001 From: Eliot Berriot Date: Tue, 27 Feb 2018 17:51:27 +0100 Subject: [PATCH 02/14] Fix #89: Always use username in sidebar --- CHANGELOG | 2 ++ front/src/store/auth.js | 1 + front/test/unit/specs/store/auth.spec.js | 1 + 3 files changed, 4 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 1b98df96e..561c3f7e8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,8 @@ Changelog 0.6 (Unreleased) ---------------- +- Always use username in sidebar (#89) + 0.5.2 (2018-02-26) ------------------ diff --git a/front/src/store/auth.js b/front/src/store/auth.js index 24dafcd72..7944cae08 100644 --- a/front/src/store/auth.js +++ b/front/src/store/auth.js @@ -89,6 +89,7 @@ export default { logger.default.info('Successfully fetched user profile') let data = response.data commit('profile', data) + commit('username', data.username) dispatch('favorites/fetch', null, {root: true}) Object.keys(data.permissions).forEach(function (key) { // this makes it easier to check for permissions in templates diff --git a/front/test/unit/specs/store/auth.spec.js b/front/test/unit/specs/store/auth.spec.js index aa07f9f8b..3271f5168 100644 --- a/front/test/unit/specs/store/auth.spec.js +++ b/front/test/unit/specs/store/auth.spec.js @@ -176,6 +176,7 @@ describe('store/auth', () => { action: store.actions.fetchProfile, expectedMutations: [ { type: 'profile', payload: profile }, + { type: 'username', payload: profile.username }, { type: 'permission', payload: {key: 'admin', status: true} } ], expectedActions: [ From b5226367a57a3542f6d31af1a28834b1914aadc7 Mon Sep 17 00:00:00 2001 From: Eliot Berriot Date: Tue, 27 Feb 2018 17:43:50 +0100 Subject: [PATCH 03/14] Fixed broken import because of missing transaction --- CHANGELOG | 1 + api/funkwhale_api/common/utils.py | 8 ++++++++ api/funkwhale_api/music/tasks.py | 5 ++++- api/funkwhale_api/music/views.py | 13 +++++++++++-- .../audiofile/management/commands/import_files.py | 9 +++++++-- api/tests/music/test_api.py | 7 +++++-- api/tests/test_import_audio_file.py | 7 +++++-- 7 files changed, 41 insertions(+), 9 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 561c3f7e8..7c1215ee4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,6 +5,7 @@ Changelog ---------------- - Always use username in sidebar (#89) +- Fixed broken import because of missing transaction 0.5.2 (2018-02-26) diff --git a/api/funkwhale_api/common/utils.py b/api/funkwhale_api/common/utils.py index 838c15c00..c9d450e6a 100644 --- a/api/funkwhale_api/common/utils.py +++ b/api/funkwhale_api/common/utils.py @@ -1,6 +1,8 @@ import os import shutil +from django.db import transaction + def rename_file(instance, field_name, new_name, allow_missing_file=False): field = getattr(instance, field_name) @@ -17,3 +19,9 @@ def rename_file(instance, field_name, new_name, allow_missing_file=False): field.name = os.path.join(initial_path, new_name_with_extension) instance.save() return new_name_with_extension + + +def on_commit(f, *args, **kwargs): + return transaction.on_commit( + lambda: f(*args, **kwargs) + ) diff --git a/api/funkwhale_api/music/tasks.py b/api/funkwhale_api/music/tasks.py index fc706c812..cb4a737c9 100644 --- a/api/funkwhale_api/music/tasks.py +++ b/api/funkwhale_api/music/tasks.py @@ -73,7 +73,10 @@ def _do_import(import_job, replace): @celery.app.task(name='ImportJob.run', bind=True) -@celery.require_instance(models.ImportJob, 'import_job') +@celery.require_instance( + models.ImportJob.objects.filter( + status__in=['pending', 'errored']), + 'import_job') def import_job_run(self, import_job, replace=False): def mark_errored(): import_job.status = 'errored' diff --git a/api/funkwhale_api/music/views.py b/api/funkwhale_api/music/views.py index bf9d39b1d..ac76667da 100644 --- a/api/funkwhale_api/music/views.py +++ b/api/funkwhale_api/music/views.py @@ -19,6 +19,7 @@ from musicbrainzngs import ResponseError from django.contrib.auth.decorators import login_required from django.utils.decorators import method_decorator +from funkwhale_api.common import utils as funkwhale_utils from funkwhale_api.requests.models import ImportRequest from funkwhale_api.musicbrainz import api from funkwhale_api.common.permissions import ( @@ -116,7 +117,10 @@ class ImportJobViewSet( def perform_create(self, serializer): source = 'file://' + serializer.validated_data['audio_file'].name serializer.save(source=source) - tasks.import_job_run.delay(import_job_id=serializer.instance.pk) + funkwhale_utils.on_commit( + tasks.import_job_run.delay, + import_job_id=serializer.instance.pk + ) class TrackViewSet(TagViewSetMixin, SearchMixin, viewsets.ReadOnlyModelViewSet): @@ -336,6 +340,7 @@ class SubmitViewSet(viewsets.ViewSet): data, request, batch=None, import_request=import_request) return Response(import_data) + @transaction.atomic def _import_album(self, data, request, batch=None, import_request=None): # we import the whole album here to prevent race conditions that occurs # when using get_or_create_from_api in tasks @@ -355,7 +360,11 @@ class SubmitViewSet(viewsets.ViewSet): models.TrackFile.objects.get(track__mbid=row['mbid']) except models.TrackFile.DoesNotExist: job = models.ImportJob.objects.create(mbid=row['mbid'], batch=batch, source=row['source']) - tasks.import_job_run.delay(import_job_id=job.pk) + funkwhale_utils.on_commit( + tasks.import_job_run.delay, + import_job_id=job.pk + ) + serializer = serializers.ImportBatchSerializer(batch) return serializer.data, batch diff --git a/api/funkwhale_api/providers/audiofile/management/commands/import_files.py b/api/funkwhale_api/providers/audiofile/management/commands/import_files.py index 4130e93f3..17a199473 100644 --- a/api/funkwhale_api/providers/audiofile/management/commands/import_files.py +++ b/api/funkwhale_api/providers/audiofile/management/commands/import_files.py @@ -3,6 +3,9 @@ import os from django.core.files import File from django.core.management.base import BaseCommand, CommandError +from django.db import transaction + +from funkwhale_api.common import utils from funkwhale_api.music import tasks from funkwhale_api.users.models import User @@ -86,6 +89,7 @@ class Command(BaseCommand): self.stdout.write( "For details, please refer to import batch #".format(batch.pk)) + @transaction.atomic def do_import(self, matching, user, options): message = 'Importing {}...' if options['async']: @@ -94,7 +98,7 @@ class Command(BaseCommand): # we create an import batch binded to the user batch = user.imports.create(source='shell') async = options['async'] - handler = tasks.import_job_run.delay if async else tasks.import_job_run + import_handler = tasks.import_job_run.delay if async else tasks.import_job_run for path in matching: job = batch.jobs.create( source='file://' + path, @@ -105,7 +109,8 @@ class Command(BaseCommand): job.save() try: - handler(import_job_id=job.pk) + utils.on_commit(import_handler, import_job_id=job.pk) except Exception as e: self.stdout.write('Error: {}'.format(e)) + return batch diff --git a/api/tests/music/test_api.py b/api/tests/music/test_api.py index 7a856efd1..8196d3c09 100644 --- a/api/tests/music/test_api.py +++ b/api/tests/music/test_api.py @@ -6,6 +6,7 @@ from django.urls import reverse from funkwhale_api.music import models from funkwhale_api.musicbrainz import api from funkwhale_api.music import serializers +from funkwhale_api.music import tasks from . import data as api_data @@ -208,7 +209,7 @@ def test_user_can_create_an_empty_batch(client, factories): def test_user_can_create_import_job_with_file(client, factories, mocker): path = os.path.join(DATA_DIR, 'test.ogg') - m = mocker.patch('funkwhale_api.music.tasks.import_job_run.delay') + m = mocker.patch('funkwhale_api.common.utils.on_commit') user = factories['users.SuperUser']() batch = factories['music.ImportBatch'](submitted_by=user) url = reverse('api:v1:import-jobs-list') @@ -231,7 +232,9 @@ def test_user_can_create_import_job_with_file(client, factories, mocker): assert 'test.ogg' in job.source assert job.audio_file.read() == content - m.assert_called_once_with(import_job_id=job.pk) + m.assert_called_once_with( + tasks.import_job_run.delay, + import_job_id=job.pk) def test_can_search_artist(factories, client): diff --git a/api/tests/test_import_audio_file.py b/api/tests/test_import_audio_file.py index e81e9b814..2c254afd9 100644 --- a/api/tests/test_import_audio_file.py +++ b/api/tests/test_import_audio_file.py @@ -6,6 +6,7 @@ from django.core.management import call_command from django.core.management.base import CommandError from funkwhale_api.providers.audiofile import tasks +from funkwhale_api.music import tasks as music_tasks DATA_DIR = os.path.join( os.path.dirname(os.path.abspath(__file__)), @@ -53,7 +54,7 @@ def test_management_command_requires_a_valid_username(factories, mocker): def test_import_files_creates_a_batch_and_job(factories, mocker): - m = mocker.patch('funkwhale_api.music.tasks.import_job_run.delay') + m = m = mocker.patch('funkwhale_api.common.utils.on_commit') user = factories['users.User'](username='me') path = os.path.join(DATA_DIR, 'dummy_file.ogg') call_command( @@ -74,4 +75,6 @@ def test_import_files_creates_a_batch_and_job(factories, mocker): assert job.audio_file.read() == f.read() assert job.source == 'file://' + path - m.assert_called_once_with(import_job_id=job.pk) + m.assert_called_once_with( + music_tasks.import_job_run.delay, + import_job_id=job.pk) From 726bbf303208dfad9b644eb796854b0986f26ee2 Mon Sep 17 00:00:00 2001 From: Eliot Berriot Date: Tue, 27 Feb 2018 19:04:50 +0100 Subject: [PATCH 04/14] Now use towncrier for changelog management --- CHANGELOG | 6 +--- changes/__init__.py | 0 changes/changelog.d/89.bugfix | 1 + changes/changelog.d/changelog.misc | 1 + changes/changelog.d/transaction-import.bugfix | 1 + changes/template.rst | 27 +++++++++++++++ pyproject.toml | 33 +++++++++++++++++++ 7 files changed, 64 insertions(+), 5 deletions(-) create mode 100644 changes/__init__.py create mode 100644 changes/changelog.d/89.bugfix create mode 100644 changes/changelog.d/changelog.misc create mode 100644 changes/changelog.d/transaction-import.bugfix create mode 100644 changes/template.rst create mode 100644 pyproject.toml diff --git a/CHANGELOG b/CHANGELOG index 7c1215ee4..4ce156e06 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,11 +1,7 @@ Changelog ========= -0.6 (Unreleased) ----------------- - -- Always use username in sidebar (#89) -- Fixed broken import because of missing transaction +.. towncrier 0.5.2 (2018-02-26) diff --git a/changes/__init__.py b/changes/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/changes/changelog.d/89.bugfix b/changes/changelog.d/89.bugfix new file mode 100644 index 000000000..5338e87db --- /dev/null +++ b/changes/changelog.d/89.bugfix @@ -0,0 +1 @@ +Always use username in sidebar (#89) diff --git a/changes/changelog.d/changelog.misc b/changes/changelog.d/changelog.misc new file mode 100644 index 000000000..46516abc7 --- /dev/null +++ b/changes/changelog.d/changelog.misc @@ -0,0 +1 @@ +Switched to towncrier for changelog management and compilation diff --git a/changes/changelog.d/transaction-import.bugfix b/changes/changelog.d/transaction-import.bugfix new file mode 100644 index 000000000..0efe3f000 --- /dev/null +++ b/changes/changelog.d/transaction-import.bugfix @@ -0,0 +1 @@ +Fixed broken import because of missing transaction diff --git a/changes/template.rst b/changes/template.rst new file mode 100644 index 000000000..f4d94dee8 --- /dev/null +++ b/changes/template.rst @@ -0,0 +1,27 @@ +{% for section, _ in sections.items() %} +{% if sections[section] %} +{% for category, val in definitions.items() if category in sections[section]%} +{{ definitions[category]['name'] }}: + +{% if definitions[category]['showcontent'] %} +{% for text in sections[section][category].keys()|sort() %} +- {{ text }} +{% endfor %} + +{% else %} +- {{ sections[section][category]['']|join(', ') }} + +{% endif %} +{% if sections[section][category]|length == 0 %} +No significant changes. + +{% else %} +{% endif %} + +{% endfor %} +{% else %} +No significant changes. + + +{% endif %} +{% endfor %} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..baea16861 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,33 @@ +[tool.towncrier] + package = "changes" + package_dir = "" + filename = "CHANGELOG" + directory = "changes/changelog.d/" + start_string = ".. towncrier\n" + template = "changes/template.rst" + issue_format = "" + title_format = "{version} (unreleased)" + underlines = "-" + + [[tool.towncrier.section]] + path = "" + + [[tool.towncrier.type]] + directory = "feature" + name = "Features" + showcontent = true + + [[tool.towncrier.type]] + directory = "bugfix" + name = "Bugfixes" + showcontent = true + + [[tool.towncrier.type]] + directory = "doc" + name = "Documentation" + showcontent = true + + [[tool.towncrier.type]] + directory = "misc" + name = "Other" + showcontent = true From 49d38d2b44704973d13c537dcc386b7a4b996240 Mon Sep 17 00:00:00 2001 From: Eliot Berriot Date: Tue, 27 Feb 2018 19:18:24 +0100 Subject: [PATCH 05/14] Fixed #83: click event outside of player icons --- changes/changelog.d/83.bugfix | 0 front/src/components/audio/Player.vue | 18 ++++++------------ 2 files changed, 6 insertions(+), 12 deletions(-) create mode 100644 changes/changelog.d/83.bugfix diff --git a/changes/changelog.d/83.bugfix b/changes/changelog.d/83.bugfix new file mode 100644 index 000000000..e69de29bb diff --git a/front/src/components/audio/Player.vue b/front/src/components/audio/Player.vue index a6cd80523..b5cbd8f81 100644 --- a/front/src/components/audio/Player.vue +++ b/front/src/components/audio/Player.vue @@ -52,32 +52,28 @@
- +
- +
- +
- +
@@ -109,19 +105,17 @@
- +
- +
Date: Tue, 27 Feb 2018 18:35:54 +0100 Subject: [PATCH 06/14] Fixed #88: Now exclude tracks without file from radio candidates --- api/funkwhale_api/radios/radios.py | 28 +++++++++++++++----- api/tests/radios/test_radios.py | 42 ++++++++++++++++++++---------- changes/changelog.d/88.bugfix | 1 + 3 files changed, 50 insertions(+), 21 deletions(-) create mode 100644 changes/changelog.d/88.bugfix diff --git a/api/funkwhale_api/radios/radios.py b/api/funkwhale_api/radios/radios.py index 585bbbe33..0d045ea4d 100644 --- a/api/funkwhale_api/radios/radios.py +++ b/api/funkwhale_api/radios/radios.py @@ -1,5 +1,6 @@ import random from rest_framework import serializers +from django.db.models import Count from django.core.exceptions import ValidationError from taggit.models import Tag from funkwhale_api.users.models import User @@ -39,8 +40,11 @@ class SessionRadio(SimpleRadio): self.session = models.RadioSession.objects.create(user=user, radio_type=self.radio_type, **kwargs) return self.session - def get_queryset(self): - raise NotImplementedError + def get_queryset(self, **kwargs): + qs = Track.objects.annotate( + files_count=Count('files') + ) + return qs.filter(files_count__gt=0) def get_queryset_kwargs(self): return {} @@ -75,7 +79,9 @@ class SessionRadio(SimpleRadio): @registry.register(name='random') class RandomRadio(SessionRadio): def get_queryset(self, **kwargs): - return Track.objects.all() + qs = super().get_queryset(**kwargs) + return qs.order_by('?') + @registry.register(name='favorites') class FavoritesRadio(SessionRadio): @@ -87,8 +93,9 @@ class FavoritesRadio(SessionRadio): return kwargs def get_queryset(self, **kwargs): + qs = super().get_queryset(**kwargs) track_ids = kwargs['user'].track_favorites.all().values_list('track', flat=True) - return Track.objects.filter(pk__in=track_ids) + return qs.filter(pk__in=track_ids) @registry.register(name='custom') @@ -101,7 +108,11 @@ class CustomRadio(SessionRadio): return kwargs def get_queryset(self, **kwargs): - return filters.run(kwargs['custom_radio'].config) + qs = super().get_queryset(**kwargs) + return filters.run( + kwargs['custom_radio'].config, + candidates=qs, + ) def validate_session(self, data, **context): data = super().validate_session(data, **context) @@ -141,6 +152,7 @@ class TagRadio(RelatedObjectRadio): model = Tag def get_queryset(self, **kwargs): + qs = super().get_queryset(**kwargs) return Track.objects.filter(tags__in=[self.session.related_object]) @registry.register(name='artist') @@ -148,7 +160,8 @@ class ArtistRadio(RelatedObjectRadio): model = Artist def get_queryset(self, **kwargs): - return self.session.related_object.tracks.all() + qs = super().get_queryset(**kwargs) + return qs.filter(artist=self.session.related_object) @registry.register(name='less-listened') @@ -160,5 +173,6 @@ class LessListenedRadio(RelatedObjectRadio): super().clean(instance) def get_queryset(self, **kwargs): + qs = super().get_queryset(**kwargs) listened = self.session.user.listenings.all().values_list('track', flat=True) - return Track.objects.exclude(pk__in=listened).order_by('?') + return qs.exclude(pk__in=listened).order_by('?') diff --git a/api/tests/radios/test_radios.py b/api/tests/radios/test_radios.py index b00bfcd79..b731e3024 100644 --- a/api/tests/radios/test_radios.py +++ b/api/tests/radios/test_radios.py @@ -51,7 +51,8 @@ def test_can_pick_by_weight(): def test_can_get_choices_for_favorites_radio(factories): - tracks = factories['music.Track'].create_batch(10) + files = factories['music.TrackFile'].create_batch(10) + tracks = [f.track for f in files] user = factories['users.User']() for i in range(5): TrackFavorite.add(track=random.choice(tracks), user=user) @@ -71,8 +72,12 @@ def test_can_get_choices_for_favorites_radio(factories): def test_can_get_choices_for_custom_radio(factories): artist = factories['music.Artist']() - tracks = factories['music.Track'].create_batch(5, artist=artist) - wrong_tracks = factories['music.Track'].create_batch(5) + files = factories['music.TrackFile'].create_batch( + 5, track__artist=artist) + tracks = [f.track for f in files] + wrong_files = factories['music.TrackFile'].create_batch(5) + wrong_tracks = [f.track for f in wrong_files] + session = factories['radios.CustomRadioSession']( custom_radio__config=[{'type': 'artist', 'ids': [artist.pk]}] ) @@ -113,7 +118,8 @@ def test_can_start_custom_radio_from_api(logged_in_client, factories): def test_can_use_radio_session_to_filter_choices(factories): - tracks = factories['music.Track'].create_batch(30) + files = factories['music.TrackFile'].create_batch(30) + tracks = [f.track for f in files] user = factories['users.User']() radio = radios.RandomRadio() session = radio.start_session(user) @@ -156,8 +162,8 @@ def test_can_start_radio_for_anonymous_user(client, db): def test_can_get_track_for_session_from_api(factories, logged_in_client): - tracks = factories['music.Track'].create_batch(size=1) - + files = factories['music.TrackFile'].create_batch(1) + tracks = [f.track for f in files] url = reverse('api:v1:radios:sessions-list') response = logged_in_client.post(url, {'radio_type': 'random'}) session = models.RadioSession.objects.latest('id') @@ -169,7 +175,7 @@ def test_can_get_track_for_session_from_api(factories, logged_in_client): assert data['track']['id'] == tracks[0].id assert data['position'] == 1 - next_track = factories['music.Track']() + next_track = factories['music.TrackFile']().track response = logged_in_client.post(url, {'session': session.pk}) data = json.loads(response.content.decode('utf-8')) @@ -193,8 +199,11 @@ def test_related_object_radio_validate_related_object(factories): def test_can_start_artist_radio(factories): user = factories['users.User']() artist = factories['music.Artist']() - wrong_tracks = factories['music.Track'].create_batch(5) - good_tracks = factories['music.Track'].create_batch(5, artist=artist) + wrong_files = factories['music.TrackFile'].create_batch(5) + wrong_tracks = [f.track for f in wrong_files] + good_files = factories['music.TrackFile'].create_batch( + 5, track__artist=artist) + good_tracks = [f.track for f in good_files] radio = radios.ArtistRadio() session = radio.start_session(user, related_object=artist) @@ -206,8 +215,11 @@ def test_can_start_artist_radio(factories): def test_can_start_tag_radio(factories): user = factories['users.User']() tag = factories['taggit.Tag']() - wrong_tracks = factories['music.Track'].create_batch(5) - good_tracks = factories['music.Track'].create_batch(5, tags=[tag]) + wrong_files = factories['music.TrackFile'].create_batch(5) + wrong_tracks = [f.track for f in wrong_files] + good_files = factories['music.TrackFile'].create_batch( + 5, track__tags=[tag]) + good_tracks = [f.track for f in good_files] radio = radios.TagRadio() session = radio.start_session(user, related_object=tag) @@ -229,9 +241,11 @@ def test_can_start_artist_radio_from_api(client, factories): def test_can_start_less_listened_radio(factories): user = factories['users.User']() - history = factories['history.Listening'].create_batch(5, user=user) - wrong_tracks = [h.track for h in history] - good_tracks = factories['music.Track'].create_batch(size=5) + wrong_files = factories['music.TrackFile'].create_batch(5) + for f in wrong_files: + factories['history.Listening'](track=f.track, user=user) + good_files = factories['music.TrackFile'].create_batch(5) + good_tracks = [f.track for f in good_files] radio = radios.LessListenedRadio() session = radio.start_session(user) assert session.related_object == user diff --git a/changes/changelog.d/88.bugfix b/changes/changelog.d/88.bugfix new file mode 100644 index 000000000..d2f707b44 --- /dev/null +++ b/changes/changelog.d/88.bugfix @@ -0,0 +1 @@ +Now exclude tracks without file from radio candidates (#88) From 5f28f7e90c94f09ef6b5b750addb86fcc854d4df Mon Sep 17 00:00:00 2001 From: Eliot Berriot Date: Tue, 27 Feb 2018 19:39:26 +0100 Subject: [PATCH 07/14] Fixed #87:Now always load next radio track on last queue track ended --- front/src/store/player.js | 9 ++++++++- front/test/unit/specs/store/player.spec.js | 13 +++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/front/src/store/player.js b/front/src/store/player.js index df8d159f4..d1de34d38 100644 --- a/front/src/store/player.js +++ b/front/src/store/player.js @@ -78,8 +78,15 @@ export default { logger.default.error('Could not record track in history') }) }, - trackEnded ({dispatch}, track) { + trackEnded ({dispatch, rootState}, track) { dispatch('trackListened', track) + let queueState = rootState.queue + if (queueState.currentIndex === queueState.tracks.length - 1) { + // we've reached last track of queue, trigger a reload + // from radio if any + dispatch('radios/populateQueue', null, {root: true}) + } + dispatch('queue/next', null, {root: true}) dispatch('queue/next', null, {root: true}) }, trackErrored ({commit, dispatch}) { diff --git a/front/test/unit/specs/store/player.spec.js b/front/test/unit/specs/store/player.spec.js index af0b6b435..f87b83cd3 100644 --- a/front/test/unit/specs/store/player.spec.js +++ b/front/test/unit/specs/store/player.spec.js @@ -122,12 +122,25 @@ describe('store/player', () => { testAction({ action: store.actions.trackEnded, payload: {test: 'track'}, + params: {rootState: {queue: {currentIndex:0, tracks: [1, 2]}}}, expectedActions: [ { type: 'trackListened', payload: {test: 'track'} }, { type: 'queue/next', payload: null, options: {root: true} } ] }, done) }) + it('trackEnded calls populateQueue if last', (done) => { + testAction({ + action: store.actions.trackEnded, + payload: {test: 'track'}, + params: {rootState: {queue: {currentIndex:1, tracks: [1, 2]}}}, + expectedActions: [ + { type: 'trackListened', payload: {test: 'track'} }, + { type: 'radios/populateQueue', payload: null, options: {root: true} }, + { type: 'queue/next', payload: null, options: {root: true} } + ] + }, done) + }) it('trackErrored', (done) => { testAction({ action: store.actions.trackErrored, From 9b0d5541e00740caeda31732bc20f772843358be Mon Sep 17 00:00:00 2001 From: Eliot Berriot Date: Tue, 27 Feb 2018 19:43:07 +0100 Subject: [PATCH 08/14] Missing changelog fragment --- changes/changelog.d/87.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/changelog.d/87.bugfix diff --git a/changes/changelog.d/87.bugfix b/changes/changelog.d/87.bugfix new file mode 100644 index 000000000..534ddc60a --- /dev/null +++ b/changes/changelog.d/87.bugfix @@ -0,0 +1 @@ +Now always load next radio track on last queue track ended (#87) From 62d0381f91d1db3d6a38bc18f98fcecc6a36bb3d Mon Sep 17 00:00:00 2001 From: Eliot Berriot Date: Tue, 27 Feb 2018 19:56:02 +0100 Subject: [PATCH 09/14] Fixed #81: Search now unaccent letters for queries --- api/config/settings/common.py | 1 + .../common/migrations/0001_initial.py | 12 +++++++++ .../common/migrations/__init__.py | 0 api/funkwhale_api/music/views.py | 25 ++++++++++++------- changes/changelog.d/81.feature | 1 + 5 files changed, 30 insertions(+), 9 deletions(-) create mode 100644 api/funkwhale_api/common/migrations/0001_initial.py create mode 100644 api/funkwhale_api/common/migrations/__init__.py create mode 100644 changes/changelog.d/81.feature diff --git a/api/config/settings/common.py b/api/config/settings/common.py index 491babdd1..f5ddec00b 100644 --- a/api/config/settings/common.py +++ b/api/config/settings/common.py @@ -37,6 +37,7 @@ DJANGO_APPS = ( 'django.contrib.sites', 'django.contrib.messages', 'django.contrib.staticfiles', + 'django.contrib.postgres', # Useful template tags: # 'django.contrib.humanize', diff --git a/api/funkwhale_api/common/migrations/0001_initial.py b/api/funkwhale_api/common/migrations/0001_initial.py new file mode 100644 index 000000000..e95cc11e9 --- /dev/null +++ b/api/funkwhale_api/common/migrations/0001_initial.py @@ -0,0 +1,12 @@ +# Generated by Django 2.0.2 on 2018-02-27 18:43 +from django.db import migrations +from django.contrib.postgres.operations import UnaccentExtension + + +class Migration(migrations.Migration): + + dependencies = [] + + operations = [ + UnaccentExtension() + ] diff --git a/api/funkwhale_api/common/migrations/__init__.py b/api/funkwhale_api/common/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/api/funkwhale_api/music/views.py b/api/funkwhale_api/music/views.py index ac76667da..d026c9847 100644 --- a/api/funkwhale_api/music/views.py +++ b/api/funkwhale_api/music/views.py @@ -63,7 +63,7 @@ class ArtistViewSet(SearchMixin, viewsets.ReadOnlyModelViewSet): 'albums__tracks__tags')) serializer_class = serializers.ArtistSerializerNested permission_classes = [ConditionalAuthentication] - search_fields = ['name'] + search_fields = ['name__unaccent'] filter_class = filters.ArtistFilter ordering_fields = ('id', 'name', 'creation_date') @@ -76,7 +76,7 @@ class AlbumViewSet(SearchMixin, viewsets.ReadOnlyModelViewSet): 'tracks__files')) serializer_class = serializers.AlbumSerializerNested permission_classes = [ConditionalAuthentication] - search_fields = ['title'] + search_fields = ['title__unaccent'] ordering_fields = ('creation_date',) @@ -133,9 +133,9 @@ class TrackViewSet(TagViewSetMixin, SearchMixin, viewsets.ReadOnlyModelViewSet): search_fields = ['title', 'artist__name'] ordering_fields = ( 'creation_date', - 'title', - 'album__title', - 'artist__name', + 'title__unaccent', + 'album__title__unaccent', + 'artist__name__unaccent', ) def get_queryset(self): @@ -249,7 +249,11 @@ class Search(views.APIView): return Response(results, status=200) def get_tracks(self, query): - search_fields = ['mbid', 'title', 'album__title', 'artist__name'] + search_fields = [ + 'mbid', + 'title__unaccent', + 'album__title__unaccent', + 'artist__name__unaccent'] query_obj = utils.get_query(query, search_fields) return ( models.Track.objects.all() @@ -263,7 +267,10 @@ class Search(views.APIView): def get_albums(self, query): - search_fields = ['mbid', 'title', 'artist__name'] + search_fields = [ + 'mbid', + 'title__unaccent', + 'artist__name__unaccent'] query_obj = utils.get_query(query, search_fields) return ( models.Album.objects.all() @@ -277,7 +284,7 @@ class Search(views.APIView): def get_artists(self, query): - search_fields = ['mbid', 'name'] + search_fields = ['mbid', 'name__unaccent'] query_obj = utils.get_query(query, search_fields) return ( models.Artist.objects.all() @@ -292,7 +299,7 @@ class Search(views.APIView): def get_tags(self, query): - search_fields = ['slug', 'name'] + search_fields = ['slug', 'name__unaccent'] query_obj = utils.get_query(query, search_fields) # We want the shortest tag first diff --git a/changes/changelog.d/81.feature b/changes/changelog.d/81.feature new file mode 100644 index 000000000..56c44d3cc --- /dev/null +++ b/changes/changelog.d/81.feature @@ -0,0 +1 @@ +Search now unaccent letters for queries like "The Dø" or "Björk", yielding more results (#81) From dac8d6e05e2508e7de9f3ae6fe14990c1a4e8387 Mon Sep 17 00:00:00 2001 From: Eliot Berriot Date: Tue, 27 Feb 2018 21:27:53 +0100 Subject: [PATCH 10/14] Fix #86: skip to next track properly on 40X errors --- changes/changelog.d/86.bugfix | 1 + front/src/components/audio/Track.vue | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 changes/changelog.d/86.bugfix diff --git a/changes/changelog.d/86.bugfix b/changes/changelog.d/86.bugfix new file mode 100644 index 000000000..c02a1997c --- /dev/null +++ b/changes/changelog.d/86.bugfix @@ -0,0 +1 @@ +skip to next track properly on 40X errors (#86) diff --git a/front/src/components/audio/Track.vue b/front/src/components/audio/Track.vue index d8dcaff9b..e2b613095 100644 --- a/front/src/components/audio/Track.vue +++ b/front/src/components/audio/Track.vue @@ -7,7 +7,11 @@ @timeupdate="updateProgress" @ended="ended" preload> - + @@ -25,6 +29,11 @@ export default { startTime: {type: Number, default: 0}, autoplay: {type: Boolean, default: false} }, + data () { + return { + sourceErrors: 0 + } + }, computed: { ...mapState({ playing: state => state.player.playing, @@ -65,6 +74,13 @@ export default { errored: function () { this.$store.dispatch('player/trackErrored') }, + sourceErrored: function () { + this.sourceErrors += 1 + if (this.sourceErrors >= this.srcs.length) { + // all sources failed + this.errored() + } + }, updateDuration: function (e) { this.$store.commit('player/duration', this.$refs.audio.duration) }, From 7dfafea26ca32de534c2b4722ec6eca76b224fe6 Mon Sep 17 00:00:00 2001 From: Eliot Berriot Date: Tue, 27 Feb 2018 21:29:07 +0100 Subject: [PATCH 11/14] Disable radio populate after too much consecutive errors --- front/src/components/audio/Track.vue | 1 + front/src/store/player.js | 11 ++++++++++- front/src/store/radios.js | 5 ++++- front/test/unit/specs/store/player.spec.js | 14 +++++++++++++- front/test/unit/specs/store/radios.spec.js | 18 +++++++++++++++++- 5 files changed, 45 insertions(+), 4 deletions(-) diff --git a/front/src/components/audio/Track.vue b/front/src/components/audio/Track.vue index e2b613095..e3f1c18b3 100644 --- a/front/src/components/audio/Track.vue +++ b/front/src/components/audio/Track.vue @@ -86,6 +86,7 @@ export default { }, loaded: function () { this.$refs.audio.volume = this.volume + this.$store.commit('player/resetErrorCount') if (this.isCurrent) { this.$store.commit('player/duration', this.$refs.audio.duration) if (this.startTime) { diff --git a/front/src/store/player.js b/front/src/store/player.js index d1de34d38..2dc3a7402 100644 --- a/front/src/store/player.js +++ b/front/src/store/player.js @@ -5,6 +5,8 @@ import time from '@/utils/time' export default { namespaced: true, state: { + maxConsecutiveErrors: 5, + errorCount: 0, playing: false, volume: 0.5, duration: 0, @@ -25,6 +27,12 @@ export default { value = Math.max(value, 0) state.volume = value }, + incrementErrorCount (state) { + state.errorCount += 1 + }, + resetErrorCount (state) { + state.errorCount = 0 + }, duration (state, value) { state.duration = value }, @@ -89,8 +97,9 @@ export default { dispatch('queue/next', null, {root: true}) dispatch('queue/next', null, {root: true}) }, - trackErrored ({commit, dispatch}) { + trackErrored ({commit, dispatch, state}) { commit('errored', true) + commit('incrementErrorCount') dispatch('queue/next', null, {root: true}) }, updateProgress ({commit}, t) { diff --git a/front/src/store/radios.js b/front/src/store/radios.js index 922083d88..e95db5126 100644 --- a/front/src/store/radios.js +++ b/front/src/store/radios.js @@ -53,10 +53,13 @@ export default { commit('current', null) commit('running', false) }, - populateQueue ({state, dispatch}) { + populateQueue ({rootState, state, dispatch}) { if (!state.running) { return } + if (rootState.player.errorCount >= rootState.player.maxConsecutiveErrors - 1) { + return + } var params = { session: state.current.session } diff --git a/front/test/unit/specs/store/player.spec.js b/front/test/unit/specs/store/player.spec.js index f87b83cd3..b55fb010d 100644 --- a/front/test/unit/specs/store/player.spec.js +++ b/front/test/unit/specs/store/player.spec.js @@ -74,6 +74,16 @@ describe('store/player', () => { store.mutations.toggleLooping(state) expect(state.looping).to.equal(0) }) + it('increment error count', () => { + const state = { errorCount: 0 } + store.mutations.incrementErrorCount(state) + expect(state.errorCount).to.equal(1) + }) + it('reset error count', () => { + const state = { errorCount: 10 } + store.mutations.resetErrorCount(state) + expect(state.errorCount).to.equal(0) + }) }) describe('getters', () => { it('durationFormatted', () => { @@ -145,8 +155,10 @@ describe('store/player', () => { testAction({ action: store.actions.trackErrored, payload: {test: 'track'}, + params: {state: {errorCount: 0, maxConsecutiveErrors: 5}}, expectedMutations: [ - { type: 'errored', payload: true } + { type: 'errored', payload: true }, + { type: 'incrementErrorCount' } ], expectedActions: [ { type: 'queue/next', payload: null, options: {root: true} } diff --git a/front/test/unit/specs/store/radios.spec.js b/front/test/unit/specs/store/radios.spec.js index 3ff8a05ed..6de6b8dd9 100644 --- a/front/test/unit/specs/store/radios.spec.js +++ b/front/test/unit/specs/store/radios.spec.js @@ -69,7 +69,11 @@ describe('store/radios', () => { }) testAction({ action: store.actions.populateQueue, - params: {state: {running: true, current: {session: 1}}}, + params: { + state: {running: true, current: {session: 1}}, + rootState: {player: {errorCount: 0, maxConsecutiveErrors: 5}} + + }, expectedActions: [ { type: 'queue/append', payload: {track: {id: 1}}, options: {root: true} } ] @@ -82,5 +86,17 @@ describe('store/radios', () => { expectedActions: [] }, done) }) + it('populateQueue does nothing when too much errors', (done) => { + testAction({ + action: store.actions.populateQueue, + payload: {test: 'track'}, + params: { + rootState: {player: {errorCount: 5, maxConsecutiveErrors: 5}}, + state: {running: true} + }, + expectedActions: [] + }, done) + }) + }) }) From d875f0d07064fdcf118f0fc3a888ea0567ed76f3 Mon Sep 17 00:00:00 2001 From: Eliot Berriot Date: Tue, 27 Feb 2018 22:38:55 +0100 Subject: [PATCH 12/14] Fixed #82: Basic instance states are now available on /about --- api/funkwhale_api/instance/stats.py | 51 ++++++++++++ api/funkwhale_api/instance/urls.py | 4 + api/funkwhale_api/instance/views.py | 11 +++ api/tests/instance/test_stats.py | 84 +++++++++++++++++++ changes/changelog.d/82.feature | 0 front/src/components/About.vue | 5 ++ front/src/components/instance/Stats.vue | 104 ++++++++++++++++++++++++ 7 files changed, 259 insertions(+) create mode 100644 api/funkwhale_api/instance/stats.py create mode 100644 api/tests/instance/test_stats.py create mode 100644 changes/changelog.d/82.feature create mode 100644 front/src/components/instance/Stats.vue diff --git a/api/funkwhale_api/instance/stats.py b/api/funkwhale_api/instance/stats.py new file mode 100644 index 000000000..167b333d6 --- /dev/null +++ b/api/funkwhale_api/instance/stats.py @@ -0,0 +1,51 @@ +from django.db.models import Sum + +from funkwhale_api.favorites.models import TrackFavorite +from funkwhale_api.history.models import Listening +from funkwhale_api.music import models +from funkwhale_api.users.models import User + + +def get(): + return { + 'users': get_users(), + 'tracks': get_tracks(), + 'albums': get_albums(), + 'artists': get_artists(), + 'track_favorites': get_track_favorites(), + 'listenings': get_listenings(), + 'music_duration': get_music_duration(), + } + + +def get_users(): + return User.objects.count() + + +def get_listenings(): + return Listening.objects.count() + + +def get_track_favorites(): + return TrackFavorite.objects.count() + + +def get_tracks(): + return models.Track.objects.count() + + +def get_albums(): + return models.Album.objects.count() + + +def get_artists(): + return models.Artist.objects.count() + + +def get_music_duration(): + seconds = models.TrackFile.objects.aggregate( + d=Sum('duration'), + )['d'] + if seconds: + return seconds / 3600 + return 0 diff --git a/api/funkwhale_api/instance/urls.py b/api/funkwhale_api/instance/urls.py index 2f2b46b87..af23e7e08 100644 --- a/api/funkwhale_api/instance/urls.py +++ b/api/funkwhale_api/instance/urls.py @@ -1,7 +1,11 @@ from django.conf.urls import url +from django.views.decorators.cache import cache_page + from . import views urlpatterns = [ url(r'^settings/$', views.InstanceSettings.as_view(), name='settings'), + url(r'^stats/$', + cache_page(60 * 5)(views.InstanceStats.as_view()), name='stats'), ] diff --git a/api/funkwhale_api/instance/views.py b/api/funkwhale_api/instance/views.py index 44ee22873..7f8f393c9 100644 --- a/api/funkwhale_api/instance/views.py +++ b/api/funkwhale_api/instance/views.py @@ -4,6 +4,8 @@ from rest_framework.response import Response from dynamic_preferences.api import serializers from dynamic_preferences.registries import global_preferences_registry +from . import stats + class InstanceSettings(views.APIView): permission_classes = [] @@ -23,3 +25,12 @@ class InstanceSettings(views.APIView): data = serializers.GlobalPreferenceSerializer( api_preferences, many=True).data return Response(data, status=200) + + +class InstanceStats(views.APIView): + permission_classes = [] + authentication_classes = [] + + def get(self, request, *args, **kwargs): + data = stats.get() + return Response(data, status=200) diff --git a/api/tests/instance/test_stats.py b/api/tests/instance/test_stats.py new file mode 100644 index 000000000..6eaad76f7 --- /dev/null +++ b/api/tests/instance/test_stats.py @@ -0,0 +1,84 @@ +from django.urls import reverse + +from funkwhale_api.instance import stats + + +def test_can_get_stats_via_api(db, api_client, mocker): + stats = { + 'foo': 'bar' + } + mocker.patch('funkwhale_api.instance.stats.get', return_value=stats) + url = reverse('api:v1:instance:stats') + response = api_client.get(url) + assert response.data == stats + + +def test_get_users(mocker): + mocker.patch( + 'funkwhale_api.users.models.User.objects.count', return_value=42) + + assert stats.get_users() == 42 + + +def test_get_music_duration(factories): + factories['music.TrackFile'].create_batch(size=5, duration=360) + + # duration is in hours + assert stats.get_music_duration() == 0.5 + + +def test_get_listenings(mocker): + mocker.patch( + 'funkwhale_api.history.models.Listening.objects.count', + return_value=42) + assert stats.get_listenings() == 42 + + +def test_get_track_favorites(mocker): + mocker.patch( + 'funkwhale_api.favorites.models.TrackFavorite.objects.count', + return_value=42) + assert stats.get_track_favorites() == 42 + + +def test_get_tracks(mocker): + mocker.patch( + 'funkwhale_api.music.models.Track.objects.count', + return_value=42) + assert stats.get_tracks() == 42 + + +def test_get_albums(mocker): + mocker.patch( + 'funkwhale_api.music.models.Album.objects.count', + return_value=42) + assert stats.get_albums() == 42 + + +def test_get_artists(mocker): + mocker.patch( + 'funkwhale_api.music.models.Artist.objects.count', + return_value=42) + assert stats.get_artists() == 42 + + +def test_get(mocker): + keys = [ + 'users', + 'tracks', + 'albums', + 'artists', + 'track_favorites', + 'listenings', + 'music_duration', + ] + mocks = [ + mocker.patch.object(stats, 'get_{}'.format(k), return_value=i) + for i, k in enumerate(keys) + ] + + expected = { + k: i for i, k in enumerate(keys) + } + + assert stats.get() == expected diff --git a/changes/changelog.d/82.feature b/changes/changelog.d/82.feature new file mode 100644 index 000000000..e69de29bb diff --git a/front/src/components/About.vue b/front/src/components/About.vue index 01ce6a294..92bafd7af 100644 --- a/front/src/components/About.vue +++ b/front/src/components/About.vue @@ -6,6 +6,7 @@ +
@@ -27,8 +28,12 @@ + + + From e13ac323701a0ee7932419cd9b40afcb17124752 Mon Sep 17 00:00:00 2001 From: Eliot Berriot Date: Tue, 27 Feb 2018 23:01:55 +0100 Subject: [PATCH 13/14] Fixed #80: Added admin interface for radios, track files, favorites... --- api/funkwhale_api/favorites/admin.py | 12 +++++++ api/funkwhale_api/history/admin.py | 4 +++ api/funkwhale_api/music/admin.py | 35 ++++++++++++++++++-- api/funkwhale_api/radios/admin.py | 48 ++++++++++++++++++++++++++++ api/funkwhale_api/requests/admin.py | 16 ++++++++++ changes/changelog.d/80.feature | 1 + 6 files changed, 113 insertions(+), 3 deletions(-) create mode 100644 api/funkwhale_api/favorites/admin.py create mode 100644 api/funkwhale_api/radios/admin.py create mode 100644 api/funkwhale_api/requests/admin.py create mode 100644 changes/changelog.d/80.feature diff --git a/api/funkwhale_api/favorites/admin.py b/api/funkwhale_api/favorites/admin.py new file mode 100644 index 000000000..e8f29fac4 --- /dev/null +++ b/api/funkwhale_api/favorites/admin.py @@ -0,0 +1,12 @@ +from django.contrib import admin + +from . import models + + +@admin.register(models.TrackFavorite) +class TrackFavoriteAdmin(admin.ModelAdmin): + list_display = ['user', 'track', 'creation_date'] + list_select_related = [ + 'user', + 'track' + ] diff --git a/api/funkwhale_api/history/admin.py b/api/funkwhale_api/history/admin.py index f8f587a01..6d0480e73 100644 --- a/api/funkwhale_api/history/admin.py +++ b/api/funkwhale_api/history/admin.py @@ -6,3 +6,7 @@ from . import models class ListeningAdmin(admin.ModelAdmin): list_display = ['track', 'end_date', 'user', 'session_key'] search_fields = ['track__name', 'user__username'] + list_select_related = [ + 'user', + 'track' + ] diff --git a/api/funkwhale_api/music/admin.py b/api/funkwhale_api/music/admin.py index 524b85386..219b40a91 100644 --- a/api/funkwhale_api/music/admin.py +++ b/api/funkwhale_api/music/admin.py @@ -25,13 +25,26 @@ class TrackAdmin(admin.ModelAdmin): @admin.register(models.ImportBatch) class ImportBatchAdmin(admin.ModelAdmin): - list_display = ['creation_date', 'status'] - + list_display = [ + 'submitted_by', + 'creation_date', + 'import_request', + 'status'] + list_select_related = [ + 'submitted_by', + 'import_request', + ] + list_filter = ['status'] + search_fields = [ + 'import_request__name', 'source', 'batch__pk', 'mbid'] @admin.register(models.ImportJob) class ImportJobAdmin(admin.ModelAdmin): list_display = ['source', 'batch', 'track_file', 'status', 'mbid'] - list_select_related = True + list_select_related = [ + 'track_file', + 'batch', + ] search_fields = ['source', 'batch__pk', 'mbid'] list_filter = ['status'] @@ -50,3 +63,19 @@ class LyricsAdmin(admin.ModelAdmin): list_select_related = True search_fields = ['url', 'work__title'] list_filter = ['work__language'] + + +@admin.register(models.TrackFile) +class TrackFileAdmin(admin.ModelAdmin): + list_display = [ + 'track', + 'audio_file', + 'source', + 'duration', + 'mimetype', + ] + list_select_related = [ + 'track' + ] + search_fields = ['source', 'acoustid_track_id'] + list_filter = ['mimetype'] diff --git a/api/funkwhale_api/radios/admin.py b/api/funkwhale_api/radios/admin.py new file mode 100644 index 000000000..6d5abadaf --- /dev/null +++ b/api/funkwhale_api/radios/admin.py @@ -0,0 +1,48 @@ +from django.contrib import admin + +from . import models + + +@admin.register(models.Radio) +class RadioAdmin(admin.ModelAdmin): + list_display = [ + 'user', 'name', 'is_public', 'creation_date', 'config'] + list_select_related = [ + 'user', + ] + list_filter = [ + 'is_public', + ] + search_fields = ['name', 'description'] + + +@admin.register(models.RadioSession) +class RadioSessionAdmin(admin.ModelAdmin): + list_display = [ + 'user', + 'custom_radio', + 'radio_type', + 'creation_date', + 'related_object'] + + list_select_related = [ + 'user', + 'custom_radio' + ] + list_filter = [ + 'radio_type', + ] + + +@admin.register(models.RadioSessionTrack) +class RadioSessionTrackAdmin(admin.ModelAdmin): + list_display = [ + 'id', + 'session', + 'position', + 'track',] + + list_select_related = [ + 'track', + 'session' + ] diff --git a/api/funkwhale_api/requests/admin.py b/api/funkwhale_api/requests/admin.py new file mode 100644 index 000000000..71933eaa9 --- /dev/null +++ b/api/funkwhale_api/requests/admin.py @@ -0,0 +1,16 @@ +from django.contrib import admin + +from . import models + + +@admin.register(models.ImportRequest) +class ImportRequestAdmin(admin.ModelAdmin): + list_display = ['artist_name', 'user', 'status', 'creation_date'] + list_select_related = [ + 'user', + 'track' + ] + list_filter = [ + 'status', + ] + search_fields = ['artist_name', 'comment', 'albums'] diff --git a/changes/changelog.d/80.feature b/changes/changelog.d/80.feature new file mode 100644 index 000000000..16591a910 --- /dev/null +++ b/changes/changelog.d/80.feature @@ -0,0 +1 @@ +Added admin interface for radios, track files, favorites and import requests (#80) From 41404a5988908c12f8f72ecd0eb5e2543e51aacd Mon Sep 17 00:00:00 2001 From: Eliot Berriot Date: Tue, 27 Feb 2018 23:10:23 +0100 Subject: [PATCH 14/14] Version bump and changelog --- CHANGELOG | 24 +++++++++++++++++++ api/funkwhale_api/__init__.py | 2 +- changes/changelog.d/{82.feature => .gitkeep} | 0 changes/changelog.d/80.feature | 1 - changes/changelog.d/81.feature | 1 - changes/changelog.d/83.bugfix | 0 changes/changelog.d/86.bugfix | 1 - changes/changelog.d/87.bugfix | 1 - changes/changelog.d/88.bugfix | 1 - changes/changelog.d/89.bugfix | 1 - changes/changelog.d/changelog.misc | 1 - changes/changelog.d/transaction-import.bugfix | 1 - 12 files changed, 25 insertions(+), 9 deletions(-) rename changes/changelog.d/{82.feature => .gitkeep} (100%) delete mode 100644 changes/changelog.d/80.feature delete mode 100644 changes/changelog.d/81.feature delete mode 100644 changes/changelog.d/83.bugfix delete mode 100644 changes/changelog.d/86.bugfix delete mode 100644 changes/changelog.d/87.bugfix delete mode 100644 changes/changelog.d/88.bugfix delete mode 100644 changes/changelog.d/89.bugfix delete mode 100644 changes/changelog.d/changelog.misc delete mode 100644 changes/changelog.d/transaction-import.bugfix diff --git a/CHANGELOG b/CHANGELOG index 4ce156e06..f2705739c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,6 +3,30 @@ Changelog .. towncrier +0.5.3 (2018-02-27) +------------------ + +Features: + +- Added admin interface for radios, track files, favorites and import requests (#80) +- Added basic instance stats on /about (#82) +- Search now unaccent letters for queries like "The Dø" or "Björk" yielding more results (#81) + + +Bugfixes: + +- Always use username in sidebar (#89) +- Click event outside of player icons (#83) +- Fixed broken import because of missing transaction +- Now always load next radio track on last queue track ended (#87) +- Now exclude tracks without file from radio candidates (#88) +- skip to next track properly on 40X errors (#86) + + +Other: + +- Switched to towncrier for changelog management and compilation + 0.5.2 (2018-02-26) ------------------ diff --git a/api/funkwhale_api/__init__.py b/api/funkwhale_api/__init__.py index 2df7e2034..03e434591 100644 --- a/api/funkwhale_api/__init__.py +++ b/api/funkwhale_api/__init__.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- -__version__ = '0.5.2' +__version__ = '0.5.3' __version_info__ = tuple([int(num) if num.isdigit() else num for num in __version__.replace('-', '.', 1).split('.')]) diff --git a/changes/changelog.d/82.feature b/changes/changelog.d/.gitkeep similarity index 100% rename from changes/changelog.d/82.feature rename to changes/changelog.d/.gitkeep diff --git a/changes/changelog.d/80.feature b/changes/changelog.d/80.feature deleted file mode 100644 index 16591a910..000000000 --- a/changes/changelog.d/80.feature +++ /dev/null @@ -1 +0,0 @@ -Added admin interface for radios, track files, favorites and import requests (#80) diff --git a/changes/changelog.d/81.feature b/changes/changelog.d/81.feature deleted file mode 100644 index 56c44d3cc..000000000 --- a/changes/changelog.d/81.feature +++ /dev/null @@ -1 +0,0 @@ -Search now unaccent letters for queries like "The Dø" or "Björk", yielding more results (#81) diff --git a/changes/changelog.d/83.bugfix b/changes/changelog.d/83.bugfix deleted file mode 100644 index e69de29bb..000000000 diff --git a/changes/changelog.d/86.bugfix b/changes/changelog.d/86.bugfix deleted file mode 100644 index c02a1997c..000000000 --- a/changes/changelog.d/86.bugfix +++ /dev/null @@ -1 +0,0 @@ -skip to next track properly on 40X errors (#86) diff --git a/changes/changelog.d/87.bugfix b/changes/changelog.d/87.bugfix deleted file mode 100644 index 534ddc60a..000000000 --- a/changes/changelog.d/87.bugfix +++ /dev/null @@ -1 +0,0 @@ -Now always load next radio track on last queue track ended (#87) diff --git a/changes/changelog.d/88.bugfix b/changes/changelog.d/88.bugfix deleted file mode 100644 index d2f707b44..000000000 --- a/changes/changelog.d/88.bugfix +++ /dev/null @@ -1 +0,0 @@ -Now exclude tracks without file from radio candidates (#88) diff --git a/changes/changelog.d/89.bugfix b/changes/changelog.d/89.bugfix deleted file mode 100644 index 5338e87db..000000000 --- a/changes/changelog.d/89.bugfix +++ /dev/null @@ -1 +0,0 @@ -Always use username in sidebar (#89) diff --git a/changes/changelog.d/changelog.misc b/changes/changelog.d/changelog.misc deleted file mode 100644 index 46516abc7..000000000 --- a/changes/changelog.d/changelog.misc +++ /dev/null @@ -1 +0,0 @@ -Switched to towncrier for changelog management and compilation diff --git a/changes/changelog.d/transaction-import.bugfix b/changes/changelog.d/transaction-import.bugfix deleted file mode 100644 index 0efe3f000..000000000 --- a/changes/changelog.d/transaction-import.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fixed broken import because of missing transaction