From 9f4f3f6f70598308a47256641db3e829ba682c6f Mon Sep 17 00:00:00 2001 From: Neraste Date: Wed, 13 Apr 2022 21:18:28 +0900 Subject: [PATCH 01/14] Add date_created in player errors serializer --- dakara_server/playlist/serializers.py | 7 ++++++- dakara_server/playlist/tests/test_player_error.py | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/dakara_server/playlist/serializers.py b/dakara_server/playlist/serializers.py index 660c49d5..96058377 100644 --- a/dakara_server/playlist/serializers.py +++ b/dakara_server/playlist/serializers.py @@ -215,7 +215,12 @@ class PlayerErrorSerializer(serializers.ModelSerializer): class Meta: model = PlayerError - fields = ("playlist_entry", "playlist_entry_id", "error_message") + fields = ( + "playlist_entry", + "playlist_entry_id", + "error_message", + "date_created", + ) read_only_fields = ("date_created",) def validate_playlist_entry_id(self, playlist_entry): diff --git a/dakara_server/playlist/tests/test_player_error.py b/dakara_server/playlist/tests/test_player_error.py index 14925c1f..1965523c 100644 --- a/dakara_server/playlist/tests/test_player_error.py +++ b/dakara_server/playlist/tests/test_player_error.py @@ -49,6 +49,7 @@ def test_get_errors_something(self): response.data["results"][0]["playlist_entry"]["id"], self.pe1.id ) self.assertEqual(response.data["results"][0]["error_message"], "dummy error") + self.assertIsNotNone(response.data["results"][0]["date_created"]) def test_get_errors_forbidden(self): """Test to get errors when not authenticated.""" From 6f2745755bd3692b28a2291963d9ab8fe5124569 Mon Sep 17 00:00:00 2001 From: Neraste Date: Wed, 13 Apr 2022 22:30:05 +0900 Subject: [PATCH 02/14] Move player timing recalculation in player status serializer --- dakara_server/playlist/serializers.py | 28 +++++++++++++++++++ .../playlist/tests/test_player_status.py | 22 ++++++++++++++- dakara_server/playlist/views.py | 7 ----- requirements_dev.txt | 1 + 4 files changed, 50 insertions(+), 8 deletions(-) diff --git a/dakara_server/playlist/serializers.py b/dakara_server/playlist/serializers.py index 96058377..99999483 100644 --- a/dakara_server/playlist/serializers.py +++ b/dakara_server/playlist/serializers.py @@ -1,3 +1,6 @@ +from datetime import datetime + +from django.utils import timezone from rest_framework import serializers from library.models import Song @@ -9,6 +12,14 @@ from playlist.models import Karaoke, Player, PlayerError, PlayerToken, PlaylistEntry from users.serializers import UserForPublicSerializer +tz = timezone.get_default_timezone() + + +class SerializerMethodSecondsDurationField( + serializers.SerializerMethodField, SecondsDurationField +): + """Read value with a method, write with a provided field.""" + class PlaylistEntrySerializer(serializers.ModelSerializer): """Playlist entry serializer.""" @@ -129,6 +140,23 @@ class Meta: read_only_fields = ("paused", "in_transition", "date", "playlist_entry") to_update_fields = ("timing",) + @property + def data(self): + self.recalculate_timing() + return super().data + + def recalculate_timing(self): + """Manually update the player timing.""" + player = self.instance + if player is None: + return + + now = datetime.now(tz) + if player.playlist_entry: + if not player.paused and not player.in_transition: + player.timing += now - player.date + player.date = now + def update(self, instance, validated_data): # filter out read only values curated_data = { diff --git a/dakara_server/playlist/tests/test_player_status.py b/dakara_server/playlist/tests/test_player_status.py index 07de7da1..6c6cfadd 100644 --- a/dakara_server/playlist/tests/test_player_status.py +++ b/dakara_server/playlist/tests/test_player_status.py @@ -3,6 +3,7 @@ from django.urls import reverse from django.utils.dateparse import parse_datetime +from freezegun import freeze_time from rest_framework import status from internal.tests.base_test import tz @@ -65,7 +66,26 @@ def test_get_status_in_play_with_timing(self): self.assertFalse(response.data["in_transition"]) self.assertEqual(response.data["timing"], 2) self.assertFalse(response.data["paused"]) - self.assertEqual(parse_datetime(response.data["date"]), player.date) + self.assertGreaterEqual(parse_datetime(response.data["date"]), player.date) + + def test_get_status_in_play_with_timing_delayed(self): + """Test to access the player status when in play with timing with delay.""" + # set the player in play + with freeze_time("1970-01-01 00:01:00"): + self.player_play_next_song(timing=timedelta(seconds=2)) + + with freeze_time("1970-01-01 00:01:02"): + now = datetime.now(tz) + self.authenticate(self.user) + + # assert the status of the player + response = self.client.get(self.url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data["playlist_entry"]["id"], self.pe1.id) + self.assertFalse(response.data["in_transition"]) + self.assertEqual(response.data["timing"], 4) + self.assertFalse(response.data["paused"]) + self.assertEqual(parse_datetime(response.data["date"]), now) def test_get_status_in_pause_with_timing(self): """Test to access the player status when in pause with timing.""" diff --git a/dakara_server/playlist/views.py b/dakara_server/playlist/views.py index aaae52d6..3f4dd361 100644 --- a/dakara_server/playlist/views.py +++ b/dakara_server/playlist/views.py @@ -237,13 +237,6 @@ def get(self, request, *args, **kwargs): # Get player player, _ = models.Player.cache.get_or_create(karaoke=karaoke) - # manually update the player timing - now = datetime.now(tz) - if player.playlist_entry: - if not player.paused and not player.in_transition: - player.timing += now - player.date - player.date = now - # Get player errors player_errors_pool = models.PlayerError.objects.all() diff --git a/requirements_dev.txt b/requirements_dev.txt index 62f05f2d..1897aaca 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,6 +1,7 @@ black>=22.3.0,<22.4.0 codecov>=2.1.12,<2.2.0 flake8>=4.0.1,<4.1.0 +freezegun>=1.2.1,<1.3.0 isort>=5.10.1,<5.11.0 pre-commit>=2.18.1,<2.19.0 pytest-asyncio>=0.18.3,<0.19.0 From d920a83bcb3d8fe6dfb012608cfbac7443a962ef Mon Sep 17 00:00:00 2001 From: Neraste Date: Wed, 13 Apr 2022 22:52:43 +0900 Subject: [PATCH 03/14] Use freezegun to freeze time --- dakara_server/playlist/serializers.py | 8 +- dakara_server/playlist/tests/test_digest.py | 36 +++------ .../playlist/tests/test_player_status.py | 73 ++++++------------- .../playlist/tests/test_playlist_entry.py | 39 +++------- 4 files changed, 42 insertions(+), 114 deletions(-) diff --git a/dakara_server/playlist/serializers.py b/dakara_server/playlist/serializers.py index 99999483..26c830c3 100644 --- a/dakara_server/playlist/serializers.py +++ b/dakara_server/playlist/serializers.py @@ -15,12 +15,6 @@ tz = timezone.get_default_timezone() -class SerializerMethodSecondsDurationField( - serializers.SerializerMethodField, SecondsDurationField -): - """Read value with a method, write with a provided field.""" - - class PlaylistEntrySerializer(serializers.ModelSerializer): """Playlist entry serializer.""" @@ -302,7 +296,7 @@ class Meta: class DigestSerializer(serializers.Serializer): """Combine player info and kara status.""" - player_status = PlayerStatusSerializer() # TODO test this + player_status = PlayerStatusSerializer() player_errors = PlayerErrorSerializer(many=True) karaoke = KaraokeSerializer() diff --git a/dakara_server/playlist/tests/test_digest.py b/dakara_server/playlist/tests/test_digest.py index 0c278a5c..807d221d 100644 --- a/dakara_server/playlist/tests/test_digest.py +++ b/dakara_server/playlist/tests/test_digest.py @@ -1,10 +1,7 @@ -from datetime import datetime, timedelta -from unittest.mock import patch - from django.urls import reverse +from freezegun import freeze_time from rest_framework import status -from internal.tests.base_test import tz from playlist.models import Karaoke, PlayerError from playlist.tests.base_test import PlaylistAPITestCase @@ -69,39 +66,24 @@ def test_get_playing(self): self.assertTrue(response.data["karaoke"]["can_add_to_playlist"]) self.assertTrue(response.data["karaoke"]["player_play_next_song"]) - @patch( - "playlist.views.datetime", - side_effect=lambda *args, **kwargs: datetime(*args, **kwargs), - ) - @patch( - "playlist.models.datetime", - side_effect=lambda *args, **kwargs: datetime(*args, **kwargs), - ) - def test_get_playing_transition( - self, mocked_datetime_models, mocked_datetime_views - ): + @freeze_time("1970-01-01 00:01:00") + def test_get_playing_transition(self): """Get the digest when the player is playing a transition. The player should be playing but with a null timing. """ self.authenticate(self.user) - # patch the now method - now = datetime.now(tz) - mocked_datetime_models.now.return_value = now - mocked_datetime_views.now.return_value = now + timedelta(seconds=1) - # start playing self.player_play_next_song(in_transition=True) - # get the digest - response = self.client.get(self.url) - self.assertEqual(response.status_code, status.HTTP_200_OK) - - # assert the response - self.assertEqual(response.data["player_status"]["timing"], 0) + with freeze_time("1970-01-01 00:01:01"): + # get the digest + response = self.client.get(self.url) + self.assertEqual(response.status_code, status.HTTP_200_OK) - # self.assertEqual(mocked_datetime.now.call_count, 2) + # assert the response + self.assertEqual(response.data["player_status"]["timing"], 0) def test_get_errors(self): """Get the digest when there are errors. diff --git a/dakara_server/playlist/tests/test_player_status.py b/dakara_server/playlist/tests/test_player_status.py index 6c6cfadd..dcca2e61 100644 --- a/dakara_server/playlist/tests/test_player_status.py +++ b/dakara_server/playlist/tests/test_player_status.py @@ -52,6 +52,7 @@ def test_get_status_in_transition(self): self.assertFalse(response.data["paused"]) self.assertEqual(parse_datetime(response.data["date"]), player.date) + @freeze_time("1970-01-01 00:01:00") def test_get_status_in_play_with_timing(self): """Test to access the player status when in play with timing.""" self.authenticate(self.user) @@ -66,13 +67,13 @@ def test_get_status_in_play_with_timing(self): self.assertFalse(response.data["in_transition"]) self.assertEqual(response.data["timing"], 2) self.assertFalse(response.data["paused"]) - self.assertGreaterEqual(parse_datetime(response.data["date"]), player.date) + self.assertEqual(parse_datetime(response.data["date"]), player.date) + @freeze_time("1970-01-01 00:01:00") def test_get_status_in_play_with_timing_delayed(self): """Test to access the player status when in play with timing with delay.""" # set the player in play - with freeze_time("1970-01-01 00:01:00"): - self.player_play_next_song(timing=timedelta(seconds=2)) + self.player_play_next_song(timing=timedelta(seconds=2)) with freeze_time("1970-01-01 00:01:02"): now = datetime.now(tz) @@ -108,16 +109,10 @@ def test_get_status_forbidden(self): response = self.client.get(self.url) self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + @freeze_time("1970-01-01 00:01:00") @patch("playlist.views.send_to_channel") - @patch( - "django.db.models.fields.timezone.now", - ) - def test_put_status_started_transition(self, mocked_now, mocked_send_to_channel): + def test_put_status_started_transition(self, mocked_send_to_channel): """Test player started transition.""" - # patch the now method - now = datetime.now(tz) - mocked_now.return_value = now - # perform the request response = self.client.put( self.url, @@ -139,6 +134,7 @@ def test_put_status_started_transition(self, mocked_now, mocked_send_to_channel) # assert the result karaoke = Karaoke.objects.get_object() player, _ = Player.cache.get_or_create(karaoke=karaoke) + now = datetime.now(tz) self.assertEqual(player.playlist_entry, self.pe1) self.assertFalse(player.paused) self.assertEqual(player.timing, timedelta(0)) @@ -178,16 +174,10 @@ def test_put_status_started_transition_with_timing(self): # assert extra fields have not been saved self.assertFalse(hasattr(player, "event")) + @freeze_time("1970-01-01 00:01:00") @patch("playlist.views.send_to_channel") - @patch( - "django.db.models.fields.timezone.now", - ) - def test_put_status_started_song(self, mocked_now, mocked_send_to_channel): + def test_put_status_started_song(self, mocked_send_to_channel): """Test player finished transition.""" - # patch the now method - now = datetime.now(tz) - mocked_now.return_value = now - # set the player already in transition self.player_play_next_song(in_transition=True) @@ -212,6 +202,7 @@ def test_put_status_started_song(self, mocked_now, mocked_send_to_channel): # assert the result karaoke = Karaoke.objects.get_object() player, _ = Player.cache.get_or_create(karaoke=karaoke) + now = datetime.now(tz) self.assertEqual(player.playlist_entry, self.pe1) self.assertFalse(player.paused) self.assertEqual(player.timing, timedelta(0)) @@ -223,16 +214,10 @@ def test_put_status_started_song(self, mocked_now, mocked_send_to_channel): # "playlist.front", "send_player_status", {"player": player} # ) + @freeze_time("1970-01-01 00:01:00") @patch("playlist.views.send_to_channel") - @patch( - "django.db.models.fields.timezone.now", - ) - def test_put_status_resumed(self, mocked_now, mocked_send_to_channel): + def test_put_status_resumed(self, mocked_send_to_channel): """Test event played resumed.""" - # patch the now method - now = datetime.now(tz) - mocked_now.return_value = now - # set the player already in play self.player_play_next_song(timing=timedelta(seconds=1), paused=True) @@ -253,6 +238,7 @@ def test_put_status_resumed(self, mocked_now, mocked_send_to_channel): # assert the result karaoke = Karaoke.objects.get_object() player, _ = Player.cache.get_or_create(karaoke=karaoke) + now = datetime.now(tz) self.assertEqual(player.playlist_entry, self.pe1) self.assertFalse(player.paused) self.assertEqual(player.timing, timedelta(seconds=2)) @@ -264,16 +250,10 @@ def test_put_status_resumed(self, mocked_now, mocked_send_to_channel): # "playlist.front", "send_player_status", {"player": player} # ) + @freeze_time("1970-01-01 00:01:00") @patch("playlist.views.send_to_channel") - @patch( - "django.db.models.fields.timezone.now", - ) - def test_put_status_updated_timing(self, mocked_now, mocked_send_to_channel): + def test_put_status_updated_timing(self, mocked_send_to_channel): """Test event udpated timing.""" - # patch the now method - now = datetime.now(tz) - mocked_now.return_value = now - # set the player already in play self.player_play_next_song(timing=timedelta(seconds=1)) @@ -296,6 +276,7 @@ def test_put_status_updated_timing(self, mocked_now, mocked_send_to_channel): # assert the result karaoke = Karaoke.objects.get_object() player, _ = Player.cache.get_or_create(karaoke=karaoke) + now = datetime.now(tz) self.assertEqual(player.playlist_entry, self.pe1) self.assertFalse(player.paused) self.assertEqual(player.timing, timedelta(seconds=0)) @@ -307,16 +288,10 @@ def test_put_status_updated_timing(self, mocked_now, mocked_send_to_channel): # "playlist.front", "send_player_status", {"player": player} # ) + @freeze_time("1970-01-01 00:01:00") @patch("playlist.views.send_to_channel") - @patch( - "django.db.models.fields.timezone.now", - ) - def test_put_status_paused(self, mocked_now, mocked_send_to_channel): + def test_put_status_paused(self, mocked_send_to_channel): """Test event paused player.""" - # patch the now method - now = datetime.now(tz) - mocked_now.return_value = now - # set the player in play self.player_play_next_song(timing=timedelta(seconds=1)) @@ -337,6 +312,7 @@ def test_put_status_paused(self, mocked_now, mocked_send_to_channel): # assert the result karaoke = Karaoke.objects.get_object() player, _ = Player.cache.get_or_create(karaoke=karaoke) + now = datetime.now(tz) self.assertEqual(player.playlist_entry, self.pe1) self.assertTrue(player.paused) self.assertEqual(player.timing, timedelta(seconds=2)) @@ -384,16 +360,10 @@ def test_put_status_finished(self, mocked_send_to_channel): # assert an event has been sent to the device mocked_send_to_channel.assert_called_with(ANY, "handle_next") + @freeze_time("1970-01-01 00:01:00") @patch("playlist.views.send_to_channel") - @patch( - "django.db.models.fields.timezone.now", - ) - def test_put_status_could_not_play(self, mocked_now, mocked_send_to_channel): + def test_put_status_could_not_play(self, mocked_send_to_channel): """Test event could not play.""" - # patch the now method - now = datetime.now(tz) - mocked_now.return_value = now - # perform the request response = self.client.put( self.url, @@ -411,6 +381,7 @@ def test_put_status_could_not_play(self, mocked_now, mocked_send_to_channel): # assert the result karaoke = Karaoke.objects.get_object() player, _ = Player.cache.get_or_create(karaoke=karaoke) + now = datetime.now(tz) self.assertIsNone(player.playlist_entry) self.assertFalse(player.paused) self.assertEqual(player.timing, timedelta(0)) diff --git a/dakara_server/playlist/tests/test_playlist_entry.py b/dakara_server/playlist/tests/test_playlist_entry.py index b6175ec6..dcdc01c3 100644 --- a/dakara_server/playlist/tests/test_playlist_entry.py +++ b/dakara_server/playlist/tests/test_playlist_entry.py @@ -3,6 +3,7 @@ from django.urls import reverse from django.utils.dateparse import parse_datetime +from freezegun import freeze_time from rest_framework import status from internal.tests.base_test import UserModel, tz @@ -16,16 +17,9 @@ class PlaylistEntryListViewTestCase(PlaylistAPITestCase): def setUp(self): self.create_test_data() - @patch( - "playlist.views.datetime", - side_effect=lambda *args, **kwargs: datetime(*args, **kwargs), - ) - def test_get_playlist_entries_list(self, mocked_datetime): + @freeze_time("1970-01-01 00:01:00") + def test_get_playlist_entries_list(self): """Test to verify playlist entries list.""" - # patch the now method - now = datetime.now(tz) - mocked_datetime.now.return_value = now - # Login as simple user self.authenticate(self.user) @@ -42,6 +36,7 @@ def test_get_playlist_entries_list(self, mocked_datetime): self.check_playlist_entry_json(pe2, self.pe2) # check the date of the end of the playlist + now = datetime.now(tz) self.assertEqual( parse_datetime(response.data["date_end"]), now + self.pe1.song.duration + self.pe2.song.duration, @@ -51,20 +46,13 @@ def test_get_playlist_entries_list(self, mocked_datetime): self.assertEqual(parse_datetime(pe1["date_play"]), now) self.assertEqual(parse_datetime(pe2["date_play"]), now + self.pe1.song.duration) - @patch( - "playlist.views.datetime", - side_effect=lambda *args, **kwargs: datetime(*args, **kwargs), - ) - def test_get_playlist_entries_list_while_playing(self, mocked_datetime): + @freeze_time("1970-01-01 00:01:00") + def test_get_playlist_entries_list_while_playing(self): """Test to verify playlist entries play dates while playing. The player is currently in the middle of the song, play dates should take account of the remaining time of the player. """ - # patch the now method - now = datetime.now(tz) - mocked_datetime.now.return_value = now - # set the player karaoke = Karaoke.objects.get_object() player, _ = Player.cache.get_or_create(karaoke=karaoke) @@ -74,6 +62,7 @@ def test_get_playlist_entries_list_while_playing(self, mocked_datetime): player.save() # set the entry + now = datetime.now(tz) self.pe1.date_played = now - play_duration self.pe1.save() @@ -325,26 +314,18 @@ def test_post_create_playlist_entry_date_stop_success_admin(self): self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(PlaylistEntry.objects.count(), 5) - @patch( - "playlist.views.datetime", - side_effect=lambda *args, **kwargs: datetime(*args, **kwargs), - ) - def test_post_create_playlist_entry_date_stop_forbidden_playlist_playing( - self, mocked_datetime - ): + @freeze_time("1970-01-01 00:01:00") + def test_post_create_playlist_entry_date_stop_forbidden_playlist_playing(self): """Test user cannot add song to playlist after its date stop. Test that only short enough songs can be added. Test when the player is playing. """ - # patch the now method - now = datetime.now(tz) - mocked_datetime.now.return_value = now - # set the player self.player_play_next_song(timing=timedelta(seconds=2)) # set kara stop such as to allow song1 to be added and not song2 + now = datetime.now(tz) date_stop = now + timedelta(seconds=20) karaoke = Karaoke.objects.get_object() karaoke.date_stop = date_stop From b079e568d8527d23d66ffe6752e7db97d9b21f95 Mon Sep 17 00:00:00 2001 From: Neraste Date: Thu, 14 Apr 2022 00:04:12 +0900 Subject: [PATCH 04/14] Display simpler player errors for the digest --- dakara_server/library/serializers.py | 11 +++++- dakara_server/playlist/serializers.py | 38 ++++++++++++++++++--- dakara_server/playlist/tests/test_digest.py | 7 ++++ 3 files changed, 50 insertions(+), 6 deletions(-) diff --git a/dakara_server/library/serializers.py b/dakara_server/library/serializers.py index ebe86a93..e7812ed7 100644 --- a/dakara_server/library/serializers.py +++ b/dakara_server/library/serializers.py @@ -188,7 +188,7 @@ class Meta: "work_type", "song_count", ) - read_only_fiels = ("id", "song_count") + read_only_fields = ("id", "song_count") @staticmethod def get_song_count(work): @@ -432,6 +432,15 @@ class Meta: read_only_fields = ("id", "filename", "directory") +class SongForDigestSerializer(serializers.ModelSerializer): + """Song serializer for playlist digest info.""" + + class Meta: + model = Song + fields = ("id", "title") + read_only_fields = ("id", "title") + + class WorkForFeederSerializer(serializers.ModelSerializer): """Work serializer for the feeder.""" diff --git a/dakara_server/playlist/serializers.py b/dakara_server/playlist/serializers.py index 26c830c3..88cb90a3 100644 --- a/dakara_server/playlist/serializers.py +++ b/dakara_server/playlist/serializers.py @@ -6,6 +6,7 @@ from library.models import Song from library.serializers import ( SecondsDurationField, + SongForDigestSerializer, SongForPlayerSerializer, SongSerializer, ) @@ -74,6 +75,13 @@ class Meta(PlaylistEntrySerializer.Meta): ) +class PlaylistEntriesWithDateEndSerializer(serializers.Serializer): + """Playlist entries with playlist end date.""" + + results = PlaylistEntryWithDatePlaySerializer(many=True, read_only=True) + date_end = serializers.DateTimeField(read_only=True) + + class PlaylistPlayedEntryWithDatePlayedSerializer(PlaylistEntrySerializer): """Playlist entry serializer. @@ -92,11 +100,15 @@ class Meta(PlaylistEntrySerializer.Meta): read_only_fields = ("date_created", "date_played") -class PlaylistEntriesWithDateEndSerializer(serializers.Serializer): - """Playlist entries with playlist end date.""" +class PlaylistPlayedEntryForDigestSerializer(serializers.ModelSerializer): + """Played playlist entry serializer for playlist digest info.""" - results = PlaylistEntryWithDatePlaySerializer(many=True, read_only=True) - date_end = serializers.DateTimeField(read_only=True) + song = SongForDigestSerializer(many=False, read_only=True) + + class Meta: + model = PlaylistEntry + fields = ("id", "song") + read_only_fields = ("id", "song") class PlayerStatusSerializer(serializers.ModelSerializer): @@ -262,6 +274,22 @@ def validate_playlist_entry_id(self, playlist_entry): return playlist_entry +class PlayerErrorForDigestSerializer(serializers.ModelSerializer): + """Player error serializers for playlist digest info.""" + + playlist_entry = PlaylistPlayedEntryForDigestSerializer(many=False, read_only=True) + + class Meta: + model = PlayerError + fields = ( + "id", + "playlist_entry", + "error_message", + "date_created", + ) + read_only_fields = ("id", "playlist_entry", "error_message", "date_created") + + class PlayerCommandSerializer(serializers.Serializer): """Player command serializer.""" @@ -297,7 +325,7 @@ class DigestSerializer(serializers.Serializer): """Combine player info and kara status.""" player_status = PlayerStatusSerializer() - player_errors = PlayerErrorSerializer(many=True) + player_errors = PlayerErrorForDigestSerializer(many=True) karaoke = KaraokeSerializer() diff --git a/dakara_server/playlist/tests/test_digest.py b/dakara_server/playlist/tests/test_digest.py index 807d221d..09208a44 100644 --- a/dakara_server/playlist/tests/test_digest.py +++ b/dakara_server/playlist/tests/test_digest.py @@ -116,6 +116,13 @@ def test_get_errors(self): response.data["player_errors"][0]["playlist_entry"]["id"], errors[0].playlist_entry.id, ) + self.assertEqual( + response.data["player_errors"][0]["playlist_entry"]["song"]["id"], + errors[0].playlist_entry.song.id, + ) + self.assertNotIn( + "artists", response.data["player_errors"][0]["playlist_entry"]["song"] + ) self.assertEqual( response.data["player_errors"][1]["playlist_entry"]["id"], errors[1].playlist_entry.id, From 0934d37557d4e88cd7494ba9917f92842276350f Mon Sep 17 00:00:00 2001 From: Neraste Date: Thu, 14 Apr 2022 02:22:04 +0900 Subject: [PATCH 05/14] Rename date_played in date_play for playlist entries Uniform the behavior of played and queuing playlist entries. --- dakara_server/playlist/consumers.py | 4 +- .../0016_playlist_entry_date_play.py | 18 +++++ dakara_server/playlist/models.py | 8 +- dakara_server/playlist/serializers.py | 77 ++++++------------- dakara_server/playlist/tests/base_test.py | 8 +- dakara_server/playlist/tests/test_device.py | 2 +- dakara_server/playlist/tests/test_models.py | 24 +++--- .../playlist/tests/test_player_error.py | 10 +-- .../playlist/tests/test_playlist_entry.py | 2 +- dakara_server/playlist/views.py | 2 +- 10 files changed, 70 insertions(+), 85 deletions(-) create mode 100644 dakara_server/playlist/migrations/0016_playlist_entry_date_play.py diff --git a/dakara_server/playlist/consumers.py b/dakara_server/playlist/consumers.py index b865a041..a0599d6c 100644 --- a/dakara_server/playlist/consumers.py +++ b/dakara_server/playlist/consumers.py @@ -118,7 +118,7 @@ def connect(self): # reset current playing playlist entry if any current_playlist_entry = models.PlaylistEntry.objects.get_playing() if current_playlist_entry is not None: - current_playlist_entry.date_played = None + current_playlist_entry.date_play = None current_playlist_entry.save() # register the channel in database @@ -136,7 +136,7 @@ def disconnect(self, close_code): # reset the current playing song if any entry = models.PlaylistEntry.objects.get_playing() if entry: - entry.date_played = None + entry.date_play = None entry.save() # reset the player diff --git a/dakara_server/playlist/migrations/0016_playlist_entry_date_play.py b/dakara_server/playlist/migrations/0016_playlist_entry_date_play.py new file mode 100644 index 00000000..2ec08a8e --- /dev/null +++ b/dakara_server/playlist/migrations/0016_playlist_entry_date_play.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.12 on 2022-04-13 17:08 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("playlist", "0015_id_fields"), + ] + + operations = [ + migrations.RenameField( + model_name="playlistentry", + old_name="date_played", + new_name="date_play", + ), + ] diff --git a/dakara_server/playlist/models.py b/dakara_server/playlist/models.py index 08fac7ef..8963240e 100644 --- a/dakara_server/playlist/models.py +++ b/dakara_server/playlist/models.py @@ -18,7 +18,7 @@ class PlaylistManager(OrderedModelManager): def get_playing(self): """Get the current playlist entry.""" - playlist = self.filter(was_played=False, date_played__isnull=False) + playlist = self.filter(was_played=False, date_play__isnull=False) if not playlist: return None @@ -36,7 +36,7 @@ def get_playing(self): def get_playlist(self): """Get the playlist of ongoing entries.""" queryset = self.exclude( - models.Q(was_played=True) | models.Q(date_played__isnull=False) + models.Q(was_played=True) | models.Q(date_play__isnull=False) ) return queryset @@ -83,7 +83,7 @@ class PlaylistEntry(OrderedModel): date_created = models.DateTimeField(auto_now_add=True) owner = models.ForeignKey(DakaraUser, null=False, on_delete=models.CASCADE) was_played = models.BooleanField(default=False, null=False) - date_played = models.DateTimeField(null=True) + date_play = models.DateTimeField(null=True) class Meta(OrderedModel.Meta): pass @@ -98,7 +98,7 @@ def set_playing(self): raise RuntimeError("A playlist entry is currently in play") # set the playlist entry - self.date_played = datetime.now(tz) + self.date_play = datetime.now(tz) self.save() def set_finished(self): diff --git a/dakara_server/playlist/serializers.py b/dakara_server/playlist/serializers.py index 88cb90a3..1d3b2980 100644 --- a/dakara_server/playlist/serializers.py +++ b/dakara_server/playlist/serializers.py @@ -33,8 +33,16 @@ class PlaylistEntrySerializer(serializers.ModelSerializer): class Meta: model = PlaylistEntry - fields = ("id", "date_created", "owner", "song", "song_id", "use_instrumental") - read_only_fields = ("date_created",) + fields = ( + "id", + "date_created", + "owner", + "song", + "song_id", + "use_instrumental", + "date_play", + ) + read_only_fields = ("date_created", "date_play") def validate(self, data): if data.get("use_instrumental") and not data["song"].has_instrumental: @@ -55,52 +63,7 @@ class Meta: read_only_fields = ("date_created",) -class PlaylistEntryWithDatePlaySerializer(PlaylistEntrySerializer): - """Playlist entry serializer. - - Reserved for song that will be played. - """ - - # date of play in the future, added manually to the PlaylistEntry object - date_play = serializers.DateTimeField(read_only=True) - - class Meta(PlaylistEntrySerializer.Meta): - fields = ( - "id", - "date_created", - "date_play", - "owner", - "song", - "use_instrumental", - ) - - -class PlaylistEntriesWithDateEndSerializer(serializers.Serializer): - """Playlist entries with playlist end date.""" - - results = PlaylistEntryWithDatePlaySerializer(many=True, read_only=True) - date_end = serializers.DateTimeField(read_only=True) - - -class PlaylistPlayedEntryWithDatePlayedSerializer(PlaylistEntrySerializer): - """Playlist entry serializer. - - Reserved for song that were played. - """ - - class Meta(PlaylistEntrySerializer.Meta): - fields = ( - "id", - "date_created", - "date_played", - "owner", - "song", - "use_instrumental", - ) - read_only_fields = ("date_created", "date_played") - - -class PlaylistPlayedEntryForDigestSerializer(serializers.ModelSerializer): +class PlaylistEntryForDigestSerializer(serializers.ModelSerializer): """Played playlist entry serializer for playlist digest info.""" song = SongForDigestSerializer(many=False, read_only=True) @@ -111,11 +74,18 @@ class Meta: read_only_fields = ("id", "song") +class PlaylistEntriesWithDateEndSerializer(serializers.Serializer): + """Playlist entries with playlist end date.""" + + results = PlaylistEntrySerializer(many=True, read_only=True) + date_end = serializers.DateTimeField(read_only=True) + + class PlayerStatusSerializer(serializers.ModelSerializer): """Player status serializer.""" # Read only fields for front - playlist_entry = PlaylistPlayedEntryWithDatePlayedSerializer( + playlist_entry = PlaylistEntrySerializer( many=False, read_only=True, allow_null=True ) @@ -238,9 +208,7 @@ class PlayerErrorSerializer(serializers.ModelSerializer): """Player errors.""" # get related entry field - playlist_entry = PlaylistPlayedEntryWithDatePlayedSerializer( - many=False, read_only=True - ) + playlist_entry = PlaylistEntrySerializer(many=False, read_only=True) # set related entry field playlist_entry_id = serializers.PrimaryKeyRelatedField( @@ -277,7 +245,7 @@ def validate_playlist_entry_id(self, playlist_entry): class PlayerErrorForDigestSerializer(serializers.ModelSerializer): """Player error serializers for playlist digest info.""" - playlist_entry = PlaylistPlayedEntryForDigestSerializer(many=False, read_only=True) + playlist_entry = PlaylistEntryForDigestSerializer(many=False, read_only=True) class Meta: model = PlayerError @@ -325,8 +293,9 @@ class DigestSerializer(serializers.Serializer): """Combine player info and kara status.""" player_status = PlayerStatusSerializer() - player_errors = PlayerErrorForDigestSerializer(many=True) karaoke = KaraokeSerializer() + player_errors = PlayerErrorForDigestSerializer(many=True) + # playlist_entries = class PlaylistReorderSerializer(serializers.Serializer): diff --git a/dakara_server/playlist/tests/base_test.py b/dakara_server/playlist/tests/base_test.py index 072a0f3e..d52d867f 100644 --- a/dakara_server/playlist/tests/base_test.py +++ b/dakara_server/playlist/tests/base_test.py @@ -55,7 +55,7 @@ def create_test_data(self): song=self.song2, owner=self.manager, was_played=True, - date_played=datetime.now(tz), + date_play=datetime.now(tz), ) self.pe3.save() @@ -63,7 +63,7 @@ def create_test_data(self): song=self.song1, owner=self.user, was_played=True, - date_played=datetime.now(tz) - timedelta(minutes=15), + date_play=datetime.now(tz) - timedelta(minutes=15), ) self.pe4.save() @@ -124,9 +124,7 @@ def check_playlist_entry_json(self, json, expected_entry): def check_playlist_played_entry_json(self, json, expected_entry): """Method to check a representation against expected playlist played entry.""" self.check_playlist_entry_json(json, expected_entry) - self.assertEqual( - parse_datetime(json["date_played"]), expected_entry.date_played - ) + self.assertEqual(parse_datetime(json["date_play"]), expected_entry.date_play) def get_player_token(self): """Create and give player token.""" diff --git a/dakara_server/playlist/tests/test_device.py b/dakara_server/playlist/tests/test_device.py index 2ccf17dc..b7285080 100644 --- a/dakara_server/playlist/tests/test_device.py +++ b/dakara_server/playlist/tests/test_device.py @@ -307,7 +307,7 @@ async def test_send_playlist_entry( player = await get_player() # pre assert - assert playlist_provider.pe1.date_played is None + assert playlist_provider.pe1.date_play is None karaoke = await get_karaoke() diff --git a/dakara_server/playlist/tests/test_models.py b/dakara_server/playlist/tests/test_models.py index 94372ce6..1c488a42 100644 --- a/dakara_server/playlist/tests/test_models.py +++ b/dakara_server/playlist/tests/test_models.py @@ -18,7 +18,7 @@ def test_get_playing_success(self, playlist_provider): assert models.PlaylistEntry.objects.get_playing() is None # set playlist entry 1 is playing - playlist_provider.pe1.date_played = datetime.now(tz) + playlist_provider.pe1.date_play = datetime.now(tz) playlist_provider.pe1.save() # assert playlist entry 1 is now playing @@ -44,7 +44,7 @@ def test_get_playing_abnormal(self, playlist_provider): assert models.PlaylistEntry.objects.get_playing() is None # set playlist entry 1 is playing after setting it played - playlist_provider.pe1.date_played = datetime.now(tz) + playlist_provider.pe1.date_play = datetime.now(tz) playlist_provider.pe1.save() # assert still no entry is playing @@ -56,9 +56,9 @@ def test_get_playing_fail(self, playlist_provider): assert models.PlaylistEntry.objects.get_playing() is None # set playlist entries 1 and 2 are playing - playlist_provider.pe1.date_played = datetime.now(tz) + playlist_provider.pe1.date_play = datetime.now(tz) playlist_provider.pe1.save() - playlist_provider.pe2.date_played = datetime.now(tz) + playlist_provider.pe2.date_play = datetime.now(tz) playlist_provider.pe2.save() # assert the method raises an exception @@ -79,7 +79,7 @@ def test_get_playlist_normal(self, playlist_provider): assert playlist[1] == playlist_provider.pe2 # set playlist entry 1 is playing - playlist_provider.pe1.date_played = datetime.now(tz) + playlist_provider.pe1.date_play = datetime.now(tz) playlist_provider.pe1.save() # assert there is one entry in playlist @@ -114,7 +114,7 @@ def test_get_playlist_abnormal(self, playlist_provider): assert playlist[0] == playlist_provider.pe2 # set playlist entry 1 is playing after setting it played - playlist_provider.pe1.date_played = datetime.now(tz) + playlist_provider.pe1.date_play = datetime.now(tz) playlist_provider.pe1.save() # assert there is still one entry in playlist @@ -131,7 +131,7 @@ def test_get_playlist_played_normal(self, playlist_provider): assert playlist_played[1] == playlist_provider.pe4 # set playlist entry 1 is playing - playlist_provider.pe1.date_played = datetime.now(tz) + playlist_provider.pe1.date_play = datetime.now(tz) playlist_provider.pe1.save() # assert there are still 2 entries played @@ -171,7 +171,7 @@ def test_get_playlist_played_abnormal(self, playlist_provider): assert playlist_played[2] == playlist_provider.pe4 # set playlist entry 1 is playing after setting it played - playlist_provider.pe1.date_played = datetime.now(tz) + playlist_provider.pe1.date_play = datetime.now(tz) playlist_provider.pe1.save() # assert there are still 3 entries played @@ -187,7 +187,7 @@ def test_get_next_normal(self, playlist_provider): assert models.PlaylistEntry.objects.get_next() == playlist_provider.pe1 # set playlist entry 1 is playing - playlist_provider.pe1.date_played = datetime.now(tz) + playlist_provider.pe1.date_play = datetime.now(tz) playlist_provider.pe1.save() # assert the next entry is still playlist entry 1 @@ -201,7 +201,7 @@ def test_get_next_normal(self, playlist_provider): assert models.PlaylistEntry.objects.get_next() == playlist_provider.pe2 # set playlist entry 2 played - playlist_provider.pe2.date_played = datetime.now(tz) + playlist_provider.pe2.date_play = datetime.now(tz) playlist_provider.pe2.was_played = True playlist_provider.pe2.save() @@ -221,7 +221,7 @@ def test_get_next_abnormal(self, playlist_provider): assert models.PlaylistEntry.objects.get_next() == playlist_provider.pe2 # set playlist entry 1 is playing after setting it played - playlist_provider.pe1.date_played = datetime.now(tz) + playlist_provider.pe1.date_play = datetime.now(tz) playlist_provider.pe1.save() # assert the next entry is still playlist entry 2 @@ -236,7 +236,7 @@ def test_get_next_relative(self, playlist_provider): ) # set playlist entry 1 is playing - playlist_provider.pe1.date_played = datetime.now(tz) + playlist_provider.pe1.date_play = datetime.now(tz) playlist_provider.pe1.save() # assert the entry after playlist entry 1 is still playlist entry 2 diff --git a/dakara_server/playlist/tests/test_player_error.py b/dakara_server/playlist/tests/test_player_error.py index 1965523c..641ecb00 100644 --- a/dakara_server/playlist/tests/test_player_error.py +++ b/dakara_server/playlist/tests/test_player_error.py @@ -63,7 +63,7 @@ def test_post_error_success(self, mocked_send_to_channel): self.assertEqual(PlayerError.objects.count(), 0) # start playing - self.pe1.date_played = datetime.now(tz) + self.pe1.date_play = datetime.now(tz) self.pe1.save() # request to create an error @@ -91,7 +91,7 @@ def test_post_error_failed_wrong_playlist_entry(self): self.assertEqual(PlayerError.objects.count(), 0) # start playing - self.pe1.date_played = datetime.now(tz) + self.pe1.date_play = datetime.now(tz) self.pe1.save() # request to create an error @@ -130,7 +130,7 @@ def test_post_error_playlist_entry_pending(self): self.assertEqual(PlayerError.objects.count(), 0) # set first playlit entry played - self.pe1.date_played = datetime.now(tz) + self.pe1.date_play = datetime.now(tz) self.pe1.was_played = True self.pe1.save() @@ -148,7 +148,7 @@ def test_post_error_playlist_entry_pending(self): def test_post_error_forbidden_not_authenticated(self): """Test to create an error when not loged in.""" # start playing - self.pe1.date_played = datetime.now(tz) + self.pe1.date_play = datetime.now(tz) self.pe1.save() # request to create an error @@ -161,7 +161,7 @@ def test_post_error_forbidden_not_authenticated(self): def test_post_error_forbidden_not_player(self): """Test to create an error when not loged in as player.""" # start playing - self.pe1.date_played = datetime.now(tz) + self.pe1.date_play = datetime.now(tz) self.pe1.save() # log in as user diff --git a/dakara_server/playlist/tests/test_playlist_entry.py b/dakara_server/playlist/tests/test_playlist_entry.py index dcdc01c3..164c5ba9 100644 --- a/dakara_server/playlist/tests/test_playlist_entry.py +++ b/dakara_server/playlist/tests/test_playlist_entry.py @@ -63,7 +63,7 @@ def test_get_playlist_entries_list_while_playing(self): # set the entry now = datetime.now(tz) - self.pe1.date_played = now - play_duration + self.pe1.date_play = now - play_duration self.pe1.save() # Login as simple user diff --git a/dakara_server/playlist/views.py b/dakara_server/playlist/views.py index 3f4dd361..926d707d 100644 --- a/dakara_server/playlist/views.py +++ b/dakara_server/playlist/views.py @@ -183,7 +183,7 @@ class PlaylistPlayedEntryListView(drf_generics.ListAPIView): """List of played entries.""" pagination_class = PlaylistEntryPagination - serializer_class = serializers.PlaylistPlayedEntryWithDatePlayedSerializer + serializer_class = serializers.PlaylistEntrySerializer queryset = models.PlaylistEntry.objects.get_playlist_played() From 1e5877685d2cf99257bd2cd794fdfec50dd3eeb3 Mon Sep 17 00:00:00 2001 From: Neraste Date: Thu, 14 Apr 2022 18:55:55 +0900 Subject: [PATCH 06/14] Fix timing not updated in digest --- dakara_server/playlist/serializers.py | 22 ++++++++++------- dakara_server/playlist/tests/test_digest.py | 27 +++++++++++++++++++++ 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/dakara_server/playlist/serializers.py b/dakara_server/playlist/serializers.py index 1d3b2980..e2dd41de 100644 --- a/dakara_server/playlist/serializers.py +++ b/dakara_server/playlist/serializers.py @@ -116,15 +116,19 @@ class Meta: read_only_fields = ("paused", "in_transition", "date", "playlist_entry") to_update_fields = ("timing",) - @property - def data(self): - self.recalculate_timing() - return super().data - - def recalculate_timing(self): - """Manually update the player timing.""" - player = self.instance - if player is None: + def to_representation(self, instance, *args, **kwargs): + # override the representation method to force recalculation of player + # timing + self.recalculate_timing(instance) + return super().to_representation(instance, *args, **kwargs) + + def recalculate_timing(self, player): + """Manually update the player timing. + + Args: + player (playlist.models.Player): Instance of the current player. + """ + if player is None or not isinstance(player, Player): return now = datetime.now(tz) diff --git a/dakara_server/playlist/tests/test_digest.py b/dakara_server/playlist/tests/test_digest.py index 09208a44..efee441e 100644 --- a/dakara_server/playlist/tests/test_digest.py +++ b/dakara_server/playlist/tests/test_digest.py @@ -59,6 +59,7 @@ def test_get_playing(self): self.assertEqual( response.data["player_status"]["playlist_entry"]["id"], self.pe1.id ) + self.assertEqual(response.data["player_status"]["timing"], 0) self.assertIn("player_errors", response.data) self.assertFalse(response.data["player_errors"]) self.assertIn("karaoke", response.data) @@ -66,6 +67,32 @@ def test_get_playing(self): self.assertTrue(response.data["karaoke"]["can_add_to_playlist"]) self.assertTrue(response.data["karaoke"]["player_play_next_song"]) + @freeze_time("1970-01-01 00:01:00") + def test_get_playing_delayed(self): + """Get the digest when the player is playing with delay. + + There should be no errors, the player should be playing and the karaoke + should be running. + """ + self.authenticate(self.user) + + # start playing + self.player_play_next_song() + + with freeze_time("1970-01-01 00:01:02"): + # get the digest + response = self.client.get(self.url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + # assert the response + self.assertIn("player_status", response.data) + self.assertEqual( + response.data["player_status"]["playlist_entry"]["id"], self.pe1.id + ) + self.assertEqual(response.data["player_status"]["timing"], 2) + self.assertIn("player_errors", response.data) + self.assertIn("karaoke", response.data) + @freeze_time("1970-01-01 00:01:00") def test_get_playing_transition(self): """Get the digest when the player is playing a transition. From c70aad0952f6a807062355b50909d30ea3cc5fc3 Mon Sep 17 00:00:00 2001 From: Neraste Date: Thu, 14 Apr 2022 18:56:47 +0900 Subject: [PATCH 07/14] Add playlist entries in digest --- dakara_server/playlist/serializers.py | 20 ++++++-- dakara_server/playlist/tests/test_digest.py | 51 ++++++++++++++++++++- dakara_server/playlist/views.py | 11 ++++- 3 files changed, 75 insertions(+), 7 deletions(-) diff --git a/dakara_server/playlist/serializers.py b/dakara_server/playlist/serializers.py index e2dd41de..c4790ef9 100644 --- a/dakara_server/playlist/serializers.py +++ b/dakara_server/playlist/serializers.py @@ -70,8 +70,22 @@ class PlaylistEntryForDigestSerializer(serializers.ModelSerializer): class Meta: model = PlaylistEntry - fields = ("id", "song") - read_only_fields = ("id", "song") + fields = ( + "id", + "song", + "use_instrumental", + "date_play", + "was_played", + "owner", + ) + read_only_fields = ( + "id", + "song", + "use_instrumental", + "date_play", + "was_played", + "owner", + ) class PlaylistEntriesWithDateEndSerializer(serializers.Serializer): @@ -299,7 +313,7 @@ class DigestSerializer(serializers.Serializer): player_status = PlayerStatusSerializer() karaoke = KaraokeSerializer() player_errors = PlayerErrorForDigestSerializer(many=True) - # playlist_entries = + playlist_entries = PlaylistEntryForDigestSerializer(many=True) class PlaylistReorderSerializer(serializers.Serializer): diff --git a/dakara_server/playlist/tests/test_digest.py b/dakara_server/playlist/tests/test_digest.py index efee441e..ff3d9a82 100644 --- a/dakara_server/playlist/tests/test_digest.py +++ b/dakara_server/playlist/tests/test_digest.py @@ -38,6 +38,7 @@ def test_get_startup(self): self.assertTrue(response.data["karaoke"]["ongoing"]) self.assertTrue(response.data["karaoke"]["can_add_to_playlist"]) self.assertTrue(response.data["karaoke"]["player_play_next_song"]) + self.assertIn("playlist_entries", response.data) def test_get_playing(self): """Get the digest when the player is playing. @@ -66,6 +67,7 @@ def test_get_playing(self): self.assertTrue(response.data["karaoke"]["ongoing"]) self.assertTrue(response.data["karaoke"]["can_add_to_playlist"]) self.assertTrue(response.data["karaoke"]["player_play_next_song"]) + self.assertIn("playlist_entries", response.data) @freeze_time("1970-01-01 00:01:00") def test_get_playing_delayed(self): @@ -111,6 +113,7 @@ def test_get_playing_transition(self): # assert the response self.assertEqual(response.data["player_status"]["timing"], 0) + self.assertIn("playlist_entries", response.data) def test_get_errors(self): """Get the digest when there are errors. @@ -158,6 +161,7 @@ def test_get_errors(self): self.assertTrue(response.data["karaoke"]["ongoing"]) self.assertTrue(response.data["karaoke"]["can_add_to_playlist"]) self.assertTrue(response.data["karaoke"]["player_play_next_song"]) + self.assertIn("playlist_entries", response.data) def test_get_player_does_not_play_next_song(self): """Get the digest when the player does not play next song. @@ -177,6 +181,49 @@ def test_get_player_does_not_play_next_song(self): self.assertIn("player_status", response.data) self.assertIsNone(response.data["player_status"]["playlist_entry"]) self.assertIn("player_errors", response.data) - self.assertFalse(response.data["player_errors"]) self.assertIn("karaoke", response.data) - self.assertFalse(response.data["karaoke"]["player_play_next_song"]) + self.assertIn("playlist_entries", response.data) + + def test_get_entries(self): + """Get the digest when there are errors. + + There should errors, the player should be idle and the karaoke + should be running. + """ + self.authenticate(self.user) + + # get the digest + response = self.client.get(self.url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + # assert the response + self.assertIn("player_status", response.data) + self.assertIn("karaoke", response.data) + self.assertIn("player_errors", response.data) + self.assertIn("playlist_entries", response.data) + self.assertEqual(len(response.data["playlist_entries"]), 4) + pe1 = response.data["playlist_entries"][0] + self.assertEqual(pe1["id"], self.pe1.id) + self.assertEqual(pe1["song"]["id"], self.pe1.song.id) + self.assertNotIn("artists", pe1["song"]) + self.assertFalse(pe1["use_instrumental"]) + self.assertFalse(pe1["was_played"]) + self.assertIsNone(pe1["date_play"]) + pe2 = response.data["playlist_entries"][1] + self.assertEqual(pe2["id"], self.pe2.id) + self.assertEqual(pe2["song"]["id"], self.pe2.song.id) + self.assertTrue(pe2["use_instrumental"]) + self.assertFalse(pe2["was_played"]) + self.assertIsNone(pe2["date_play"]) + pe3 = response.data["playlist_entries"][2] + self.assertEqual(pe3["id"], self.pe3.id) + self.assertEqual(pe3["song"]["id"], self.pe3.song.id) + self.assertFalse(pe3["use_instrumental"]) + self.assertTrue(pe3["was_played"]) + self.assertIsNotNone(pe3["date_play"]) + pe4 = response.data["playlist_entries"][3] + self.assertEqual(pe4["id"], self.pe4.id) + self.assertEqual(pe4["song"]["id"], self.pe4.song.id) + self.assertFalse(pe4["use_instrumental"]) + self.assertTrue(pe4["was_played"]) + self.assertIsNotNone(pe4["date_play"]) diff --git a/dakara_server/playlist/views.py b/dakara_server/playlist/views.py index 926d707d..29ee69e5 100644 --- a/dakara_server/playlist/views.py +++ b/dakara_server/playlist/views.py @@ -223,8 +223,11 @@ class DigestView(APIView): Includes: - player_status: current player; + - karaoke: current karaoke session; - player_errors: errors from the player; - - karaoke: current karaoke session. + - playlist_entries: content of the playlist. + + The page is cached 1 second. """ permission_classes = [IsAuthenticated] @@ -240,11 +243,15 @@ def get(self, request, *args, **kwargs): # Get player errors player_errors_pool = models.PlayerError.objects.all() + # Get playlist entries + playlist_entries_pool = models.PlaylistEntry.objects.all() + serializer = serializers.DigestSerializer( { "player_status": player, - "player_errors": player_errors_pool, "karaoke": karaoke, + "player_errors": player_errors_pool, + "playlist_entries": playlist_entries_pool, } ) From bb2010e33793d65fb2ae18887a63eaac40e656b1 Mon Sep 17 00:00:00 2001 From: Neraste Date: Thu, 14 Apr 2022 18:59:42 +0900 Subject: [PATCH 08/14] Cache digest during 0.5 seconds --- dakara_server/playlist/views.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/dakara_server/playlist/views.py b/dakara_server/playlist/views.py index 29ee69e5..26bdec60 100644 --- a/dakara_server/playlist/views.py +++ b/dakara_server/playlist/views.py @@ -6,6 +6,9 @@ from django.core.cache import cache from django.shortcuts import get_object_or_404 from django.utils import timezone +from django.utils.decorators import method_decorator +from django.views.decorators.cache import cache_page +from django.views.decorators.vary import vary_on_cookie, vary_on_headers from rest_framework import generics as drf_generics from rest_framework import status from rest_framework.authentication import SessionAuthentication, TokenAuthentication @@ -227,11 +230,14 @@ class DigestView(APIView): - player_errors: errors from the player; - playlist_entries: content of the playlist. - The page is cached 1 second. + The page is cached 0.5 seconds. """ permission_classes = [IsAuthenticated] + @method_decorator(cache_page(0.5)) + @method_decorator(vary_on_headers("Authorization")) + @method_decorator(vary_on_cookie) def get(self, request, *args, **kwargs): """Send aggregated player data.""" # Get kara status From c48020e096fdaa2e0fd437d412f31a5ed69b4a14 Mon Sep 17 00:00:00 2001 From: Neraste Date: Fri, 15 Apr 2022 00:06:22 +0900 Subject: [PATCH 09/14] Add content in digest serializers --- dakara_server/library/serializers.py | 6 ++++-- dakara_server/playlist/serializers.py | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/dakara_server/library/serializers.py b/dakara_server/library/serializers.py index e7812ed7..6c4ee2c4 100644 --- a/dakara_server/library/serializers.py +++ b/dakara_server/library/serializers.py @@ -435,10 +435,12 @@ class Meta: class SongForDigestSerializer(serializers.ModelSerializer): """Song serializer for playlist digest info.""" + duration = SecondsDurationField() + class Meta: model = Song - fields = ("id", "title") - read_only_fields = ("id", "title") + fields = ("id", "title", "duration") + read_only_fields = ("id", "title", "duration") class WorkForFeederSerializer(serializers.ModelSerializer): diff --git a/dakara_server/playlist/serializers.py b/dakara_server/playlist/serializers.py index c4790ef9..ce05ee21 100644 --- a/dakara_server/playlist/serializers.py +++ b/dakara_server/playlist/serializers.py @@ -67,6 +67,7 @@ class PlaylistEntryForDigestSerializer(serializers.ModelSerializer): """Played playlist entry serializer for playlist digest info.""" song = SongForDigestSerializer(many=False, read_only=True) + owner = UserForPublicSerializer(read_only=True) class Meta: model = PlaylistEntry From 44cb58949cdcae977ff5ba6ba15408e67fa6340b Mon Sep 17 00:00:00 2001 From: Neraste Date: Fri, 15 Apr 2022 00:10:33 +0900 Subject: [PATCH 10/14] Rename playlist as queuing and playlist played as played --- dakara_server/dakara_server/urls.py | 18 ++-- dakara_server/playlist/models.py | 8 +- dakara_server/playlist/serializers.py | 2 +- dakara_server/playlist/tests/test_models.py | 28 +++--- ...ylist_entry.py => test_playlist_played.py} | 12 +-- ...list_entry.py => test_playlist_queuing.py} | 87 ++++--------------- dakara_server/playlist/views.py | 41 ++------- 7 files changed, 58 insertions(+), 138 deletions(-) rename dakara_server/playlist/tests/{test_played_playlist_entry.py => test_playlist_played.py} (72%) rename dakara_server/playlist/tests/{test_playlist_entry.py => test_playlist_queuing.py} (87%) diff --git a/dakara_server/dakara_server/urls.py b/dakara_server/dakara_server/urls.py index 9b4db3fc..c3f7e807 100644 --- a/dakara_server/dakara_server/urls.py +++ b/dakara_server/dakara_server/urls.py @@ -73,19 +73,19 @@ name="playlist-digest", ), path( - "api/playlist/entries/", - playlist_views.PlaylistEntryListView.as_view(), - name="playlist-entries-list", + "api/playlist/queuing/", + playlist_views.PlaylistQueuingListView.as_view(), + name="playlist-queuing-list", ), path( - "api/playlist/entries//", - playlist_views.PlaylistEntryView.as_view(), - name="playlist-entries", + "api/playlist/queuing//", + playlist_views.PlaylistQueuingView.as_view(), + name="playlist-queuing", ), path( - "api/playlist/played-entries/", - playlist_views.PlaylistPlayedEntryListView.as_view(), - name="playlist-played-entries-list", + "api/playlist/played/", + playlist_views.PlaylistPlayedListView.as_view(), + name="playlist-played-list", ), path( "api/playlist/karaoke/", diff --git a/dakara_server/playlist/models.py b/dakara_server/playlist/models.py index 8963240e..ace0bae1 100644 --- a/dakara_server/playlist/models.py +++ b/dakara_server/playlist/models.py @@ -33,7 +33,7 @@ def get_playing(self): return playlist.first() - def get_playlist(self): + def get_queuing(self): """Get the playlist of ongoing entries.""" queryset = self.exclude( models.Q(was_played=True) | models.Q(date_play__isnull=False) @@ -41,7 +41,7 @@ def get_playlist(self): return queryset - def get_playlist_played(self): + def get_played(self): """Get the playlist of passed entries.""" playlist = self.filter(was_played=True) @@ -62,10 +62,10 @@ def get_next(self, entry_id=None): else: # do not process a played entry - if self.get_playlist_played().filter(pk=entry_id): + if self.get_played().filter(pk=entry_id): return None - playlist = self.get_playlist().exclude(pk=entry_id) + playlist = self.get_queuing().exclude(pk=entry_id) if not playlist: return None diff --git a/dakara_server/playlist/serializers.py b/dakara_server/playlist/serializers.py index ce05ee21..247907a1 100644 --- a/dakara_server/playlist/serializers.py +++ b/dakara_server/playlist/serializers.py @@ -249,7 +249,7 @@ def validate_playlist_entry_id(self, playlist_entry): # about to be played if not ( playlist_entry == PlaylistEntry.objects.get_playing() - or playlist_entry in PlaylistEntry.objects.get_playlist_played() + or playlist_entry in PlaylistEntry.objects.get_played() or PlaylistEntry.objects.get_playing() is None and playlist_entry == PlaylistEntry.objects.get_next() ): diff --git a/dakara_server/playlist/tests/test_models.py b/dakara_server/playlist/tests/test_models.py index 1c488a42..1ff0ab13 100644 --- a/dakara_server/playlist/tests/test_models.py +++ b/dakara_server/playlist/tests/test_models.py @@ -73,7 +73,7 @@ def test_get_playing_fail(self, playlist_provider): def test_get_playlist_normal(self, playlist_provider): """Test to get the playlist.""" # pre assert there are 2 entries in playlist - playlist = models.PlaylistEntry.objects.get_playlist() + playlist = models.PlaylistEntry.objects.get_queuing() assert len(playlist) == 2 assert playlist[0] == playlist_provider.pe1 assert playlist[1] == playlist_provider.pe2 @@ -83,7 +83,7 @@ def test_get_playlist_normal(self, playlist_provider): playlist_provider.pe1.save() # assert there is one entry in playlist - playlist = models.PlaylistEntry.objects.get_playlist() + playlist = models.PlaylistEntry.objects.get_queuing() assert len(playlist) == 1 assert playlist[0] == playlist_provider.pe2 @@ -92,14 +92,14 @@ def test_get_playlist_normal(self, playlist_provider): playlist_provider.pe1.save() # assert there is still one entry in playlist - playlist = models.PlaylistEntry.objects.get_playlist() + playlist = models.PlaylistEntry.objects.get_queuing() assert len(playlist) == 1 assert playlist[0] == playlist_provider.pe2 def test_get_playlist_abnormal(self, playlist_provider): """Test to get the playlist in abnormal condition.""" # pre assert there are 2 entries in playlist - playlist = models.PlaylistEntry.objects.get_playlist() + playlist = models.PlaylistEntry.objects.get_queuing() assert len(playlist) == 2 assert playlist[0] == playlist_provider.pe1 assert playlist[1] == playlist_provider.pe2 @@ -109,7 +109,7 @@ def test_get_playlist_abnormal(self, playlist_provider): playlist_provider.pe1.save() # assert there is one entry in playlist - playlist = models.PlaylistEntry.objects.get_playlist() + playlist = models.PlaylistEntry.objects.get_queuing() assert len(playlist) == 1 assert playlist[0] == playlist_provider.pe2 @@ -118,14 +118,14 @@ def test_get_playlist_abnormal(self, playlist_provider): playlist_provider.pe1.save() # assert there is still one entry in playlist - playlist = models.PlaylistEntry.objects.get_playlist() + playlist = models.PlaylistEntry.objects.get_queuing() assert len(playlist) == 1 assert playlist[0] == playlist_provider.pe2 - def test_get_playlist_played_normal(self, playlist_provider): + def test_get_played_normal(self, playlist_provider): """Test to get the playlist of played entries.""" # pre assert there are 2 entries played - playlist_played = models.PlaylistEntry.objects.get_playlist_played() + playlist_played = models.PlaylistEntry.objects.get_played() assert len(playlist_played) == 2 assert playlist_played[0] == playlist_provider.pe3 assert playlist_played[1] == playlist_provider.pe4 @@ -135,7 +135,7 @@ def test_get_playlist_played_normal(self, playlist_provider): playlist_provider.pe1.save() # assert there are still 2 entries played - playlist_played = models.PlaylistEntry.objects.get_playlist_played() + playlist_played = models.PlaylistEntry.objects.get_played() assert len(playlist_played) == 2 assert playlist_played[0] == playlist_provider.pe3 assert playlist_played[1] == playlist_provider.pe4 @@ -145,16 +145,16 @@ def test_get_playlist_played_normal(self, playlist_provider): playlist_provider.pe1.save() # assert there are now 3 entries played - playlist_played = models.PlaylistEntry.objects.get_playlist_played() + playlist_played = models.PlaylistEntry.objects.get_played() assert len(playlist_played) == 3 assert playlist_played[0] == playlist_provider.pe1 assert playlist_played[1] == playlist_provider.pe3 assert playlist_played[2] == playlist_provider.pe4 - def test_get_playlist_played_abnormal(self, playlist_provider): + def test_get_played_abnormal(self, playlist_provider): """Test to get the playlist of played entries in abnormal condition.""" # pre assert there are 2 entries played - playlist_played = models.PlaylistEntry.objects.get_playlist_played() + playlist_played = models.PlaylistEntry.objects.get_played() assert len(playlist_played) == 2 assert playlist_played[0] == playlist_provider.pe3 assert playlist_played[1] == playlist_provider.pe4 @@ -164,7 +164,7 @@ def test_get_playlist_played_abnormal(self, playlist_provider): playlist_provider.pe1.save() # assert there are now 3 entries played - playlist_played = models.PlaylistEntry.objects.get_playlist_played() + playlist_played = models.PlaylistEntry.objects.get_played() assert len(playlist_played) == 3 assert playlist_played[0] == playlist_provider.pe1 assert playlist_played[1] == playlist_provider.pe3 @@ -175,7 +175,7 @@ def test_get_playlist_played_abnormal(self, playlist_provider): playlist_provider.pe1.save() # assert there are still 3 entries played - playlist_played = models.PlaylistEntry.objects.get_playlist_played() + playlist_played = models.PlaylistEntry.objects.get_played() assert len(playlist_played) == 3 assert playlist_played[0] == playlist_provider.pe1 assert playlist_played[1] == playlist_provider.pe3 diff --git a/dakara_server/playlist/tests/test_played_playlist_entry.py b/dakara_server/playlist/tests/test_playlist_played.py similarity index 72% rename from dakara_server/playlist/tests/test_played_playlist_entry.py rename to dakara_server/playlist/tests/test_playlist_played.py index 2b482d4a..5c367a5f 100644 --- a/dakara_server/playlist/tests/test_played_playlist_entry.py +++ b/dakara_server/playlist/tests/test_playlist_played.py @@ -4,14 +4,14 @@ from playlist.tests.base_test import PlaylistAPITestCase -class PlaylistPlayedEntryListViewTestCase(PlaylistAPITestCase): - url = reverse("playlist-played-entries-list") +class PlaylistPlayedListViewTestCase(PlaylistAPITestCase): + url = reverse("playlist-played-list") def setUp(self): self.create_test_data() - def test_get_playlist_entries_list(self): - """Test to verify playlist played entries list.""" + def test_get_playlist_played_list(self): + """Test to verify playlist entries played list.""" # Login as simple user self.authenticate(self.user) @@ -26,8 +26,8 @@ def test_get_playlist_entries_list(self): self.check_playlist_played_entry_json(response.data["results"][0], self.pe3) self.check_playlist_played_entry_json(response.data["results"][1], self.pe4) - def test_get_playlist_entries_list_forbidden(self): - """Test to verify playlist entries list forbidden when not logged in.""" + def test_get_playlist_played_list_forbidden(self): + """Test to verify playlist entries played list forbidden when not logged in.""" # Get playlist entries list response = self.client.get(self.url) self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) diff --git a/dakara_server/playlist/tests/test_playlist_entry.py b/dakara_server/playlist/tests/test_playlist_queuing.py similarity index 87% rename from dakara_server/playlist/tests/test_playlist_entry.py rename to dakara_server/playlist/tests/test_playlist_queuing.py index 164c5ba9..193f56d5 100644 --- a/dakara_server/playlist/tests/test_playlist_entry.py +++ b/dakara_server/playlist/tests/test_playlist_queuing.py @@ -2,24 +2,22 @@ from unittest.mock import ANY, patch from django.urls import reverse -from django.utils.dateparse import parse_datetime from freezegun import freeze_time from rest_framework import status from internal.tests.base_test import UserModel, tz -from playlist.models import Karaoke, Player, PlaylistEntry +from playlist.models import Karaoke, PlaylistEntry from playlist.tests.base_test import PlaylistAPITestCase -class PlaylistEntryListViewTestCase(PlaylistAPITestCase): - url = reverse("playlist-entries-list") +class PlaylistQueuingListViewTestCase(PlaylistAPITestCase): + url = reverse("playlist-queuing-list") def setUp(self): self.create_test_data() - @freeze_time("1970-01-01 00:01:00") - def test_get_playlist_entries_list(self): - """Test to verify playlist entries list.""" + def test_get_playlist_queuing_list(self): + """Test to verify playlist entries queuing list.""" # Login as simple user self.authenticate(self.user) @@ -27,6 +25,7 @@ def test_get_playlist_entries_list(self): # Should only return entries with `was_played`=False response = self.client.get(self.url) self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data["count"], 2) self.assertEqual(len(response.data["results"]), 2) # Playlist entries are in order of creation @@ -35,64 +34,12 @@ def test_get_playlist_entries_list(self): self.check_playlist_entry_json(pe1, self.pe1) self.check_playlist_entry_json(pe2, self.pe2) - # check the date of the end of the playlist - now = datetime.now(tz) - self.assertEqual( - parse_datetime(response.data["date_end"]), - now + self.pe1.song.duration + self.pe2.song.duration, - ) - - # check the date of play of each entries - self.assertEqual(parse_datetime(pe1["date_play"]), now) - self.assertEqual(parse_datetime(pe2["date_play"]), now + self.pe1.song.duration) - - @freeze_time("1970-01-01 00:01:00") - def test_get_playlist_entries_list_while_playing(self): - """Test to verify playlist entries play dates while playing. - - The player is currently in the middle of the song, play dates should - take account of the remaining time of the player. - """ - # set the player - karaoke = Karaoke.objects.get_object() - player, _ = Player.cache.get_or_create(karaoke=karaoke) - player.playlist_entry_id = self.pe1.id - play_duration = timedelta(seconds=2) - player.timing = play_duration - player.save() - - # set the entry - now = datetime.now(tz) - self.pe1.date_play = now - play_duration - self.pe1.save() - - # Login as simple user - self.authenticate(self.user) + # check the date of play is null + self.assertIsNone(pe1["date_play"]) + self.assertIsNone(pe2["date_play"]) - # Get playlist entries list - # Should only return entries with `was_played`=False - response = self.client.get(self.url) - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(len(response.data["results"]), 1) - - # Playlist entries are in order of creation - pe2 = response.data["results"][0] - self.check_playlist_entry_json(pe2, self.pe2) - - # check the date of play - self.assertEqual( - parse_datetime(response.data["date_end"]), - now + self.pe1.song.duration - play_duration + self.pe2.song.duration, - ) - - # check the date of play of each entries - self.assertEqual( - parse_datetime(pe2["date_play"]), - now + self.pe1.song.duration - play_duration, - ) - - def test_get_playlist_entries_list_forbidden(self): - """Test to verify playlist entries list forbidden when not logged in.""" + def test_get_playlist_queuing_list_forbidden(self): + """Test to verify playlist entries queuing list forbidden when not logged in.""" # Get playlist entries list response = self.client.get(self.url) self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) @@ -366,7 +313,7 @@ def test_post_create_user_unauthenticated_forbidden(self): response = self.client.post(self.url, {"song_id": self.song1.id}) self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) - def test_get_playlist_entries_list_playing_entry(self): + def test_get_playlist_queuing_list_playing_entry(self): """Test to verify playlist entries list does not include playing song.""" # Login as simple user self.authenticate(self.user) @@ -423,14 +370,16 @@ def test_post_create_playlist_entry_disabled_tag_manager(self): self.assertEqual(response.status_code, status.HTTP_201_CREATED) -class PlaylistEntryViewTestCase(PlaylistAPITestCase): +class PlaylistQueuingViewTestCase(PlaylistAPITestCase): + url_name = "playlist-queuing" + def setUp(self): self.create_test_data() # Create urls to access these playlist entries - self.url_pe1 = reverse("playlist-entries", kwargs={"pk": self.pe1.id}) - self.url_pe2 = reverse("playlist-entries", kwargs={"pk": self.pe2.id}) - self.url_pe3 = reverse("playlist-entries", kwargs={"pk": self.pe3.id}) + self.url_pe1 = reverse(self.url_name, kwargs={"pk": self.pe1.id}) + self.url_pe2 = reverse(self.url_name, kwargs={"pk": self.pe2.id}) + self.url_pe3 = reverse(self.url_name, kwargs={"pk": self.pe3.id}) def test_delete_playlist_entry_manager(self): """Test to verify playlist entry deletion as playlist manager.""" diff --git a/dakara_server/playlist/views.py b/dakara_server/playlist/views.py index 26bdec60..e9ee9065 100644 --- a/dakara_server/playlist/views.py +++ b/dakara_server/playlist/views.py @@ -18,7 +18,6 @@ from rest_framework.views import APIView from internal import permissions as internal_permissions -from internal.pagination import PageNumberPaginationCustom from library import permissions as library_permissions from playlist import authentications, models, permissions, serializers from playlist.consumers import send_to_channel @@ -29,13 +28,7 @@ UserModel = get_user_model() -class PlaylistEntryPagination(PageNumberPaginationCustom): - """Pagination setup for playlist entries.""" - - page_size = 100 - - -class PlaylistEntryView(drf_generics.DestroyAPIView): +class PlaylistQueuingView(drf_generics.DestroyAPIView): """Edition or deletion of a playlist entry.""" serializer_class = serializers.PlaylistEntrySerializer @@ -44,7 +37,7 @@ class PlaylistEntryView(drf_generics.DestroyAPIView): permissions.IsPlaylistManager | (internal_permissions.IsDelete & permissions.IsOwner), ] - queryset = models.PlaylistEntry.objects.get_playlist() + queryset = models.PlaylistEntry.objects.get_queuing() def put(self, request, *args, **kwargs): playlist_entry = self.get_object() @@ -66,7 +59,7 @@ def put(self, request, *args, **kwargs): return Response(status=status.HTTP_204_NO_CONTENT) -class PlaylistEntryListView(drf_generics.ListCreateAPIView): +class PlaylistQueuingListView(drf_generics.ListCreateAPIView): """List of entries or creation of a new entry in the playlist.""" serializer_class = serializers.PlaylistEntrySerializer @@ -76,28 +69,7 @@ class PlaylistEntryListView(drf_generics.ListCreateAPIView): (permissions.IsPlaylistManager & library_permissions.IsLibraryManager) | permissions.IsSongEnabled, ] - queryset = models.PlaylistEntry.objects.get_playlist() - - def get(self, request, *args, **kwargs): - queryset = self.queryset.all() - karaoke = models.Karaoke.objects.get_object() - player, _ = models.Player.cache.get_or_create(karaoke=karaoke) - date = datetime.now(tz) - - # add player remaining time - if player.playlist_entry: - date += player.playlist_entry.song.duration - player.timing - - # for each entry, compute when it is supposed to play - for playlist_entry in queryset: - playlist_entry.date_play = date - date += playlist_entry.song.duration - - serializer = serializers.PlaylistEntriesWithDateEndSerializer( - {"results": queryset, "date_end": date}, context={"request": request} - ) - - return Response(serializer.data) + queryset = models.PlaylistEntry.objects.get_queuing() def perform_create(self, serializer): # Deny creation if kara is not ongoing @@ -182,12 +154,11 @@ def perform_create(self, serializer): ) -class PlaylistPlayedEntryListView(drf_generics.ListAPIView): +class PlaylistPlayedListView(drf_generics.ListAPIView): """List of played entries.""" - pagination_class = PlaylistEntryPagination serializer_class = serializers.PlaylistEntrySerializer - queryset = models.PlaylistEntry.objects.get_playlist_played() + queryset = models.PlaylistEntry.objects.get_played() class PlayerCommandView(drf_generics.UpdateAPIView): From 846f69979aad79c54c8050bc47c008912d19de2e Mon Sep 17 00:00:00 2001 From: Neraste Date: Mon, 25 Apr 2022 11:40:14 +0900 Subject: [PATCH 11/14] Reverse playlist played entries order and add was_played to playlist serializer --- dakara_server/playlist/serializers.py | 3 ++- dakara_server/playlist/tests/test_playlist_played.py | 4 ++-- dakara_server/playlist/views.py | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/dakara_server/playlist/serializers.py b/dakara_server/playlist/serializers.py index 247907a1..1601fa76 100644 --- a/dakara_server/playlist/serializers.py +++ b/dakara_server/playlist/serializers.py @@ -41,8 +41,9 @@ class Meta: "song_id", "use_instrumental", "date_play", + "was_played", ) - read_only_fields = ("date_created", "date_play") + read_only_fields = ("date_created", "date_play", "was_played") def validate(self, data): if data.get("use_instrumental") and not data["song"].has_instrumental: diff --git a/dakara_server/playlist/tests/test_playlist_played.py b/dakara_server/playlist/tests/test_playlist_played.py index 5c367a5f..3455e380 100644 --- a/dakara_server/playlist/tests/test_playlist_played.py +++ b/dakara_server/playlist/tests/test_playlist_played.py @@ -23,8 +23,8 @@ def test_get_playlist_played_list(self): self.assertEqual(len(response.data["results"]), 2) # Playlist entries are in order of creation - self.check_playlist_played_entry_json(response.data["results"][0], self.pe3) - self.check_playlist_played_entry_json(response.data["results"][1], self.pe4) + self.check_playlist_played_entry_json(response.data["results"][0], self.pe4) + self.check_playlist_played_entry_json(response.data["results"][1], self.pe3) def test_get_playlist_played_list_forbidden(self): """Test to verify playlist entries played list forbidden when not logged in.""" diff --git a/dakara_server/playlist/views.py b/dakara_server/playlist/views.py index e9ee9065..b1c905c0 100644 --- a/dakara_server/playlist/views.py +++ b/dakara_server/playlist/views.py @@ -158,7 +158,7 @@ class PlaylistPlayedListView(drf_generics.ListAPIView): """List of played entries.""" serializer_class = serializers.PlaylistEntrySerializer - queryset = models.PlaylistEntry.objects.get_played() + queryset = models.PlaylistEntry.objects.get_played().reverse() class PlayerCommandView(drf_generics.UpdateAPIView): From 40e150b04226ad92daad3d3450fd050e94687cd4 Mon Sep 17 00:00:00 2001 From: Neraste Date: Sun, 24 Mar 2024 01:09:43 +0100 Subject: [PATCH 12/14] Add player error ID --- dakara_server/playlist/serializers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dakara_server/playlist/serializers.py b/dakara_server/playlist/serializers.py index 1601fa76..ad461bfe 100644 --- a/dakara_server/playlist/serializers.py +++ b/dakara_server/playlist/serializers.py @@ -238,6 +238,7 @@ class PlayerErrorSerializer(serializers.ModelSerializer): class Meta: model = PlayerError fields = ( + "id", "playlist_entry", "playlist_entry_id", "error_message", From 54eb9aeb6fa6dd01d2392316540cfda74b5e97a2 Mon Sep 17 00:00:00 2001 From: Neraste Date: Sun, 28 Jul 2024 18:25:19 +0200 Subject: [PATCH 13/14] Reverse playler errors order --- .../playlist/tests/test_player_error.py | 46 +++++++++++++++++-- dakara_server/playlist/views.py | 2 +- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/dakara_server/playlist/tests/test_player_error.py b/dakara_server/playlist/tests/test_player_error.py index 641ecb00..cf1f36ac 100644 --- a/dakara_server/playlist/tests/test_player_error.py +++ b/dakara_server/playlist/tests/test_player_error.py @@ -2,6 +2,7 @@ from unittest.mock import patch from django.urls import reverse +from freezegun import freeze_time from rest_framework import status from internal.tests.base_test import tz @@ -32,10 +33,13 @@ def test_get_errors_empty(self): # assert the response self.assertEqual(response.data["results"], []) - def test_get_errors_something(self): - """Test to get errors when there is an error.""" + def test_get_errors_one(self): + """Test to get errors when there is one.""" # set an error - PlayerError.objects.create(playlist_entry=self.pe1, error_message="dummy error") + with freeze_time("1970-01-01 00:01:00"): + PlayerError.objects.create( + playlist_entry=self.pe1, error_message="dummy error" + ) # log as user self.authenticate(self.user) @@ -45,11 +49,45 @@ def test_get_errors_something(self): self.assertEqual(response.status_code, status.HTTP_200_OK) # assert the response + self.assertEqual(len(response.data["results"]), 1) self.assertEqual( response.data["results"][0]["playlist_entry"]["id"], self.pe1.id ) self.assertEqual(response.data["results"][0]["error_message"], "dummy error") - self.assertIsNotNone(response.data["results"][0]["date_created"]) + self.assertEqual( + response.data["results"][0]["date_created"], "1970-01-01T00:01:00Z" + ) + + def test_get_errors_two(self): + """Test to get errors when there are two.""" + # set an error + with freeze_time("1970-01-01 00:01:00"): + PlayerError.objects.create( + playlist_entry=self.pe1, error_message="dummy error 1" + ) + + with freeze_time("1970-01-01 00:02:00"): + PlayerError.objects.create( + playlist_entry=self.pe2, error_message="dummy error 2" + ) + + # log as user + self.authenticate(self.user) + + # request the errors list + response = self.client.get(self.url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + # assert the response + self.assertEqual(len(response.data["results"]), 2) + + # check the order of the player errors + self.assertEqual( + response.data["results"][0]["playlist_entry"]["id"], self.pe2.id + ) + self.assertEqual( + response.data["results"][1]["playlist_entry"]["id"], self.pe1.id + ) def test_get_errors_forbidden(self): """Test to get errors when not authenticated.""" diff --git a/dakara_server/playlist/views.py b/dakara_server/playlist/views.py index 41b909cd..207fc8bf 100644 --- a/dakara_server/playlist/views.py +++ b/dakara_server/playlist/views.py @@ -466,7 +466,7 @@ class PlayerErrorView(drf_generics.ListCreateAPIView): IsAuthenticated & internal_permissions.IsReadOnly | permissions.IsPlayer ] serializer_class = serializers.PlayerErrorSerializer - queryset = models.PlayerError.objects.order_by("date_created") + queryset = models.PlayerError.objects.order_by("date_created").reverse() def perform_create(self, serializer): """Create an error and perform other actions. From 29c40cd5bb5d6caf3d6ac4f09be60d3ba36c31af Mon Sep 17 00:00:00 2001 From: Neraste Date: Sun, 28 Jul 2024 19:40:43 +0200 Subject: [PATCH 14/14] Update changelog --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ad10efc..7452afc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,10 +38,17 @@ Any important notes regarding the update. - MacOS support. - Added Python 3.12 support. +- Player errors can be accessed in a library fashion (using pagination). + +### Changed + +- Playlist is integrated to the digest view in its minimal form. +- Played and queuing playlist entries can be accessed in a library fashion (using pagination), their routes are `playlist/played` and `playlist/queuing`. ### Removed - Dropped Python 3.7 support. +- Playlist entries in digest view don't have pre-calculated date of play any more. This data has to be calculated by the front now. ## 1.8.1 - 2023-12-17