mirror of
https://gitlab.science.ru.nl/technicie/MarietjeDjango.git
synced 2025-12-11 13:52:22 +01:00
Add logging for API endpoints
This commit is contained in:
@ -1,9 +1,18 @@
|
||||
from django.contrib import admin
|
||||
from .models import Queue, Playlist, PlaylistSong, QueueCommand
|
||||
from typing import Optional
|
||||
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
from .models import Queue, Playlist, PlaylistSong, QueueCommand, QueueLogEntry, UserQueue
|
||||
from marietje.admin import UserAdmin as BaseUserAdmin
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from .services import get_queue_for_user
|
||||
|
||||
admin.site.register(Playlist)
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
@admin.register(Queue)
|
||||
class OrderAdmin(admin.ModelAdmin):
|
||||
@ -21,3 +30,61 @@ class PlaylistSongAdmin(admin.ModelAdmin):
|
||||
|
||||
|
||||
admin.site.register(QueueCommand)
|
||||
|
||||
|
||||
@admin.register(QueueLogEntry)
|
||||
class QueueLogEntryAdmin(admin.ModelAdmin):
|
||||
"""Admin for log entries."""
|
||||
|
||||
list_display = [
|
||||
"timestamp",
|
||||
"queue",
|
||||
"action",
|
||||
"user",
|
||||
"description",
|
||||
]
|
||||
|
||||
list_filter = [
|
||||
"queue",
|
||||
"action",
|
||||
("timestamp", admin.DateFieldListFilter),
|
||||
]
|
||||
|
||||
def has_delete_permission(self, request, obj=None):
|
||||
"""Disable delete permission."""
|
||||
return False
|
||||
|
||||
def has_add_permission(self, request):
|
||||
"""Disable add permission."""
|
||||
return False
|
||||
|
||||
def has_change_permission(self, request, obj=None):
|
||||
"""Disable change permission."""
|
||||
return False
|
||||
|
||||
|
||||
class UserQueueInline(admin.StackedInline):
|
||||
model = UserQueue
|
||||
fields = ("queue",)
|
||||
|
||||
|
||||
class UserAdmin(BaseUserAdmin):
|
||||
fieldsets = (
|
||||
(None, {"fields": ("username", "password")}),
|
||||
(_("Personal info"), {"fields": ("name", "email")}),
|
||||
(_("Permissions"), {"fields": ("is_active", "is_staff", "is_superuser", "groups", "user_permissions")}),
|
||||
(_("Important dates"), {"fields": ("last_login", "date_joined")}),
|
||||
(_("Activation"), {"fields": ("activation_token", "reset_token")}),
|
||||
)
|
||||
list_display = ("username", "email", "name", "date_joined", "last_login", "queue__queue", "is_staff")
|
||||
inlines = (UserQueueInline,)
|
||||
|
||||
def queue__queue(self, obj: User) -> Optional[Queue]:
|
||||
"""Retrieve the Queue for a User."""
|
||||
return get_queue_for_user(obj)
|
||||
|
||||
queue__queue.short_description = "queue"
|
||||
|
||||
|
||||
admin.site.unregister(User)
|
||||
admin.site.register(User, UserAdmin)
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
from django.db.models import Q
|
||||
from rest_framework.generics import ListAPIView, RetrieveAPIView, get_object_or_404, CreateAPIView, DestroyAPIView
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
@ -8,7 +9,7 @@ from django.http import Http404
|
||||
|
||||
from queues.api.v1.serializers import PlaylistSerializer, QueueSerializer, PlaylistSongSerializer
|
||||
from queues.exceptions import RequestException
|
||||
from queues.models import Playlist, PlaylistSong, QueueCommand
|
||||
from queues.models import Playlist, PlaylistSong, QueueCommand, Queue
|
||||
from queues.services import get_user_or_default_queue
|
||||
from songs.counters import request_counter
|
||||
from songs.models import Song
|
||||
@ -80,7 +81,7 @@ class QueueSkipAPIView(APIView):
|
||||
if queue is None:
|
||||
return Response(status=404)
|
||||
|
||||
playlist_song = request.user.queue.current_song()
|
||||
playlist_song = queue.current_song()
|
||||
if (
|
||||
request.user is not None
|
||||
and playlist_song.user != request.user
|
||||
@ -90,6 +91,7 @@ class QueueSkipAPIView(APIView):
|
||||
|
||||
playlist_song.state = 2
|
||||
playlist_song.save()
|
||||
queue.log_action(request.user, "next", "Skipped to next song.")
|
||||
|
||||
return Response(status=200, data=QueueSerializer(queue).data)
|
||||
|
||||
@ -111,7 +113,18 @@ class PlaylistSongMoveDownAPIView(APIView):
|
||||
and not request.user.has_perm("queues.can_move")
|
||||
):
|
||||
return Response(status=403)
|
||||
|
||||
playlist_song.move_down()
|
||||
|
||||
for queue in Queue.objects.filter(
|
||||
Q(playlist=playlist_song.playlist) | Q(random_playlist=playlist_song.playlist)
|
||||
):
|
||||
queue.log_action(
|
||||
request.user,
|
||||
"down",
|
||||
'Moved song "{}" of playlist "{}" down.'.format(playlist_song.song, playlist_song.playlist),
|
||||
)
|
||||
|
||||
return Response(status=200, data=self.serializer_class(playlist_song).data)
|
||||
|
||||
|
||||
@ -131,7 +144,18 @@ class PlaylistSongCancelAPIView(DestroyAPIView):
|
||||
and not request.user.has_perm("queues.can_cancel")
|
||||
):
|
||||
return Response(status=403)
|
||||
|
||||
playlist_song.delete()
|
||||
|
||||
for queue in Queue.objects.filter(
|
||||
Q(playlist=playlist_song.playlist) | Q(random_playlist=playlist_song.playlist)
|
||||
):
|
||||
queue.log_action(
|
||||
request.user,
|
||||
"cancel",
|
||||
'Cancelled song "{}" of playlist "{}".'.format(playlist_song.song, playlist_song.playlist),
|
||||
)
|
||||
|
||||
return Response(status=200, data=self.serializer_class(playlist_song).data)
|
||||
|
||||
|
||||
@ -165,6 +189,8 @@ class QueueRequestAPIView(CreateAPIView):
|
||||
except RequestException as e:
|
||||
return Response(data={"success": False, "errorMessage": str(e)})
|
||||
|
||||
queue.log_action(request.user, "request_song", "Requested song {}.".format(song))
|
||||
|
||||
request_counter.labels(queue=queue.name).inc()
|
||||
return Response(status=200, data=self.serializer_class(playlist_song).data)
|
||||
|
||||
@ -196,7 +222,11 @@ class QueueVolumeDownAPIView(APIView):
|
||||
return Response(status=404)
|
||||
if request.user is not None and not request.user.has_perm("queues.can_control_volume"):
|
||||
return Response(status=403)
|
||||
|
||||
QueueCommand.objects.create(queue=queue, command="volume_down")
|
||||
|
||||
queue.log_action(request.user, "volume_down", "Reduced the volume of {}.".format(queue))
|
||||
|
||||
return Response(status=200, data=self.serializer_class(queue).data)
|
||||
|
||||
|
||||
@ -227,7 +257,11 @@ class QueueVolumeUpAPIView(APIView):
|
||||
return Response(status=404)
|
||||
if request.user is not None and not request.user.has_perm("queues.can_control_volume"):
|
||||
return Response(status=403)
|
||||
|
||||
QueueCommand.objects.create(queue=queue, command="volume_up")
|
||||
|
||||
queue.log_action(request.user, "volume_up", "Increased the volume of {}.".format(queue))
|
||||
|
||||
return Response(status=200, data=self.serializer_class(queue).data)
|
||||
|
||||
|
||||
@ -258,5 +292,9 @@ class QueueMuteAPIView(APIView):
|
||||
return Response(status=404)
|
||||
if request.user is not None and not request.user.has_perm("queues.can_control_volume"):
|
||||
return Response(status=403)
|
||||
|
||||
QueueCommand.objects.create(queue=queue, command="mute")
|
||||
|
||||
queue.log_action(request.user, "mute", "Muted the volume of {}.".format(queue))
|
||||
|
||||
return Response(status=200, data=self.serializer_class(queue).data)
|
||||
|
||||
64
marietje/queues/migrations/0012_userqueue_queuelogentry.py
Normal file
64
marietje/queues/migrations/0012_userqueue_queuelogentry.py
Normal file
@ -0,0 +1,64 @@
|
||||
# Generated by Django 4.2.6 on 2023-11-24 20:17
|
||||
|
||||
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),
|
||||
("queues", "0011_alter_playlistsong_playlist"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="UserQueue",
|
||||
fields=[
|
||||
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
|
||||
(
|
||||
"queue",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="users",
|
||||
to="queues.queue",
|
||||
),
|
||||
),
|
||||
(
|
||||
"user",
|
||||
models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="queue_new",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="QueueLogEntry",
|
||||
fields=[
|
||||
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
|
||||
("action", models.CharField(max_length=255)),
|
||||
("timestamp", models.DateTimeField(auto_now_add=True)),
|
||||
("description", models.CharField(max_length=255)),
|
||||
(
|
||||
"queue",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE, related_name="logs", to="queues.queue"
|
||||
),
|
||||
),
|
||||
(
|
||||
"user",
|
||||
models.ForeignKey(
|
||||
blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "player log entry",
|
||||
"verbose_name_plural": "player log entries",
|
||||
},
|
||||
),
|
||||
]
|
||||
22
marietje/queues/migrations/0013_alter_userqueue_user.py
Normal file
22
marietje/queues/migrations/0013_alter_userqueue_user.py
Normal file
@ -0,0 +1,22 @@
|
||||
# Generated by Django 4.2.6 on 2023-11-24 20:19
|
||||
|
||||
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),
|
||||
("queues", "0012_userqueue_queuelogentry"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="userqueue",
|
||||
name="user",
|
||||
field=models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.CASCADE, related_name="queue", to=settings.AUTH_USER_MODEL
|
||||
),
|
||||
),
|
||||
]
|
||||
@ -1,3 +1,4 @@
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
from django.conf import settings
|
||||
@ -6,6 +7,8 @@ from django.utils import timezone
|
||||
from queues.exceptions import RequestException
|
||||
from songs.models import Song
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class Playlist(models.Model):
|
||||
def __str__(self):
|
||||
@ -143,10 +146,41 @@ class Queue(models.Model):
|
||||
playlist_song.save()
|
||||
song_count += 1
|
||||
|
||||
def log_action(self, user: User, action: str, description: str) -> "QueueLogEntry":
|
||||
"""
|
||||
Log a queue action.
|
||||
|
||||
:param user: The user performing the action.
|
||||
:param action: An identifier of the action performed.
|
||||
:param description: An optional description for the action.
|
||||
:return: The created QueueLogEntry object.
|
||||
"""
|
||||
return QueueLogEntry.objects.create(
|
||||
queue=self,
|
||||
user=user,
|
||||
action=action,
|
||||
description=description,
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.name)
|
||||
|
||||
|
||||
class UserQueue(models.Model):
|
||||
"""
|
||||
UserQueue model.
|
||||
|
||||
This model connects a user to its queue.
|
||||
"""
|
||||
|
||||
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="queue")
|
||||
queue = models.ForeignKey(Queue, on_delete=models.SET_NULL, null=True, blank=True, related_name="users")
|
||||
|
||||
def __str__(self):
|
||||
"""Convert this object to string."""
|
||||
return "Queue for user {}".format(self.user)
|
||||
|
||||
|
||||
class QueueCommand(models.Model):
|
||||
queue = models.ForeignKey(
|
||||
Queue,
|
||||
@ -157,3 +191,20 @@ class QueueCommand(models.Model):
|
||||
|
||||
def __str__(self):
|
||||
return str(self.command)
|
||||
|
||||
|
||||
class QueueLogEntry(models.Model):
|
||||
"""Model for logging queue events."""
|
||||
|
||||
queue = models.ForeignKey(Queue, on_delete=models.CASCADE, related_name="logs")
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)
|
||||
action = models.CharField(max_length=255)
|
||||
timestamp = models.DateTimeField(auto_now_add=True)
|
||||
description = models.CharField(max_length=255)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.queue} {self.action} by {self.user} at {self.timestamp}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "player log entry"
|
||||
verbose_name_plural = "player log entries"
|
||||
|
||||
@ -1,15 +1,44 @@
|
||||
from queues.models import Queue
|
||||
from typing import Optional
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
from queues.models import Queue, Playlist
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
def get_user_or_default_queue(request):
|
||||
"""Get the user or default queue."""
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
def get_user_or_default_queue(request) -> Queue:
|
||||
"""Get the user or default queue from a request."""
|
||||
if request.user is None:
|
||||
return get_default_queue()
|
||||
else:
|
||||
return request.user.queue
|
||||
return get_queue_for_user(request.user)
|
||||
|
||||
|
||||
def get_default_queue():
|
||||
def get_queue_for_user(user: User) -> Optional[Queue]:
|
||||
"""Get the queue for a User."""
|
||||
if user.queue is None:
|
||||
return None
|
||||
else:
|
||||
return user.queue.queue
|
||||
|
||||
|
||||
def get_default_queue() -> Queue:
|
||||
"""Get the default queue."""
|
||||
return Queue.objects.get(pk=settings.DEFAULT_QUEUE)
|
||||
try:
|
||||
return Queue.objects.get(pk=settings.DEFAULT_QUEUE)
|
||||
except Queue.DoesNotExist:
|
||||
return get_first_queue()
|
||||
|
||||
|
||||
def get_first_queue() -> Queue:
|
||||
"""Get the first Queue object or create one."""
|
||||
queue = Queue.objects.first()
|
||||
if queue is not None:
|
||||
return queue
|
||||
|
||||
playlist = Playlist.objects.create()
|
||||
random_playlist = Playlist.objects.create()
|
||||
return Queue.objects.create(name="Queue", playlist=playlist, random_playlist=random_playlist)
|
||||
|
||||
18
marietje/queues/signals.py
Normal file
18
marietje/queues/signals.py
Normal file
@ -0,0 +1,18 @@
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
|
||||
from queues.models import UserQueue
|
||||
from queues.services import get_default_queue
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
@receiver(post_save, sender=User)
|
||||
def create_default_queue(sender, instance, created, **kwargs):
|
||||
"""Create a UserQueue object when a User gets created."""
|
||||
if created:
|
||||
user_queue, user_queue_created = UserQueue.objects.get_or_create(user=instance)
|
||||
if user_queue_created:
|
||||
user_queue.queue = get_default_queue()
|
||||
user_queue.save()
|
||||
Reference in New Issue
Block a user