diff --git a/CHANGELOG.md b/CHANGELOG.md index 06b885e9..65844ede 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,10 @@ Any important notes regarding the update. - Allow to search playlist entries, player errors, users, and song tags. - Allow to search songs, playlist entries, player errors and users by ID. +### Changed + +- The `has_instrumental` field of songs has been replaced by the `instrumental_file` and `instrumental_track` fields. It field remains in the songs serializer (for client) as a read-only field. + ### Fixed - Fixed a bug when creating a player token with a MariaDB database. diff --git a/dakara_server/library/migrations/0013_improve_instrumental.py b/dakara_server/library/migrations/0013_improve_instrumental.py new file mode 100644 index 00000000..d10bc89f --- /dev/null +++ b/dakara_server/library/migrations/0013_improve_instrumental.py @@ -0,0 +1,27 @@ +# Generated by Django 5.2.12 on 2026-07-06 23:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("library", "0012_id_fields"), + ] + + operations = [ + migrations.RemoveField( + model_name="song", + name="has_instrumental", + ), + migrations.AddField( + model_name="song", + name="instrumental_file", + field=models.CharField(max_length=255, null=True), + ), + migrations.AddField( + model_name="song", + name="instrumental_track", + field=models.IntegerField(null=True), + ), + ] diff --git a/dakara_server/library/models.py b/dakara_server/library/models.py index 002b51e9..907b5bdf 100644 --- a/dakara_server/library/models.py +++ b/dakara_server/library/models.py @@ -18,13 +18,19 @@ class Song(models.Model): artists = models.ManyToManyField("Artist") works = models.ManyToManyField("Work", through="SongWorkLink") lyrics = models.TextField(blank=True) - has_instrumental = models.BooleanField(default=False) + instrumental_file = models.CharField(max_length=255, null=True) + instrumental_track = models.IntegerField(null=True) date_created = models.DateTimeField(auto_now_add=True) date_updated = models.DateTimeField(auto_now=True) def __str__(self): return self.title + @property + def has_instrumental(self): + """bool: Check if a song has an instrumental file or an instrumental track.""" + return self.instrumental_file or self.instrumental_track + class Artist(models.Model): """Artist object.""" diff --git a/dakara_server/library/serializers.py b/dakara_server/library/serializers.py index cf76978d..b3dd0929 100644 --- a/dakara_server/library/serializers.py +++ b/dakara_server/library/serializers.py @@ -335,6 +335,7 @@ class SongSerializer(serializers.ModelSerializer): tags = SongTagForSongSerializer(many=True, required=False) works = SongWorkLinkSerializer(many=True, source="songworklink_set", required=False) lyrics_preview = serializers.SerializerMethodField() + has_instrumental = serializers.SerializerMethodField() class Meta: model = Song @@ -353,10 +354,16 @@ class Meta: "lyrics", "lyrics_preview", "has_instrumental", + "instrumental_file", + "instrumental_track", "date_created", "date_updated", ) - extra_kwargs = {"lyrics": {"write_only": True}} + extra_kwargs = { + "lyrics": {"write_only": True}, + "instrumental_file": {"write_only": True}, + "instrumental_track": {"write_only": True}, + } @staticmethod def get_lyrics_preview(song, max_lines=5): @@ -374,6 +381,11 @@ def get_lyrics_preview(song, max_lines=5): return {"text": "\n".join(lyrics_list[:max_lines]), "truncated": True} + @staticmethod + def get_has_instrumental(song): + """Check if the song has an instrumental file or an instrumental track.""" + return song.instrumental_file or song.instrumental_track + def create(self, validated_data): """Create the Song instance.""" # create vanilla song @@ -430,7 +442,8 @@ class Meta: "artists", "works", "file_path", - "has_instrumental", + "instrumental_file", + "instrumental_track", ) @staticmethod diff --git a/dakara_server/library/tests/base_test.py b/dakara_server/library/tests/base_test.py index 728d4024..2a5ee9e6 100644 --- a/dakara_server/library/tests/base_test.py +++ b/dakara_server/library/tests/base_test.py @@ -62,7 +62,8 @@ def create_test_data(self): version="Version2", detail="Detail2", detail_video="Detail_Video2", - has_instrumental=True, + instrumental_file=None, + instrumental_track=1, ) self.song2.save() self.song2.tags.add(self.tag1) @@ -80,7 +81,17 @@ def check_song_json(self, json, expected_song): self.assertEqual(json["version"], expected_song.version) self.assertEqual(json["detail"], expected_song.detail) self.assertEqual(json["detail_video"], expected_song.detail_video) - self.assertEqual(json["has_instrumental"], expected_song.has_instrumental) + + if "has_instrumental" in json: + self.assertEqual( + json["has_instrumental"], + expected_song.instrumental_file or expected_song.instrumental_track, + ) + else: + self.assertEqual(json["instrumental_file"], expected_song.instrumental_file) + self.assertEqual( + json["instrumental_track"], expected_song.instrumental_track + ) # tags expected_tags = expected_song.tags.all() diff --git a/dakara_server/library/tests/test_song.py b/dakara_server/library/tests/test_song.py index 84e4b824..7a5a70ce 100644 --- a/dakara_server/library/tests/test_song.py +++ b/dakara_server/library/tests/test_song.py @@ -380,6 +380,70 @@ def test_post_song_simple(self): self.assertEqual(song.version, "version 1") self.assertEqual(song.detail, "test") self.assertEqual(song.detail_video, "here") + self.assertIsNone(song.instrumental_file) + self.assertIsNone(song.instrumental_track) + + def test_post_song_with_instrumental_file(self): + """Test to create a song an instrumental file.""" + # login as manager + self.authenticate(self.manager) + + # pre assert the amount of songs + self.assertEqual(Song.objects.count(), 2) + + # create a new song + song = { + "title": "Song3", + "filename": "song3", + "directory": "directory", + "duration": 0, + "instrumental_file": "song3.ogg", + "instrumental_track": None, + } + response = self.client.post(self.url, song) + + # assert the response + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + # assert the created song + song = Song.objects.get(title="Song3") + self.assertIsNotNone(song) + self.assertEqual(song.filename, "song3") + self.assertEqual(song.directory, "directory") + self.assertEqual(song.duration, timedelta(0)) + self.assertEqual(song.instrumental_file, "song3.ogg") + self.assertIsNone(song.instrumental_track) + + def test_post_song_with_instrumental_track(self): + """Test to create a song an instrumental track.""" + # login as manager + self.authenticate(self.manager) + + # pre assert the amount of songs + self.assertEqual(Song.objects.count(), 2) + + # create a new song + song = { + "title": "Song3", + "filename": "song3", + "directory": "directory", + "duration": 0, + "instrumental_file": None, + "instrumental_track": 1, + } + response = self.client.post(self.url, song) + + # assert the response + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + # assert the created song + song = Song.objects.get(title="Song3") + self.assertIsNotNone(song) + self.assertEqual(song.filename, "song3") + self.assertEqual(song.directory, "directory") + self.assertEqual(song.duration, timedelta(0)) + self.assertIsNone(song.instrumental_file) + self.assertEqual(song.instrumental_track, 1) def test_post_song_with_tag(self): """Test to create a song with nested tags.""" @@ -677,7 +741,8 @@ def test_put_song_simple(self): "version": "version 1", "detail": "test", "detail_video": "here", - "has_instrumental": True, + "instrumental_file": "song1 new.ogg", + "instrumental_track": None, } response = self.client.put(self.url_song1, song) @@ -694,7 +759,8 @@ def test_put_song_simple(self): self.assertEqual(song.version, "version 1") self.assertEqual(song.detail, "test") self.assertEqual(song.detail_video, "here") - self.assertTrue(song.has_instrumental) + self.assertEqual(song.instrumental_file, "song1 new.ogg") + self.assertIsNone(song.instrumental_track) def test_put_song_embedded(self): """Test to update a song with nested artists, tags and works.""" diff --git a/dakara_server/playlist/tests/base_test.py b/dakara_server/playlist/tests/base_test.py index c740872d..be833f2e 100644 --- a/dakara_server/playlist/tests/base_test.py +++ b/dakara_server/playlist/tests/base_test.py @@ -38,7 +38,7 @@ def create_test_data(self): self.song1.save() self.song1.tags.add(self.tag1) self.song2 = Song( - title="Song2", duration=timedelta(seconds=10), has_instrumental=True + title="Song2", duration=timedelta(seconds=10), instrumental_track=1 ) self.song2.save()