updated song liked

This commit is contained in:
Alexander Karpov 2025-09-12 15:56:33 +03:00
parent 03c05d7adf
commit e394705eca
3 changed files with 110 additions and 77 deletions

View File

@ -279,39 +279,33 @@ def validate(self, attrs):
def create(self, validated_data): def create(self, validated_data):
song = Song.objects.get(slug=validated_data["song"]) song = Song.objects.get(slug=validated_data["song"])
if self.context["like"]: user = self.context["request"].user
if SongUserRating.objects.filter( is_like_action = self.context["like"]
song=song, user=self.context["request"].user
).exists(): try:
song_user_rating = SongUserRating.objects.get( existing_rating = SongUserRating.objects.get(song=song, user=user)
song=song, user=self.context["request"].user
if is_like_action:
if existing_rating.like:
existing_rating.delete()
return None
else:
existing_rating.like = True
existing_rating.save()
return existing_rating
else:
if not existing_rating.like:
existing_rating.delete()
return None
else:
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
) )
if song_user_rating.like:
song_user_rating.delete()
else:
song_user_rating.like = True
song_user_rating.save()
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()
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
class ListSongSlugsSerializer(serializers.Serializer): class ListSongSlugsSerializer(serializers.Serializer):

View File

@ -323,7 +323,7 @@ def get_serializer_context(self, **kwargs):
return context return context
class LikeSongAPIView(generics.CreateAPIView): class LikeSongAPIView(generics.GenericAPIView):
serializer_class = LikeDislikeSongSerializer serializer_class = LikeDislikeSongSerializer
permission_classes = [permissions.IsAuthenticated] permission_classes = [permissions.IsAuthenticated]
@ -335,8 +335,19 @@ def get_serializer_context(self, **kwargs):
context["like"] = True context["like"] = True
return context 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 serializer_class = LikeDislikeSongSerializer
permission_classes = [permissions.IsAuthenticated] permission_classes = [permissions.IsAuthenticated]
@ -348,6 +359,19 @@ def get_serializer_context(self, **kwargs):
context["like"] = False context["like"] = False
return context 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): class ListAlbumsAPIView(generics.ListAPIView):
serializer_class = ListAlbumSerializer serializer_class = ListAlbumSerializer

View File

@ -1,4 +1,5 @@
import os import os
import re
import time import time
from io import BytesIO from io import BytesIO
from pathlib import Path from pathlib import Path
@ -9,7 +10,6 @@
import numpy as np import numpy as np
from django.core.files import File from django.core.files import File
from django.db import IntegrityError, transaction from django.db import IntegrityError, transaction
from django.db.models import Count
from django.utils.text import slugify from django.utils.text import slugify
from mutagen.id3 import ID3 from mutagen.id3 import ID3
from mutagen.mp3 import MP3 from mutagen.mp3 import MP3
@ -259,39 +259,6 @@ def clean_title_for_slug(title: str) -> str:
return cleaned.strip() 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: def extract_mp3_metadata(file_path: str) -> dict | None:
"""Extract metadata from MP3 file.""" """Extract metadata from MP3 file."""
try: try:
@ -442,20 +409,64 @@ def get_or_create_album(album_name: str, authors: list[Author]) -> Album | None:
raise 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: 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.""" """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) query = Song.objects.filter(name__iexact=title)
if album: if album:
query = query.filter(album=album) query = query.filter(album=album)
if authors: if authors:
# Ensure exact author match # Get all author IDs
query = query.annotate(author_count=Count("authors")).filter( author_ids = {author.id for author in authors}
author_count=len(authors), authors__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( def load_mp3_directory(
@ -507,10 +518,10 @@ def load_mp3_directory(
failed_files.append(str(mp3_path)) failed_files.append(str(mp3_path))
continue continue
# Check for existing song # Check for existing song with exact matches
if check_song_exists(metadata["title"], album, authors): if check_song_exists(metadata["title"], album, authors):
print( print(
f"Skipping existing song: {metadata['artists']} - {metadata['title']}" f"Skipping existing song: {' & '.join(author_names)} - {metadata['title']}"
) )
continue continue
@ -526,13 +537,17 @@ def load_mp3_directory(
f"{album.slug}.png", File(img_file), save=True f"{album.slug}.png", File(img_file), save=True
) )
# Create song with proper slug from file name # Create song with proper slug
file_name = os.path.splitext(os.path.basename(str(mp3_path)))[0] _name = (
metadata["title"]
if metadata["title"]
else os.path.splitext(os.path.basename(str(mp3_path)))[0]
)
song = Song( song = Song(
name=metadata["title"], name=metadata["title"],
length=metadata["length"], length=metadata["length"],
album=album, 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, creator=User.objects.get(id=user_id) if user_id else None,
meta={ meta={
"genre": metadata["genre"], "genre": metadata["genre"],
@ -560,7 +575,7 @@ def load_mp3_directory(
print(f"Error processing {mp3_path}: {str(e)}") print(f"Error processing {mp3_path}: {str(e)}")
failed_files.append(str(mp3_path)) 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...") print("Processing image cropping...")
for author_id in created_authors: for author_id in created_authors: