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 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/library/serializers.py b/dakara_server/library/serializers.py index abc80af9..03e70e8b 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): @@ -440,6 +440,17 @@ class Meta: read_only_fields = ("id", "filename", "directory") +class SongForDigestSerializer(serializers.ModelSerializer): + """Song serializer for playlist digest info.""" + + duration = SecondsDurationField() + + class Meta: + model = Song + fields = ("id", "title", "duration") + read_only_fields = ("id", "title", "duration") + + class WorkForFeederSerializer(serializers.ModelSerializer): """Work serializer for the feeder.""" 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..ace0bae1 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 @@ -33,15 +33,15 @@ 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_played__isnull=False) + models.Q(was_played=True) | models.Q(date_play__isnull=False) ) 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 @@ -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 660c49d5..ad461bfe 100644 --- a/dakara_server/playlist/serializers.py +++ b/dakara_server/playlist/serializers.py @@ -1,14 +1,20 @@ +from datetime import datetime + +from django.utils import timezone from rest_framework import serializers from library.models import Song from library.serializers import ( SecondsDurationField, + SongForDigestSerializer, SongForPlayerSerializer, SongSerializer, ) from playlist.models import Karaoke, Player, PlayerError, PlayerToken, PlaylistEntry from users.serializers import UserForPublicSerializer +tz = timezone.get_default_timezone() + class PlaylistEntrySerializer(serializers.ModelSerializer): """Playlist entry serializer.""" @@ -27,8 +33,17 @@ 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", + "was_played", + ) + read_only_fields = ("date_created", "date_play", "was_played") def validate(self, data): if data.get("use_instrumental") and not data["song"].has_instrumental: @@ -49,48 +64,36 @@ class Meta: read_only_fields = ("date_created",) -class PlaylistEntryWithDatePlaySerializer(PlaylistEntrySerializer): - """Playlist entry serializer. - - Reserved for song that will be played. - """ +class PlaylistEntryForDigestSerializer(serializers.ModelSerializer): + """Played playlist entry serializer for playlist digest info.""" - # date of play in the future, added manually to the PlaylistEntry object - date_play = serializers.DateTimeField(read_only=True) + song = SongForDigestSerializer(many=False, read_only=True) + owner = UserForPublicSerializer(read_only=True) - class Meta(PlaylistEntrySerializer.Meta): + class Meta: + model = PlaylistEntry fields = ( "id", - "date_created", - "date_play", - "owner", "song", "use_instrumental", + "date_play", + "was_played", + "owner", ) - - -class PlaylistPlayedEntryWithDatePlayedSerializer(PlaylistEntrySerializer): - """Playlist entry serializer. - - Reserved for song that were played. - """ - - class Meta(PlaylistEntrySerializer.Meta): - fields = ( + read_only_fields = ( "id", - "date_created", - "date_played", - "owner", "song", "use_instrumental", + "date_play", + "was_played", + "owner", ) - read_only_fields = ("date_created", "date_played") class PlaylistEntriesWithDateEndSerializer(serializers.Serializer): """Playlist entries with playlist end date.""" - results = PlaylistEntryWithDatePlaySerializer(many=True, read_only=True) + results = PlaylistEntrySerializer(many=True, read_only=True) date_end = serializers.DateTimeField(read_only=True) @@ -98,7 +101,7 @@ 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 ) @@ -129,6 +132,27 @@ class Meta: read_only_fields = ("paused", "in_transition", "date", "playlist_entry") to_update_fields = ("timing",) + 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) + 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 = { @@ -204,9 +228,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( @@ -215,7 +237,13 @@ class PlayerErrorSerializer(serializers.ModelSerializer): class Meta: model = PlayerError - fields = ("playlist_entry", "playlist_entry_id", "error_message") + fields = ( + "id", + "playlist_entry", + "playlist_entry_id", + "error_message", + "date_created", + ) read_only_fields = ("date_created",) def validate_playlist_entry_id(self, playlist_entry): @@ -223,7 +251,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() ): @@ -235,6 +263,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 = PlaylistEntryForDigestSerializer(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.""" @@ -269,9 +313,10 @@ class Meta: class DigestSerializer(serializers.Serializer): """Combine player info and kara status.""" - player_status = PlayerStatusSerializer() # TODO test this - player_errors = PlayerErrorSerializer(many=True) + player_status = PlayerStatusSerializer() karaoke = KaraokeSerializer() + player_errors = PlayerErrorForDigestSerializer(many=True) + playlist_entries = PlaylistEntryForDigestSerializer(many=True) 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_digest.py b/dakara_server/playlist/tests/test_digest.py index 0c278a5c..ff3d9a82 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 @@ -41,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. @@ -62,46 +60,60 @@ 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) 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): + """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) - @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 - ): + # 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. 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) + 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) - # assert the response - self.assertEqual(response.data["player_status"]["timing"], 0) - - # self.assertEqual(mocked_datetime.now.call_count, 2) + # 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. @@ -134,6 +146,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, @@ -142,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. @@ -161,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/tests/test_models.py b/dakara_server/playlist/tests/test_models.py index 94372ce6..1ff0ab13 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 @@ -73,17 +73,17 @@ 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 # 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 - 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,33 +109,33 @@ 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 # 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 - 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 # 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 - 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,18 +164,18 @@ 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 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 - 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 @@ -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 14925c1f..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,10 +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.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.""" @@ -62,7 +101,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 @@ -90,7 +129,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 @@ -129,7 +168,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() @@ -147,7 +186,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 @@ -160,7 +199,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_player_status.py b/dakara_server/playlist/tests/test_player_status.py index 07de7da1..dcca2e61 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 @@ -51,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) @@ -67,6 +69,25 @@ def test_get_status_in_play_with_timing(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_delayed(self): + """Test to access the player status when in play with timing with delay.""" + # set the player in play + 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.""" self.authenticate(self.user) @@ -88,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, @@ -119,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)) @@ -158,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) @@ -192,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)) @@ -203,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) @@ -233,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)) @@ -244,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)) @@ -276,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)) @@ -287,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)) @@ -317,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)) @@ -364,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, @@ -391,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_played_playlist_entry.py b/dakara_server/playlist/tests/test_playlist_played.py similarity index 69% rename from dakara_server/playlist/tests/test_played_playlist_entry.py rename to dakara_server/playlist/tests/test_playlist_played.py index 2b482d4a..3455e380 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) @@ -23,11 +23,11 @@ def test_get_playlist_entries_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_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 85% rename from dakara_server/playlist/tests/test_playlist_entry.py rename to dakara_server/playlist/tests/test_playlist_queuing.py index b6175ec6..193f56d5 100644 --- a/dakara_server/playlist/tests/test_playlist_entry.py +++ b/dakara_server/playlist/tests/test_playlist_queuing.py @@ -2,30 +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() - @patch( - "playlist.views.datetime", - side_effect=lambda *args, **kwargs: datetime(*args, **kwargs), - ) - def test_get_playlist_entries_list(self, mocked_datetime): - """Test to verify playlist entries list.""" - # patch the now method - now = datetime.now(tz) - mocked_datetime.now.return_value = now - + def test_get_playlist_queuing_list(self): + """Test to verify playlist entries queuing list.""" # Login as simple user self.authenticate(self.user) @@ -33,6 +25,7 @@ def test_get_playlist_entries_list(self, mocked_datetime): # 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 @@ -41,69 +34,12 @@ def test_get_playlist_entries_list(self, mocked_datetime): 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 - 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) - - @patch( - "playlist.views.datetime", - side_effect=lambda *args, **kwargs: datetime(*args, **kwargs), - ) - def test_get_playlist_entries_list_while_playing(self, mocked_datetime): - """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) - player.playlist_entry_id = self.pe1.id - play_duration = timedelta(seconds=2) - player.timing = play_duration - player.save() - - # set the entry - self.pe1.date_played = now - play_duration - self.pe1.save() - - # Login as simple user - self.authenticate(self.user) - - # 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, - ) + # check the date of play is null + self.assertIsNone(pe1["date_play"]) + self.assertIsNone(pe2["date_play"]) - 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) @@ -325,26 +261,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 @@ -385,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) @@ -442,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 3e232f72..207fc8bf 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 @@ -15,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 @@ -26,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 @@ -41,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() @@ -63,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 @@ -73,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 @@ -179,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.PlaylistPlayedEntryWithDatePlayedSerializer - queryset = models.PlaylistEntry.objects.get_playlist_played() + serializer_class = serializers.PlaylistEntrySerializer + queryset = models.PlaylistEntry.objects.get_played().reverse() class PlayerCommandView(drf_generics.UpdateAPIView): @@ -223,12 +197,18 @@ 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 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 @@ -237,21 +217,18 @@ 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() + # 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, } ) @@ -489,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. diff --git a/requirements_dev.txt b/requirements_dev.txt index 23f30d1d..f45b807b 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,5 +1,6 @@ black>=22.10.0,<22.11.0 codecov>=2.1.12,<2.2.0 +freezegun>=1.2.1,<1.3.0 flake8>=7.0.0,<7.1.0 isort>=5.10.1,<5.11.0 pre-commit>=2.20.0,<2.21.0