From 1df341006cd8531de97cd3ec64ce0f60a8ca66e0 Mon Sep 17 00:00:00 2001 From: Viner Abubakirov Date: Tue, 6 Jan 2026 16:44:15 +0500 Subject: [PATCH 1/2] =?UTF-8?q?=D0=A1=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20?= =?UTF-8?q?=D0=9F=D0=BB=D0=B5=D0=B9=D0=BB=D0=B8=D1=81=D1=82=20=D0=B8=20?= =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20=D1=80=D0=B5=D0=BA?= =?UTF-8?q?=D0=BE=D0=BC=D0=B5=D0=BD=D0=B4=D0=B0=D1=86=D0=B8=D1=8E=20=D0=BD?= =?UTF-8?q?=D0=B0=20=D0=B3=D0=BB=D0=B0=D0=B2=D0=BD=D1=83=D1=8E=20=D1=81?= =?UTF-8?q?=D1=82=D1=80=D0=B0=D0=BD=D0=B8=D1=86=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- music_storage/core/urls.py | 5 +- music_storage/core/views.py | 22 ++++++--- music_storage/music/admin.py | 26 +++++++++- .../0011_playlist_recommendationplaylist.py | 48 +++++++++++++++++++ music_storage/music/models.py | 37 ++++++++++++++ music_storage/templates/index.html | 8 ++++ 6 files changed, 136 insertions(+), 10 deletions(-) create mode 100644 music_storage/music/migrations/0011_playlist_recommendationplaylist.py 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..01b4000 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.tracks.all().select_related( + "album", "album__artist" + )[:10] + 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..c231990 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,23 @@ 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",) + + +@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/models.py b/music_storage/music/models.py index 4160b8b..fb8b666 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.ForeignKey( + 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..466f82a 100644 --- a/music_storage/templates/index.html +++ b/music_storage/templates/index.html @@ -1 +1,9 @@ {% extends "base.html" %} +{% load static %} + +{% block content %} +
+

Рекомендации:

+ {% include "components/track_list.html" with tracks=tracks %} +
+{% endblock %} -- 2.49.1 From 2cc0614c1e327888efda096835fc5340e8dd0cba Mon Sep 17 00:00:00 2001 From: Viner Abubakirov Date: Tue, 6 Jan 2026 17:06:53 +0500 Subject: [PATCH 2/2] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=20=D0=BC=D0=BE=D0=B4=D0=B5=D0=BB=D1=8C=20RecommendationPlaylis?= =?UTF-8?q?t,=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=B8=D0=BB=20IndexVi?= =?UTF-8?q?ew=20=D0=B8=20=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=B2=D0=B8=D0=B4=20=D0=B0=D0=B4=D0=BC=D0=B8=D0=BD=D0=BA=D0=B8?= =?UTF-8?q?=20Playlist?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- music_storage/core/views.py | 4 ++-- music_storage/music/admin.py | 2 ++ ...2_alter_recommendationplaylist_playlist.py | 19 +++++++++++++++++++ music_storage/music/models.py | 2 +- music_storage/templates/index.html | 1 + 5 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 music_storage/music/migrations/0012_alter_recommendationplaylist_playlist.py diff --git a/music_storage/core/views.py b/music_storage/core/views.py index 01b4000..2693e62 100644 --- a/music_storage/core/views.py +++ b/music_storage/core/views.py @@ -11,9 +11,9 @@ class IndexView(View): if recommendation_playlist := RecommendationPlaylist.objects.filter( is_actual=True ).first(): - tracks = recommendation_playlist.tracks.all().select_related( + tracks = recommendation_playlist.playlist.tracks.all().select_related( "album", "album__artist" - )[:10] + ) else: tracks = [] return render(request, "index.html", {"tracks": tracks}) diff --git a/music_storage/music/admin.py b/music_storage/music/admin.py index c231990..683b901 100644 --- a/music_storage/music/admin.py +++ b/music_storage/music/admin.py @@ -91,6 +91,8 @@ class PlaylistAdmin(admin.ModelAdmin): search_fields = ("name",) list_filter = ("created_at",) + filter_horizontal = ("tracks",) + @admin.register(RecommendationPlaylist) class RecommendationPlaylistAdmin(admin.ModelAdmin): 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 fb8b666..673ef3d 100644 --- a/music_storage/music/models.py +++ b/music_storage/music/models.py @@ -101,7 +101,7 @@ class RecommendationPlaylist(BaseModel): name = models.CharField(max_length=200) description = models.TextField(blank=True) - playlist = models.ForeignKey( + playlist = models.OneToOneField( Playlist, on_delete=models.CASCADE, related_name="recommendations" ) is_actual = models.BooleanField(default=False) diff --git a/music_storage/templates/index.html b/music_storage/templates/index.html index 466f82a..765bfb7 100644 --- a/music_storage/templates/index.html +++ b/music_storage/templates/index.html @@ -6,4 +6,5 @@

Рекомендации:

{% include "components/track_list.html" with tracks=tracks %} +{% include "components/player.html" %} {% endblock %} -- 2.49.1