adds vote posting

also moves some validation code to the model
This commit is contained in:
2025-05-22 15:17:35 +02:00
parent 501044eef2
commit 3de8234fb9
7 changed files with 49 additions and 10 deletions
+2 -1
View File
@@ -1,10 +1,11 @@
from django.db import models from django.db import models
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.validators import MinValueValidator, MaxValueValidator
class Vote(models.Model): class Vote(models.Model):
class Meta: class Meta:
unique_together = ["user", "track"] unique_together = ["user", "track"]
track = models.ForeignKey('Track', on_delete=models.CASCADE) track = models.ForeignKey('Track', on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE) user = models.ForeignKey(User, on_delete=models.CASCADE)
points = models.IntegerField() points = models.IntegerField(validators=[MinValueValidator(1), MaxValueValidator(5)])
skipped = models.BooleanField(default=False) skipped = models.BooleanField(default=False)
+1 -1
View File
@@ -3,4 +3,4 @@ from .artist import ArtistSerializer
from .user import UserSerializer from .user import UserSerializer
from .album import AlbumSerializer from .album import AlbumSerializer
from .profile import ProfileSerializer, BackgroundSerializer from .profile import ProfileSerializer, BackgroundSerializer
from .vote import VoteSerializer from .vote import *
+17 -1
View File
@@ -1,5 +1,5 @@
from rest_framework import serializers from rest_framework import serializers
from playlist.models import Vote from playlist.models import Vote, Track
class VoteSerializer(serializers.HyperlinkedModelSerializer): class VoteSerializer(serializers.HyperlinkedModelSerializer):
class Meta: class Meta:
@@ -10,3 +10,19 @@ class VoteSerializer(serializers.HyperlinkedModelSerializer):
'track': {'view_name': 'api-track', 'lookup_field': 'id'}, 'track': {'view_name': 'api-track', 'lookup_field': 'id'},
'user': {'view_name': 'api-user', 'lookup_field': 'id'} 'user': {'view_name': 'api-user', 'lookup_field': 'id'}
} }
class VoteSaveSerializer(serializers.ModelSerializer):
class Meta:
model = Vote
fields = ['track', 'points', 'skipped']
def create(self, validated_data):
"""
Create a new vote. Deletes a previous skipped vote if it exists.
"""
user = self.context['request'].user
track = validated_data['track']
previous_vote = Vote.objects.filter(user=user, track=track).first()
if previous_vote and previous_vote.skipped:
previous_vote.delete()
return Vote.objects.create(**validated_data)
+4 -4
View File
@@ -6,15 +6,15 @@ from django.conf.urls.static import static
urlpatterns = [ urlpatterns = [
### API ### API
path('api/', views.api_root, name="api-root"), path('api/', views.api_root, name="api-root"),
path('api/track', views.TrackListView.as_view(), name="api-tracks"), path('api/track/', views.TrackListView.as_view(), name="api-tracks"),
path('api/track/<id>', views.TrackDetailView.as_view(), name="api-track"), path('api/track/<id>', views.TrackDetailView.as_view(), name="api-track"),
path('api/artist/<id>', views.ArtistDetailView.as_view(), name="api-artist"), path('api/artist/<id>', views.ArtistDetailView.as_view(), name="api-artist"),
path('api/artist', views.ArtistListView.as_view(), name="api-artists"), path('api/artist', views.ArtistListView.as_view(), name="api-artists"),
path('api/album/<id>', views.AlbumDetailView.as_view(), name="api-album"), path('api/album/<id>', views.AlbumDetailView.as_view(), name="api-album"),
path('api/album', views.AlbumListView.as_view(), name="api-albums"), path('api/album/', views.AlbumListView.as_view(), name="api-albums"),
path('api/vote', views.VoteListView.as_view(), name="api-votes"), path('api/vote/', views.VoteListView.as_view(), name="api-votes"),
path('api/vote/<id>', views.VoteDetailView.as_view(), name="api-vote"), path('api/vote/<id>', views.VoteDetailView.as_view(), name="api-vote"),
path('api/user', views.UserListView.as_view(), name="api-users"), path('api/user/', views.UserListView.as_view(), name="api-users"),
path('api/user/<int:id>', views.UserDetailView.as_view(), name="api-user"), path('api/user/<int:id>', views.UserDetailView.as_view(), name="api-user"),
path('api/user/<int:user_id>/votes', views.VoteListView.as_view(), name="api-user-votes"), path('api/user/<int:user_id>/votes', views.VoteListView.as_view(), name="api-user-votes"),
path('api/profile/<int:id>', views.ProfileDetailView.as_view(), name="api-profile"), path('api/profile/<int:id>', views.ProfileDetailView.as_view(), name="api-profile"),
+1
View File
@@ -10,4 +10,5 @@ def api_root(request, format=None):
'artists': reverse('api-artists', request=request, format=format), 'artists': reverse('api-artists', request=request, format=format),
'users': reverse('api-users', request=request, format=format), 'users': reverse('api-users', request=request, format=format),
'albums': reverse('api-albums', request=request, format=format), 'albums': reverse('api-albums', request=request, format=format),
'votes': reverse('api-votes', request=request, format=format),
}) })
+23 -2
View File
@@ -1,9 +1,10 @@
from django.db import IntegrityError
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.authentication import SessionAuthentication from rest_framework.authentication import SessionAuthentication
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from playlist.models import Vote from playlist.models import Vote, Profile
from playlist.serializers import VoteSerializer from playlist.serializers import VoteSerializer, VoteSaveSerializer
class VoteListView(APIView): class VoteListView(APIView):
authentication_classes = [SessionAuthentication] authentication_classes = [SessionAuthentication]
@@ -17,6 +18,26 @@ class VoteListView(APIView):
serializer = VoteSerializer(votes, many=True, context={'request': request}) serializer = VoteSerializer(votes, many=True, context={'request': request})
return Response(serializer.data) return Response(serializer.data)
def post(self, request):
(profile,_) = Profile.objects.get_or_create(user=request.user)
if not profile.can_vote:
return Response({'error': 'User cannot vote'}, status=403)
serializer = VoteSaveSerializer(data=request.data, context={'request': request})
if serializer.is_valid():
try:
serializer.save(user=request.user)
return Response(serializer.data, status=201)
except IntegrityError:
return Response({'error': 'Duplicate vote'}, status=400)
return Response(serializer.errors, status=400)
def delete(self, request):
if request.user.is_superuser:
Vote.objects.all().delete()
return Response(status=204)
else:
return Response(status=403)
class VoteDetailView(APIView): class VoteDetailView(APIView):
authentication_classes = [SessionAuthentication] authentication_classes = [SessionAuthentication]
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]