diff --git a/music_storage/core/urls.py b/music_storage/core/urls.py index 86e7c54..a8b2e19 100644 --- a/music_storage/core/urls.py +++ b/music_storage/core/urls.py @@ -1,11 +1,10 @@ from django.urls import path -from core.views import index, sentry_debug +from core.views import IndexView app_name = "index" urlpatterns = [ - path("", index, name="main_index"), - path("sentry-debug/", sentry_debug, name="sentry_debug"), + path("", IndexView.as_view(), name="main_index"), ] diff --git a/music_storage/core/views.py b/music_storage/core/views.py index 053e1eb..2693e62 100644 --- a/music_storage/core/views.py +++ b/music_storage/core/views.py @@ -1,9 +1,19 @@ from django.shortcuts import render +from django.views.generic import View +from django.http.request import HttpRequest +from django.shortcuts import get_object_or_404 + +from music.models import RecommendationPlaylist -def index(request, *args, **kwargs): - return render(request, "index.html") - - -def sentry_debug(request): - division_by_zero = 1 / 0 # noqa +class IndexView(View): + def get(self, request: HttpRequest, *args, **kwargs): + if recommendation_playlist := RecommendationPlaylist.objects.filter( + is_actual=True + ).first(): + tracks = recommendation_playlist.playlist.tracks.all().select_related( + "album", "album__artist" + ) + else: + tracks = [] + return render(request, "index.html", {"tracks": tracks}) diff --git a/music_storage/music/admin.py b/music_storage/music/admin.py index 7c22392..683b901 100644 --- a/music_storage/music/admin.py +++ b/music_storage/music/admin.py @@ -5,7 +5,11 @@ from django.http import HttpRequest from django.urls import reverse from django.utils.html import format_html -from music.models import Track, Album, Artist +from music.models import Track +from music.models import Album +from music.models import Artist +from music.models import Playlist +from music.models import RecommendationPlaylist @admin.register(Track) @@ -79,3 +83,25 @@ class ArtistAdmin(admin.ModelAdmin): return format_html('Add Album', url) add_album_link.short_description = "Add Album" + + +@admin.register(Playlist) +class PlaylistAdmin(admin.ModelAdmin): + list_display = ("name", "created_by", "created_at") + search_fields = ("name",) + list_filter = ("created_at",) + + filter_horizontal = ("tracks",) + + +@admin.register(RecommendationPlaylist) +class RecommendationPlaylistAdmin(admin.ModelAdmin): + list_display = ("name", "is_actual", "created_by", "created_at") + search_fields = ("name",) + list_filter = ("is_actual", "created_at") + actions = ["make_actual"] + + @admin.action(description="Set selected recommendation playlists as actual") + def make_actual(self, request: HttpRequest, queryset: Any) -> None: + for recommendation in queryset: + recommendation.switch_actual() diff --git a/music_storage/music/migrations/0011_playlist_recommendationplaylist.py b/music_storage/music/migrations/0011_playlist_recommendationplaylist.py new file mode 100644 index 0000000..48d13c7 --- /dev/null +++ b/music_storage/music/migrations/0011_playlist_recommendationplaylist.py @@ -0,0 +1,48 @@ +# Generated by Django 6.0 on 2026-01-06 11:42 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('music', '0010_album_release_date'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Playlist', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('name', models.CharField(max_length=200)), + ('created_by', models.ForeignKey(blank=True, editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_created', to=settings.AUTH_USER_MODEL)), + ('tracks', models.ManyToManyField(related_name='playlists', to='music.track')), + ('updated_by', models.ForeignKey(blank=True, editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_updated', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='RecommendationPlaylist', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('name', models.CharField(max_length=200)), + ('description', models.TextField(blank=True)), + ('is_actual', models.BooleanField(default=False)), + ('created_by', models.ForeignKey(blank=True, editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_created', to=settings.AUTH_USER_MODEL)), + ('playlist', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='recommendations', to='music.playlist')), + ('updated_by', models.ForeignKey(blank=True, editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_updated', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'constraints': [models.UniqueConstraint(condition=models.Q(('is_actual', True)), fields=('is_actual',), name='unique_actual_recommendation_playlist')], + }, + ), + ] diff --git a/music_storage/music/migrations/0012_alter_recommendationplaylist_playlist.py b/music_storage/music/migrations/0012_alter_recommendationplaylist_playlist.py new file mode 100644 index 0000000..f4d7522 --- /dev/null +++ b/music_storage/music/migrations/0012_alter_recommendationplaylist_playlist.py @@ -0,0 +1,19 @@ +# Generated by Django 6.0 on 2026-01-06 12:04 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('music', '0011_playlist_recommendationplaylist'), + ] + + operations = [ + migrations.AlterField( + model_name='recommendationplaylist', + name='playlist', + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='recommendations', to='music.playlist'), + ), + ] diff --git a/music_storage/music/models.py b/music_storage/music/models.py index 4160b8b..673ef3d 100644 --- a/music_storage/music/models.py +++ b/music_storage/music/models.py @@ -79,3 +79,40 @@ class Track(BaseModel): def __str__(self): return f"{self.album.artist} - {self.title}" + + +class Playlist(BaseModel): + name = models.CharField(max_length=200) + tracks = models.ManyToManyField(Track, related_name="playlists") + + def __str__(self): + return f"Playlist: {self.name}" + + +class RecommendationPlaylist(BaseModel): + class Meta: + constraints = [ + models.UniqueConstraint( + fields=["is_actual"], + condition=models.Q(is_actual=True), + name="unique_actual_recommendation_playlist", + ) + ] + + name = models.CharField(max_length=200) + description = models.TextField(blank=True) + playlist = models.OneToOneField( + Playlist, on_delete=models.CASCADE, related_name="recommendations" + ) + is_actual = models.BooleanField(default=False) + + def __str__(self): + return f"Recommendation Playlist: {self.name}" + + def switch_actual(self): + if not self.is_actual: + (RecommendationPlaylist.objects + .filter(is_actual=True) + .update(is_actual=False)) + self.is_actual = True + self.save() diff --git a/music_storage/templates/index.html b/music_storage/templates/index.html index 94d9808..765bfb7 100644 --- a/music_storage/templates/index.html +++ b/music_storage/templates/index.html @@ -1 +1,10 @@ {% extends "base.html" %} +{% load static %} + +{% block content %} +