Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
27 changes: 27 additions & 0 deletions dakara_server/library/migrations/0013_improve_instrumental.py
Original file line number Diff line number Diff line change
@@ -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),
),
]
8 changes: 7 additions & 1 deletion dakara_server/library/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down
17 changes: 15 additions & 2 deletions dakara_server/library/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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):
Expand All @@ -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
Expand Down Expand Up @@ -430,7 +442,8 @@ class Meta:
"artists",
"works",
"file_path",
"has_instrumental",
"instrumental_file",
"instrumental_track",
)

@staticmethod
Expand Down
15 changes: 13 additions & 2 deletions dakara_server/library/tests/base_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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()
Expand Down
70 changes: 68 additions & 2 deletions dakara_server/library/tests/test_song.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down Expand Up @@ -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)

Expand All @@ -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."""
Expand Down
2 changes: 1 addition & 1 deletion dakara_server/playlist/tests/base_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
Loading