from django.contrib.auth.models import User, Group from django_filters import rest_framework as drf_filters from rest_framework import status, viewsets, parsers, permissions from rest_framework.decorators import action from rest_framework.response import Response from filer.models.imagemodels import Image from . import models, forms, filters, serializers from .views import BaseAPIView __all__ = ( "ImageViewSet", "SoundViewSet", "TrackROViewSet", "UserGroupViewSet", "UserSettingsViewSet", ) class AutocompleteMixin: """Based on provided filterset and serializer, add an "autocomplete" action to the viewset. Url ``GET`` parameters: - `field` (many): if provided, only return provided field names - filterset's lookups. Return a list of values if ``field`` is provided, result of `list()` otherwise. """ @action(name="autocomplete", detail=False) def autocomplete(self, request): field = request.GET.get("field", None) if field: queryset = self.filter_queryset(self.get_queryset()) values = queryset.values_list(field, flat=True).distinct() return Response(values[:10]) return self.list(request) class ListCommitMixin: @action(name="commit", detail=False, methods=["POST"]) def commit(self, request): """ Data: { "delete": [pk], "update": [{pk, **object}], "create": [object_data] } Return: { "deleted": [pk], "updated": [object], "created": [object], } """ queryset = self.get_queryset() resp = {"deleted": [], "updated": [], "created": []} if ids := request.data.get("delete"): q = queryset.filter(id__in=ids) resp["deleted"] = list(q.values_list("id", flat=True)) q.delete() # TODO: bulk save and update if items := request.data.get("update"): resp["updated"] = self._commit_save_many(items) if items := request.data.get("create"): resp["created"] = self._commit_save_many(items) return Response(data=resp) def _commit_save_many(self, data): ser = self.get_serializer(data=data, many=True) ser.is_valid(raise_exception=True) items = ser.save() ser = self.get_serializer(items, many=True) return ser.data class ImageViewSet(viewsets.ModelViewSet): parsers = (parsers.MultiPartParser,) permissions = (permissions.IsAuthenticatedOrReadOnly,) serializer_class = serializers.admin.ImageSerializer queryset = Image.objects.all().order_by("-uploaded_at") filter_backends = (drf_filters.DjangoFilterBackend,) filterset_class = filters.ImageFilterSet def create(self, request, **kwargs): # FIXME: to be replaced by regular DRF form = forms.ImageForm(request.POST, request.FILES) if form.is_valid(): file = form.cleaned_data["file"] Image.objects.create(original_filename=file.name, file=file) return Response({"status": "ok"}) return Response({"status": "error", "errors": form.errors}) class SoundViewSet(BaseAPIView, viewsets.ModelViewSet): parsers = (parsers.MultiPartParser,) permissions = (permissions.IsAuthenticatedOrReadOnly,) serializer_class = serializers.SoundSerializer queryset = models.Sound.objects.order_by("-pk") filter_backends = (drf_filters.DjangoFilterBackend,) filterset_class = filters.SoundFilterSet def perform_create(self, serializer): obj = serializer.save() # FIXME: hack to avoid "TYPE_REMOVED" status # -> file is saved to fs after object is saved to db obj.save() def get_queryset(self): query = super().get_queryset() if not self.request.user.is_authenticated: return query.available() return query class TrackROViewSet(AutocompleteMixin, viewsets.ReadOnlyModelViewSet): """Track viewset used for auto completion.""" serializer_class = serializers.admin.TrackSerializer permission_classes = (permissions.IsAuthenticated,) filterset_class = filters.TrackFilterSet queryset = models.Track.objects.all() class CommentViewSet(viewsets.ModelViewSet): serializer_class = serializers.CommentSerializer permission_classes = (permissions.IsAuthenticatedOrReadOnly,) queryset = models.Comment.objects.all() # --- admin class UserViewSet(AutocompleteMixin, viewsets.ModelViewSet): serializer_class = serializers.auth.UserSerializer permission_classes = (permissions.IsAdminUser,) filterset_class = filters.UserFilterSet queryset = User.objects.all().distinct().order_by("username") class GroupViewSet(AutocompleteMixin, viewsets.ModelViewSet): serializer_class = serializers.auth.GroupSerializer permission_classes = (permissions.IsAdminUser,) filterset_class = filters.GroupFilterSet queryset = Group.objects.all().distinct().order_by("name") class UserGroupViewSet(ListCommitMixin, viewsets.ModelViewSet): serializer_class = serializers.auth.UserGroupSerializer permission_classes = (permissions.IsAdminUser,) filterset_class = filters.UserGroupFilterSet model = User.groups.through queryset = model.objects.all().distinct().order_by("user__username") class UserSettingsViewSet(viewsets.ViewSet): """User's settings specific to aircox. Allow only to create and edit user's own settings. """ serializer_class = serializers.UserSettingsSerializer permission_classes = (permissions.IsAuthenticated,) def get_serializer(self, instance=None, **kwargs): return self.serializer_class(instance=instance, context={"user": self.request.user}, **kwargs) @action(detail=False, methods=["GET"]) def retrieve(self, request): user = self.request.user settings = getattr(user, "aircox_settings", None) data = settings and self.get_serializer(settings) or None return Response(data) @action(detail=False, methods=["POST", "PUT"]) def update(self, request): user = self.request.user settings = getattr(user, "aircox_settings", None) data = dict(request.data) data["user_id"] = self.request.user serializer = self.get_serializer(instance=settings, data=request.data) if serializer.is_valid(): serializer.save() return Response({"status": "ok"}) else: return Response( {"errors": serializer.errors}, status=status.HTTP_400_BAD_REQUEST, )