mirror of
https://github.com/Alexander-D-Karpov/akarpov
synced 2024-12-01 04:03:44 +03:00
Compare commits
2 Commits
b45ffbf915
...
7b2b413966
Author | SHA1 | Date | |
---|---|---|---|
|
7b2b413966 | ||
|
7ad2ab043a |
|
@ -1,17 +1,9 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from akarpov.music.models import (
|
from akarpov.music.models import Album, Author, Playlist, PlaylistSong, Song
|
||||||
Album,
|
|
||||||
Author,
|
|
||||||
Playlist,
|
|
||||||
PlaylistSong,
|
|
||||||
Song,
|
|
||||||
SongUserRating,
|
|
||||||
)
|
|
||||||
|
|
||||||
admin.site.register(Author)
|
admin.site.register(Author)
|
||||||
admin.site.register(Album)
|
admin.site.register(Album)
|
||||||
admin.site.register(Song)
|
admin.site.register(Song)
|
||||||
admin.site.register(Playlist)
|
admin.site.register(Playlist)
|
||||||
admin.site.register(PlaylistSong)
|
admin.site.register(PlaylistSong)
|
||||||
admin.site.register(SongUserRating)
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ class SongSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
@extend_schema_field(serializers.BooleanField)
|
@extend_schema_field(serializers.BooleanField)
|
||||||
def get_liked(self, obj):
|
def get_liked(self, obj):
|
||||||
if "request" in self.context and self.context["request"]:
|
if "request" in self.context:
|
||||||
if self.context["request"].user.is_authenticated:
|
if self.context["request"].user.is_authenticated:
|
||||||
return SongUserRating.objects.filter(
|
return SongUserRating.objects.filter(
|
||||||
song=obj, user=self.context["request"].user, like=True
|
song=obj, user=self.context["request"].user, like=True
|
||||||
|
@ -61,8 +61,8 @@ class Meta:
|
||||||
|
|
||||||
|
|
||||||
class ListSongSerializer(SetUserModelSerializer):
|
class ListSongSerializer(SetUserModelSerializer):
|
||||||
album = serializers.SerializerMethodField(method_name="get_album")
|
album = AlbumSerializer(read_only=True)
|
||||||
authors = serializers.SerializerMethodField(method_name="get_authors")
|
authors = AuthorSerializer(many=True, read_only=True)
|
||||||
liked = serializers.SerializerMethodField(method_name="get_liked")
|
liked = serializers.SerializerMethodField(method_name="get_liked")
|
||||||
|
|
||||||
@extend_schema_field(serializers.BooleanField)
|
@extend_schema_field(serializers.BooleanField)
|
||||||
|
@ -73,20 +73,6 @@ def get_liked(self, obj):
|
||||||
return obj.id in self.context["likes_ids"]
|
return obj.id in self.context["likes_ids"]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@extend_schema_field(AlbumSerializer)
|
|
||||||
def get_album(self, obj):
|
|
||||||
if obj.album:
|
|
||||||
return AlbumSerializer(Album.objects.cache().get(id=obj.album_id)).data
|
|
||||||
return None
|
|
||||||
|
|
||||||
@extend_schema_field(AuthorSerializer(many=True))
|
|
||||||
def get_authors(self, obj):
|
|
||||||
if obj.authors:
|
|
||||||
return AuthorSerializer(
|
|
||||||
Author.objects.cache().filter(songs__id=obj.id), many=True
|
|
||||||
).data
|
|
||||||
return None
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Song
|
model = Song
|
||||||
fields = [
|
fields = [
|
||||||
|
@ -239,26 +225,10 @@ class Meta:
|
||||||
|
|
||||||
class FullAlbumSerializer(serializers.ModelSerializer):
|
class FullAlbumSerializer(serializers.ModelSerializer):
|
||||||
songs = ListSongSerializer(many=True, read_only=True)
|
songs = ListSongSerializer(many=True, read_only=True)
|
||||||
artists = serializers.SerializerMethodField("get_artists")
|
|
||||||
|
|
||||||
@extend_schema_field(AuthorSerializer(many=True))
|
|
||||||
def get_artists(self, obj):
|
|
||||||
artists = []
|
|
||||||
qs = Author.objects.cache().filter(
|
|
||||||
songs__id__in=obj.songs.cache().all().values("id").distinct()
|
|
||||||
)
|
|
||||||
for artist in qs:
|
|
||||||
if artist not in artists:
|
|
||||||
artists.append(artist)
|
|
||||||
|
|
||||||
return AuthorSerializer(
|
|
||||||
artists,
|
|
||||||
many=True,
|
|
||||||
).data
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Album
|
model = Album
|
||||||
fields = ["name", "link", "image", "songs", "artists"]
|
fields = ["name", "link", "image", "songs"]
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
"link": {"read_only": True},
|
"link": {"read_only": True},
|
||||||
"image": {"read_only": True},
|
"image": {"read_only": True},
|
||||||
|
@ -267,27 +237,10 @@ class Meta:
|
||||||
|
|
||||||
class FullAuthorSerializer(serializers.ModelSerializer):
|
class FullAuthorSerializer(serializers.ModelSerializer):
|
||||||
songs = ListSongSerializer(many=True, read_only=True)
|
songs = ListSongSerializer(many=True, read_only=True)
|
||||||
albums = serializers.SerializerMethodField(method_name="get_albums")
|
|
||||||
|
|
||||||
@extend_schema_field(AlbumSerializer(many=True))
|
|
||||||
def get_albums(self, obj):
|
|
||||||
qs = Album.objects.cache().filter(
|
|
||||||
songs__id__in=obj.songs.cache().all().values("id").distinct()
|
|
||||||
)
|
|
||||||
albums = []
|
|
||||||
for album in qs:
|
|
||||||
# TODO: rewrite to filter
|
|
||||||
if album not in albums:
|
|
||||||
albums.append(album)
|
|
||||||
|
|
||||||
return AlbumSerializer(
|
|
||||||
albums,
|
|
||||||
many=True,
|
|
||||||
).data
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Author
|
model = Author
|
||||||
fields = ["name", "link", "image", "songs", "albums"]
|
fields = ["name", "link", "image", "songs"]
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
"link": {"read_only": True},
|
"link": {"read_only": True},
|
||||||
"image": {"read_only": True},
|
"image": {"read_only": True},
|
||||||
|
|
|
@ -9,11 +9,8 @@
|
||||||
ListCreatePlaylistAPIView,
|
ListCreatePlaylistAPIView,
|
||||||
ListCreateSongAPIView,
|
ListCreateSongAPIView,
|
||||||
ListDislikedSongsAPIView,
|
ListDislikedSongsAPIView,
|
||||||
ListenSongAPIView,
|
|
||||||
ListLikedSongsAPIView,
|
ListLikedSongsAPIView,
|
||||||
ListPublicPlaylistAPIView,
|
ListPublicPlaylistAPIView,
|
||||||
ListSongPlaylistsAPIView,
|
|
||||||
ListUserListenedSongsAPIView,
|
|
||||||
RemoveSongFromPlaylistAPIView,
|
RemoveSongFromPlaylistAPIView,
|
||||||
RetrieveUpdateDestroyAlbumAPIView,
|
RetrieveUpdateDestroyAlbumAPIView,
|
||||||
RetrieveUpdateDestroyAuthorAPIView,
|
RetrieveUpdateDestroyAuthorAPIView,
|
||||||
|
@ -45,25 +42,10 @@
|
||||||
RetrieveUpdateDestroySongAPIView.as_view(),
|
RetrieveUpdateDestroySongAPIView.as_view(),
|
||||||
name="retrieve_update_delete_song",
|
name="retrieve_update_delete_song",
|
||||||
),
|
),
|
||||||
path(
|
path("song/like/", LikeSongAPIView.as_view()),
|
||||||
"song/<str:slug>/playlists/",
|
path("song/dislike/", DislikeSongAPIView.as_view()),
|
||||||
ListSongPlaylistsAPIView.as_view(),
|
path("playlists/add/", AddSongToPlaylistAPIView.as_view()),
|
||||||
name="list_song_playlists",
|
path("playlists/remove/", RemoveSongFromPlaylistAPIView.as_view()),
|
||||||
),
|
|
||||||
path("song/listen/", ListenSongAPIView.as_view(), name="listen-song"),
|
|
||||||
path("song/listened/", ListUserListenedSongsAPIView.as_view(), name="listened"),
|
|
||||||
path("song/like/", LikeSongAPIView.as_view(), name="like-song"),
|
|
||||||
path("song/dislike/", DislikeSongAPIView.as_view(), name="dislike-song"),
|
|
||||||
path(
|
|
||||||
"playlists/add/",
|
|
||||||
AddSongToPlaylistAPIView.as_view(),
|
|
||||||
name="add-song-to-playlists",
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
"playlists/remove/",
|
|
||||||
RemoveSongFromPlaylistAPIView.as_view(),
|
|
||||||
name="playlists-remove",
|
|
||||||
),
|
|
||||||
path("albums/", ListAlbumsAPIView.as_view(), name="list_albums"),
|
path("albums/", ListAlbumsAPIView.as_view(), name="list_albums"),
|
||||||
path(
|
path(
|
||||||
"albums/<str:slug>",
|
"albums/<str:slug>",
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
from drf_spectacular.utils import OpenApiExample, OpenApiParameter, extend_schema
|
|
||||||
from rest_framework import generics, permissions
|
from rest_framework import generics, permissions
|
||||||
from rest_framework.response import Response
|
|
||||||
|
|
||||||
from akarpov.common.api.pagination import StandardResultsSetPagination
|
from akarpov.common.api.pagination import StandardResultsSetPagination
|
||||||
from akarpov.common.api.permissions import IsAdminOrReadOnly, IsCreatorOrReadOnly
|
from akarpov.common.api.permissions import IsAdminOrReadOnly, IsCreatorOrReadOnly
|
||||||
|
@ -17,15 +15,7 @@
|
||||||
PlaylistSerializer,
|
PlaylistSerializer,
|
||||||
SongSerializer,
|
SongSerializer,
|
||||||
)
|
)
|
||||||
from akarpov.music.models import (
|
from akarpov.music.models import Album, Author, Playlist, Song, SongUserRating
|
||||||
Album,
|
|
||||||
Author,
|
|
||||||
Playlist,
|
|
||||||
Song,
|
|
||||||
SongUserRating,
|
|
||||||
UserListenHistory,
|
|
||||||
)
|
|
||||||
from akarpov.music.tasks import listen_to_song
|
|
||||||
|
|
||||||
|
|
||||||
class LikedSongsContextMixin(generics.GenericAPIView):
|
class LikedSongsContextMixin(generics.GenericAPIView):
|
||||||
|
@ -83,76 +73,19 @@ class ListCreateSongAPIView(LikedSongsContextMixin, generics.ListCreateAPIView):
|
||||||
pagination_class = StandardResultsSetPagination
|
pagination_class = StandardResultsSetPagination
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
qs = Song.objects.cache()
|
|
||||||
|
|
||||||
if "sort" in self.request.query_params:
|
|
||||||
sorts = self.request.query_params["sort"].split(",")
|
|
||||||
for sort in sorts:
|
|
||||||
pref = "-"
|
|
||||||
if sort.startswith("-"):
|
|
||||||
pref = ""
|
|
||||||
if sort == "likes":
|
|
||||||
qs = qs.order_by(pref + "likes")
|
|
||||||
elif sort == "length":
|
|
||||||
qs = qs.order_by(pref + "length")
|
|
||||||
elif sort == "played":
|
|
||||||
qs = qs.order_by(pref + "played")
|
|
||||||
elif sort == "uploaded":
|
|
||||||
qs = qs.order_by(pref + "created")
|
|
||||||
|
|
||||||
if self.request.user.is_authenticated:
|
if self.request.user.is_authenticated:
|
||||||
return qs.exclude(
|
return (
|
||||||
|
Song.objects.all()
|
||||||
|
.exclude(
|
||||||
id__in=SongUserRating.objects.filter(
|
id__in=SongUserRating.objects.filter(
|
||||||
user=self.request.user,
|
user=self.request.user,
|
||||||
like=False,
|
like=False,
|
||||||
).values_list("song_id", flat=True)
|
).values_list("song_id", flat=True)
|
||||||
)
|
)
|
||||||
return qs
|
.prefetch_related("authors")
|
||||||
|
.select_related("album")
|
||||||
@extend_schema(
|
|
||||||
parameters=[
|
|
||||||
OpenApiParameter(
|
|
||||||
name="sort",
|
|
||||||
description="Sorting algorithm",
|
|
||||||
required=False,
|
|
||||||
type=str,
|
|
||||||
examples=[
|
|
||||||
OpenApiExample(
|
|
||||||
"Default",
|
|
||||||
description="by date added",
|
|
||||||
value=None,
|
|
||||||
),
|
|
||||||
OpenApiExample(
|
|
||||||
"played",
|
|
||||||
description="by total times played",
|
|
||||||
value="played",
|
|
||||||
),
|
|
||||||
OpenApiExample(
|
|
||||||
"likes",
|
|
||||||
description="by total likes",
|
|
||||||
value="likes",
|
|
||||||
),
|
|
||||||
OpenApiExample(
|
|
||||||
"likes reversed",
|
|
||||||
description="by total likes",
|
|
||||||
value="-likes",
|
|
||||||
),
|
|
||||||
OpenApiExample(
|
|
||||||
"length",
|
|
||||||
description="by track length",
|
|
||||||
value="length",
|
|
||||||
),
|
|
||||||
OpenApiExample(
|
|
||||||
"uploaded",
|
|
||||||
description="by date uploaded",
|
|
||||||
value="uploaded",
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
def get(self, request, *args, **kwargs):
|
return Song.objects.all().prefetch_related("authors").select_related("album")
|
||||||
return self.list(request, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class RetrieveUpdateDestroySongAPIView(generics.RetrieveUpdateDestroyAPIView):
|
class RetrieveUpdateDestroySongAPIView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
|
@ -209,11 +142,16 @@ def get_serializer_context(self):
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return Song.objects.cache().filter(
|
return (
|
||||||
|
Song.objects.cache()
|
||||||
|
.filter(
|
||||||
id__in=SongUserRating.objects.cache()
|
id__in=SongUserRating.objects.cache()
|
||||||
.filter(user=self.request.user, like=True)
|
.filter(user=self.request.user, like=True)
|
||||||
.values_list("song_id", flat=False)
|
.values_list("song_id", flat=False)
|
||||||
)
|
)
|
||||||
|
.prefetch_related("authors")
|
||||||
|
.select_related("album")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class AddSongToPlaylistAPIView(generics.CreateAPIView):
|
class AddSongToPlaylistAPIView(generics.CreateAPIView):
|
||||||
|
@ -272,7 +210,9 @@ class ListAlbumsAPIView(generics.ListAPIView):
|
||||||
serializer_class = AlbumSerializer
|
serializer_class = AlbumSerializer
|
||||||
pagination_class = StandardResultsSetPagination
|
pagination_class = StandardResultsSetPagination
|
||||||
permission_classes = [permissions.AllowAny]
|
permission_classes = [permissions.AllowAny]
|
||||||
queryset = Album.objects.cache().all()
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return Album.objects.all()
|
||||||
|
|
||||||
|
|
||||||
class RetrieveUpdateDestroyAlbumAPIView(
|
class RetrieveUpdateDestroyAlbumAPIView(
|
||||||
|
@ -282,14 +222,15 @@ class RetrieveUpdateDestroyAlbumAPIView(
|
||||||
lookup_url_kwarg = "slug"
|
lookup_url_kwarg = "slug"
|
||||||
permission_classes = [IsAdminOrReadOnly]
|
permission_classes = [IsAdminOrReadOnly]
|
||||||
serializer_class = FullAlbumSerializer
|
serializer_class = FullAlbumSerializer
|
||||||
queryset = Album.objects.cache().all()
|
|
||||||
|
|
||||||
|
|
||||||
class ListAuthorsAPIView(generics.ListAPIView):
|
class ListAuthorsAPIView(generics.ListAPIView):
|
||||||
serializer_class = AuthorSerializer
|
serializer_class = AuthorSerializer
|
||||||
pagination_class = StandardResultsSetPagination
|
pagination_class = StandardResultsSetPagination
|
||||||
permission_classes = [permissions.AllowAny]
|
permission_classes = [permissions.AllowAny]
|
||||||
queryset = Author.objects.cache().all()
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return Author.objects.all()
|
||||||
|
|
||||||
|
|
||||||
class RetrieveUpdateDestroyAuthorAPIView(
|
class RetrieveUpdateDestroyAuthorAPIView(
|
||||||
|
@ -299,46 +240,3 @@ class RetrieveUpdateDestroyAuthorAPIView(
|
||||||
lookup_url_kwarg = "slug"
|
lookup_url_kwarg = "slug"
|
||||||
permission_classes = [IsAdminOrReadOnly]
|
permission_classes = [IsAdminOrReadOnly]
|
||||||
serializer_class = FullAuthorSerializer
|
serializer_class = FullAuthorSerializer
|
||||||
queryset = Author.objects.cache().all()
|
|
||||||
|
|
||||||
|
|
||||||
class ListenSongAPIView(generics.GenericAPIView):
|
|
||||||
serializer_class = LikeDislikeSongSerializer
|
|
||||||
permission_classes = [permissions.AllowAny]
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
return Song.objects.cache()
|
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
|
||||||
serializer = self.get_serializer(data=request.data)
|
|
||||||
serializer.is_valid(raise_exception=False)
|
|
||||||
data = serializer.validated_data
|
|
||||||
|
|
||||||
try:
|
|
||||||
song = Song.objects.cache().get(slug=data["song"])
|
|
||||||
except Song.DoesNotExist:
|
|
||||||
return Response(status=404)
|
|
||||||
if self.request.user.is_authenticated:
|
|
||||||
listen_to_song.apply_async(
|
|
||||||
kwargs={"song_id": song.id, "user_id": self.request.user.id},
|
|
||||||
countdown=2,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
listen_to_song.apply_async(
|
|
||||||
kwargs={"song_id": song.id},
|
|
||||||
countdown=2,
|
|
||||||
)
|
|
||||||
return Response(status=201)
|
|
||||||
|
|
||||||
|
|
||||||
class ListUserListenedSongsAPIView(generics.ListAPIView):
|
|
||||||
serializer_class = ListSongSerializer
|
|
||||||
pagination_class = StandardResultsSetPagination
|
|
||||||
permission_classes = [permissions.IsAuthenticated]
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
return Song.objects.cache().filter(
|
|
||||||
id__in=UserListenHistory.objects.cache()
|
|
||||||
.filter(user=self.request.user)
|
|
||||||
.values_list("song_id", flat=True)
|
|
||||||
)
|
|
||||||
|
|
|
@ -1,54 +0,0 @@
|
||||||
# Generated by Django 4.2.7 on 2023-12-16 17:53
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
||||||
("music", "0010_song_likes_songuserrating"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="playlist",
|
|
||||||
name="private",
|
|
||||||
field=models.BooleanField(default=True),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="UserListenHistory",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"id",
|
|
||||||
models.BigAutoField(
|
|
||||||
auto_created=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
verbose_name="ID",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
("created", models.DateTimeField(auto_now_add=True)),
|
|
||||||
(
|
|
||||||
"song",
|
|
||||||
models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
related_name="listeners",
|
|
||||||
to="music.song",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"user",
|
|
||||||
models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
related_name="songs_listened",
|
|
||||||
to=settings.AUTH_USER_MODEL,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
"ordering": ["-created"],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -147,14 +147,3 @@ def __str__(self):
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ["song", "user"]
|
unique_together = ["song", "user"]
|
||||||
ordering = ["-created"]
|
ordering = ["-created"]
|
||||||
|
|
||||||
|
|
||||||
class UserListenHistory(models.Model):
|
|
||||||
user = models.ForeignKey(
|
|
||||||
"users.User", related_name="songs_listened", on_delete=models.CASCADE
|
|
||||||
)
|
|
||||||
song = models.ForeignKey("Song", related_name="listeners", on_delete=models.CASCADE)
|
|
||||||
created = models.DateTimeField(auto_now_add=True)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
ordering = ["-created"]
|
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from deep_translator import GoogleTranslator
|
|
||||||
from django.core.files import File
|
from django.core.files import File
|
||||||
from django.utils.text import slugify
|
|
||||||
from mutagen import File as MutagenFile
|
from mutagen import File as MutagenFile
|
||||||
from mutagen.id3 import APIC, ID3, TCON, TORY, TextFrame
|
from mutagen.id3 import APIC, ID3, TCON, TORY, TextFrame
|
||||||
from mutagen.mp3 import MP3
|
from mutagen.mp3 import MP3
|
||||||
|
@ -74,32 +72,16 @@ def load_track(
|
||||||
if kwargs:
|
if kwargs:
|
||||||
song.meta = kwargs
|
song.meta = kwargs
|
||||||
|
|
||||||
new_file_name = (
|
|
||||||
str(
|
|
||||||
slugify(
|
|
||||||
GoogleTranslator(source="auto", target="en").translate(
|
|
||||||
f"{song.name} {' '.join([x.name for x in song.authors])}",
|
|
||||||
target_language="en",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
+ ".mp3"
|
|
||||||
)
|
|
||||||
|
|
||||||
if image_path:
|
if image_path:
|
||||||
with open(path, "rb") as file, open(image_path, "rb") as image:
|
with open(path, "rb") as file, open(image_path, "rb") as image:
|
||||||
song.image = File(image, name=image_path.split("/")[-1])
|
song.image = File(image, name=image_path.split("/")[-1])
|
||||||
song.file = File(file, name=new_file_name)
|
song.file = File(file, name=path.split("/")[-1])
|
||||||
song.save()
|
song.save()
|
||||||
else:
|
else:
|
||||||
with open(path, "rb") as file:
|
with open(path, "rb") as file:
|
||||||
song.file = File(file, name=new_file_name)
|
song.file = File(file, name=path.split("/")[-1])
|
||||||
song.save()
|
song.save()
|
||||||
|
|
||||||
if not album.image and song.image:
|
|
||||||
album.image = song.image
|
|
||||||
album.save()
|
|
||||||
|
|
||||||
if authors:
|
if authors:
|
||||||
song.authors.set(authors)
|
song.authors.set(authors)
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
from django.db.models.signals import post_delete, post_save, pre_save
|
from django.db.models.signals import post_delete, post_save, pre_save
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
|
|
||||||
from akarpov.music.models import Album, Author, PlaylistSong, Song, SongUserRating
|
from akarpov.music.models import PlaylistSong, Song, SongUserRating
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_delete, sender=Song)
|
@receiver(post_delete, sender=Song)
|
||||||
|
@ -13,20 +13,6 @@ def auto_delete_file_on_delete(sender, instance, **kwargs):
|
||||||
os.remove(instance.file.path)
|
os.remove(instance.file.path)
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=Author)
|
|
||||||
def author_create(sender, instance, created, **kwargs):
|
|
||||||
if created:
|
|
||||||
# TODO: add logic to retrieve author info here
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=Album)
|
|
||||||
def album_create(sender, instance, created, **kwargs):
|
|
||||||
if created:
|
|
||||||
# TODO: add logic to retrieve author info here
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save)
|
@receiver(post_save)
|
||||||
def send_que_status(sender, instance, created, **kwargs):
|
def send_que_status(sender, instance, created, **kwargs):
|
||||||
...
|
...
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
from asgiref.sync import async_to_sync
|
from asgiref.sync import async_to_sync
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
from channels.layers import get_channel_layer
|
from channels.layers import get_channel_layer
|
||||||
from django.utils.timezone import now
|
|
||||||
from pytube import Channel, Playlist
|
from pytube import Channel, Playlist
|
||||||
|
|
||||||
from akarpov.music.api.serializers import SongSerializer
|
from akarpov.music.api.serializers import SongSerializer
|
||||||
from akarpov.music.models import RadioSong, Song, UserListenHistory
|
from akarpov.music.models import RadioSong, Song
|
||||||
from akarpov.music.services import yandex, youtube
|
from akarpov.music.services import yandex, youtube
|
||||||
from akarpov.music.services.file import load_dir, load_file
|
from akarpov.music.services.file import load_dir, load_file
|
||||||
from akarpov.utils.celery import get_scheduled_tasks_name
|
from akarpov.utils.celery import get_scheduled_tasks_name
|
||||||
|
@ -87,29 +86,3 @@ def start_next_song(previous_ids: list):
|
||||||
countdown=song.length,
|
countdown=song.length,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
|
||||||
def listen_to_song(song_id, user_id=None):
|
|
||||||
# protection from multiple listen,
|
|
||||||
# check that last listen by user was more than the length of the song
|
|
||||||
# and last listened song is not the same
|
|
||||||
s = Song.objects.get(id=song_id)
|
|
||||||
s.played += 1
|
|
||||||
s.save(update_fields=["played"])
|
|
||||||
if user_id:
|
|
||||||
try:
|
|
||||||
last_listen = UserListenHistory.objects.filter(user_id=user_id).latest("id")
|
|
||||||
except UserListenHistory.DoesNotExist:
|
|
||||||
last_listen = None
|
|
||||||
if (
|
|
||||||
last_listen
|
|
||||||
and last_listen.song_id == song_id
|
|
||||||
or last_listen.created + s.length > now()
|
|
||||||
):
|
|
||||||
return
|
|
||||||
UserListenHistory.objects.create(
|
|
||||||
user_id=user_id,
|
|
||||||
song_id=song_id,
|
|
||||||
)
|
|
||||||
return song_id
|
|
||||||
|
|
|
@ -159,35 +159,6 @@
|
||||||
const toastContainer = document.getElementById('toastContainer')
|
const toastContainer = document.getElementById('toastContainer')
|
||||||
|
|
||||||
|
|
||||||
function sleep(ms) {
|
|
||||||
return new Promise(resolve => setTimeout(resolve, ms));
|
|
||||||
}
|
|
||||||
|
|
||||||
function timeSince(date) {
|
|
||||||
let seconds = Math.floor((new Date() - date) / 1000);
|
|
||||||
let interval = seconds / 31536000;
|
|
||||||
if (interval > 1) {
|
|
||||||
return Math.floor(interval) + " years";
|
|
||||||
}
|
|
||||||
interval = seconds / 2592000;
|
|
||||||
if (interval > 1) {
|
|
||||||
return Math.floor(interval) + " months";
|
|
||||||
}
|
|
||||||
interval = seconds / 86400;
|
|
||||||
if (interval > 1) {
|
|
||||||
return Math.floor(interval) + " days";
|
|
||||||
}
|
|
||||||
interval = seconds / 3600;
|
|
||||||
if (interval > 1) {
|
|
||||||
return Math.floor(interval) + " hours";
|
|
||||||
}
|
|
||||||
interval = seconds / 60;
|
|
||||||
if (interval > 1) {
|
|
||||||
return Math.floor(interval) + " minutes";
|
|
||||||
}
|
|
||||||
return Math.floor(seconds) + " seconds";
|
|
||||||
}
|
|
||||||
|
|
||||||
let fn = async function(event) {
|
let fn = async function(event) {
|
||||||
let data = JSON.parse(event.data)
|
let data = JSON.parse(event.data)
|
||||||
const toast = document.createElement("div")
|
const toast = document.createElement("div")
|
||||||
|
|
|
@ -73,10 +73,10 @@
|
||||||
"auth.*": {"ops": ("fetch", "get"), "timeout": 60 * 2},
|
"auth.*": {"ops": ("fetch", "get"), "timeout": 60 * 2},
|
||||||
"blog.post": {"ops": ("fetch", "get"), "timeout": 20 * 15},
|
"blog.post": {"ops": ("fetch", "get"), "timeout": 20 * 15},
|
||||||
"themes.theme": {"ops": ("fetch", "get"), "timeout": 60 * 60},
|
"themes.theme": {"ops": ("fetch", "get"), "timeout": 60 * 60},
|
||||||
"gallery.*": {"ops": ("fetch", "get", "list"), "timeout": 60 * 15},
|
"gallery.*": {"ops": "all", "timeout": 60 * 15},
|
||||||
"files.*": {"ops": ("fetch", "get", "list"), "timeout": 60},
|
"files.*": {"ops": ("fetch", "get"), "timeout": 60},
|
||||||
"auth.permission": {"ops": "all", "timeout": 60 * 15},
|
"auth.permission": {"ops": "all", "timeout": 60 * 15},
|
||||||
"music.*": {"ops": ("fetch", "get", "list"), "timeout": 60 * 15},
|
"music.*": {"ops": "all", "timeout": 60 * 15},
|
||||||
}
|
}
|
||||||
CACHEOPS_REDIS = env.str("REDIS_URL")
|
CACHEOPS_REDIS = env.str("REDIS_URL")
|
||||||
|
|
||||||
|
|
17
poetry.lock
generated
17
poetry.lock
generated
|
@ -1327,21 +1327,6 @@ files = [
|
||||||
{file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"},
|
{file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "deep-translator"
|
|
||||||
version = "1.4.2"
|
|
||||||
description = "A flexible python tool to translate between different languages in a simple way."
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.0"
|
|
||||||
files = [
|
|
||||||
{file = "deep_translator-1.4.2-py2.py3-none-any.whl", hash = "sha256:85ec4cf52b9a48bdabc992eb931545abd2a0c63e092e50773d14f7bc506e6e89"},
|
|
||||||
{file = "deep_translator-1.4.2.tar.gz", hash = "sha256:1af1feaaa351904e2db10d5fa86a09009839ad2252c0981150350d962d6b3dc4"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
beautifulsoup4 = "*"
|
|
||||||
requests = "*"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "defusedxml"
|
name = "defusedxml"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
|
@ -6719,4 +6704,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.11"
|
python-versions = "^3.11"
|
||||||
content-hash = "f3dd82fbbee6bd4accfca7000c9de3102b49478dd18365887a56590c2a9e84d7"
|
content-hash = "b3ff2e53b9e9ee3a3a3c5985e09987ca3114fb34869d0c76b771535d4d38f2ac"
|
||||||
|
|
|
@ -114,7 +114,6 @@ pydantic-settings = "^2.0.3"
|
||||||
django-elasticsearch-dsl = "^8.0"
|
django-elasticsearch-dsl = "^8.0"
|
||||||
elasticsearch-dsl = "^8.11.0"
|
elasticsearch-dsl = "^8.11.0"
|
||||||
numpy = "1.25.2"
|
numpy = "1.25.2"
|
||||||
deep-translator = "1.4.2"
|
|
||||||
|
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
|
|
Loading…
Reference in New Issue
Block a user