From e394705ecae71129f8c5645027b730020478986b Mon Sep 17 00:00:00 2001 From: sanspie Date: Fri, 12 Sep 2025 15:56:33 +0300 Subject: [PATCH] updated song liked --- akarpov/music/api/serializers.py | 54 +++++++--------- akarpov/music/api/views.py | 28 ++++++++- akarpov/music/services/file.py | 105 ++++++++++++++++++------------- 3 files changed, 110 insertions(+), 77 deletions(-) diff --git a/akarpov/music/api/serializers.py b/akarpov/music/api/serializers.py index f133ef2..0432aa7 100644 --- a/akarpov/music/api/serializers.py +++ b/akarpov/music/api/serializers.py @@ -279,39 +279,33 @@ def validate(self, attrs): def create(self, validated_data): song = Song.objects.get(slug=validated_data["song"]) - if self.context["like"]: - if SongUserRating.objects.filter( - song=song, user=self.context["request"].user - ).exists(): - song_user_rating = SongUserRating.objects.get( - song=song, user=self.context["request"].user - ) - if song_user_rating.like: - song_user_rating.delete() + user = self.context["request"].user + is_like_action = self.context["like"] + + try: + existing_rating = SongUserRating.objects.get(song=song, user=user) + + if is_like_action: + if existing_rating.like: + existing_rating.delete() + return None else: - song_user_rating.like = True - song_user_rating.save() + existing_rating.like = True + existing_rating.save() + return existing_rating else: - song_user_rating = SongUserRating.objects.create( - song=song, user=self.context["request"].user, like=True - ) - else: - if SongUserRating.objects.filter( - song=song, user=self.context["request"].user - ).exists(): - song_user_rating = SongUserRating.objects.get( - song=song, user=self.context["request"].user - ) - if not song_user_rating.like: - song_user_rating.delete() + if not existing_rating.like: + existing_rating.delete() + return None else: - song_user_rating.like = False - song_user_rating.save() - else: - song_user_rating = SongUserRating.objects.create( - song=song, user=self.context["request"].user, like=False - ) - return song_user_rating + existing_rating.like = False + existing_rating.save() + return existing_rating + + except SongUserRating.DoesNotExist: + return SongUserRating.objects.create( + song=song, user=user, like=is_like_action + ) class ListSongSlugsSerializer(serializers.Serializer): diff --git a/akarpov/music/api/views.py b/akarpov/music/api/views.py index 68b9227..4af604c 100644 --- a/akarpov/music/api/views.py +++ b/akarpov/music/api/views.py @@ -323,7 +323,7 @@ def get_serializer_context(self, **kwargs): return context -class LikeSongAPIView(generics.CreateAPIView): +class LikeSongAPIView(generics.GenericAPIView): serializer_class = LikeDislikeSongSerializer permission_classes = [permissions.IsAuthenticated] @@ -335,8 +335,19 @@ def get_serializer_context(self, **kwargs): context["like"] = True return context + def post(self, request, *args, **kwargs): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) -class DislikeSongAPIView(generics.CreateAPIView): + result = serializer.save() + + if result is None: + return Response({"message": "Like removed"}, status=200) + else: + return Response({"message": "Song liked", "liked": result.like}, status=201) + + +class DislikeSongAPIView(generics.GenericAPIView): serializer_class = LikeDislikeSongSerializer permission_classes = [permissions.IsAuthenticated] @@ -348,6 +359,19 @@ def get_serializer_context(self, **kwargs): context["like"] = False return context + def post(self, request, *args, **kwargs): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + + result = serializer.save() + + if result is None: + return Response({"message": "Dislike removed"}, status=200) + else: + return Response( + {"message": "Song disliked", "liked": result.like}, status=201 + ) + class ListAlbumsAPIView(generics.ListAPIView): serializer_class = ListAlbumSerializer diff --git a/akarpov/music/services/file.py b/akarpov/music/services/file.py index 9435283..74096af 100644 --- a/akarpov/music/services/file.py +++ b/akarpov/music/services/file.py @@ -1,4 +1,5 @@ import os +import re import time from io import BytesIO from pathlib import Path @@ -9,7 +10,6 @@ import numpy as np from django.core.files import File from django.db import IntegrityError, transaction -from django.db.models import Count from django.utils.text import slugify from mutagen.id3 import ID3 from mutagen.mp3 import MP3 @@ -259,39 +259,6 @@ def clean_title_for_slug(title: str) -> str: return cleaned.strip() -def process_authors_string(authors_str: str) -> list[str]: - """Split author string into individual author names.""" - if not authors_str: - return [] - - # First split by major separators - authors = [] - for part in authors_str.split("/"): - for subpart in part.split("&"): - # Handle various featuring cases - for feat_marker in [ - " feat.", - " ft.", - " featuring.", - " presents ", - " pres. ", - ]: - if feat_marker in subpart.lower(): - parts = subpart.lower().split(feat_marker, 1) - authors.extend(part.strip() for part in parts) - break - else: - # Handle collaboration markers - if " x " in subpart: - authors.extend(p.strip() for p in subpart.split(" x ")) - else: - authors.append(subpart.strip()) - - # Remove duplicates while preserving order - seen = set() - return [x for x in authors if not (x.lower() in seen or seen.add(x.lower()))] - - def extract_mp3_metadata(file_path: str) -> dict | None: """Extract metadata from MP3 file.""" try: @@ -442,20 +409,64 @@ def get_or_create_album(album_name: str, authors: list[Author]) -> Album | None: raise +def process_authors_string(authors_str: str) -> list[str]: + """Split author string into individual author names.""" + if not authors_str: + return [] + + # First split by major delimiters + authors = [] + + # Split by common delimiters while preserving Asian character sequences + for part in re.split(r"[,&/](?![^\[]*\])", authors_str): + # Clean up each part + cleaned = part.strip() + + # Handle featuring cases + featuring_markers = [" feat.", " ft.", " featuring", " presents ", " pres. "] + for marker in featuring_markers: + if marker in cleaned.lower(): + main_artist, feat_artist = cleaned.lower().split(marker, 1) + authors.extend( + [a.strip() for a in [main_artist, feat_artist] if a.strip()] + ) + break + else: + # Handle collaborations with 'x' or 'X' + if " x " in cleaned or " X " in cleaned: + collab_parts = re.split(" [xX] ", cleaned) + authors.extend(p.strip() for p in collab_parts if p.strip()) + else: + if cleaned: + authors.append(cleaned) + + # Remove duplicates while preserving order + seen = set() + return [x for x in authors if not (x.lower() in seen or seen.add(x.lower()))] + + def check_song_exists(title: str, album: Album | None, authors: list[Author]) -> bool: """Check if a song already exists with the given title, album and authors.""" + if not title: + return False + + # Start with base query query = Song.objects.filter(name__iexact=title) if album: query = query.filter(album=album) if authors: - # Ensure exact author match - query = query.annotate(author_count=Count("authors")).filter( - author_count=len(authors), authors__in=authors - ) + # Get all author IDs + author_ids = {author.id for author in authors} - return query.exists() + # Filter songs that have exactly these authors + for song in query: + song_author_ids = set(song.authors.values_list("id", flat=True)) + if song_author_ids == author_ids: + return True + + return False def load_mp3_directory( @@ -507,10 +518,10 @@ def load_mp3_directory( failed_files.append(str(mp3_path)) continue - # Check for existing song + # Check for existing song with exact matches if check_song_exists(metadata["title"], album, authors): print( - f"Skipping existing song: {metadata['artists']} - {metadata['title']}" + f"Skipping existing song: {' & '.join(author_names)} - {metadata['title']}" ) continue @@ -526,13 +537,17 @@ def load_mp3_directory( f"{album.slug}.png", File(img_file), save=True ) - # Create song with proper slug from file name - file_name = os.path.splitext(os.path.basename(str(mp3_path)))[0] + # Create song with proper slug + _name = ( + metadata["title"] + if metadata["title"] + else os.path.splitext(os.path.basename(str(mp3_path)))[0] + ) song = Song( name=metadata["title"], length=metadata["length"], album=album, - slug=generate_unique_slug(file_name, Song), + slug=generate_unique_slug(_name, Song), creator=User.objects.get(id=user_id) if user_id else None, meta={ "genre": metadata["genre"], @@ -560,7 +575,7 @@ def load_mp3_directory( print(f"Error processing {mp3_path}: {str(e)}") failed_files.append(str(mp3_path)) - # Trigger image cropping for all created/updated objects + # Process image cropping for all created/updated objects print("Processing image cropping...") for author_id in created_authors: