<reponame>anders-hoovy/django-rest-framework<gh_stars>0
"""
Basic building blocks for generic class based views.

We don't bind behaviour to http method handlers yet,
which allows mixin classes to be composed in interesting ways.
"""
import logging
from rest_framework import status
from rest_framework.response import Response
from rest_framework.settings import api_settings
from django.core.cache import cache
from django.contrib.auth.models import User

log = logging.getLogger(__name__)


class CreateModelMixin:
    """
    Create a model instance.
    """

    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

    def perform_create(self, serializer):
        serializer.save()

    def get_success_headers(self, data):
        try:
            return {'Location': str(data[api_settings.URL_FIELD_NAME])}
        except (TypeError, KeyError):
            return {}


class ListModelMixin:
    """
    List a queryset.
    """

    def list(self, request, *args, **kwargs):
        queryset = self.filter_queryset(self.get_queryset())

        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)


class RetrieveModelMixin:
    """
    Retrieve a model instance.
    """

    def retrieve(self, request, *args, **kwargs):
        instance = self.get_object()
        serializer = self.get_serializer(instance)
        return Response(serializer.data)


class UpdateModelMixin:
    """
    Update a model instance.
    """

    def update(self, request, *args, **kwargs):
        partial = kwargs.pop('partial', False)
        instance = self.get_object()
        serializer = self.get_serializer(instance, data=request.data, partial=partial)
        serializer.is_valid(raise_exception=True)
        self.perform_update(serializer)

        if getattr(instance, '_prefetched_objects_cache', None):
            # If 'prefetch_related' has been applied to a queryset, we need to
            # forcibly invalidate the prefetch cache on the instance.
            instance._prefetched_objects_cache = {}

        return Response(serializer.data)

    def perform_update(self, serializer):
        serializer.save()

    def partial_update(self, request, *args, **kwargs):
        kwargs['partial'] = True
        return self.update(request, *args, **kwargs)


class DestroyModelMixin:
    """
    Destroy a model instance.
    """

    def destroy(self, request, *args, **kwargs):
        instance = self.get_object()
        self.perform_destroy(instance)
        return Response(status=status.HTTP_204_NO_CONTENT)

    def perform_destroy(self, instance):
        instance.delete()


class ByUserMixin:

    def get_queryset(self) -> list:
        qs = super().get_queryset()
        user = self.request.user
        if self.action == "list":
            if user.is_anonymous:
                return qs.none()
            return qs.by_user(user).all()
        return qs


class TopLevelMixin:

    def get_queryset(self) -> list:
        qs = super().get_queryset()
        if self.action == "list":
            return qs.filter(parent__isnull=True).distinct().all()
        return qs


class CacheMixin:
    BY_USER_CACHE_TIMEOUT = 60 * 15

    def get_queryset(self) -> list:
        def get():
            return super(CacheMixin, self).get_queryset()
        if self.action == "list":
            return cache.get_or_set(
                self.queryset.model.cache_key(self.request.user),
                get,
                self.BY_USER_CACHE_TIMEOUT,
            )
        return get()


class ListMixin:

    def list(self, request, *args, **kwargs) -> list:
        response = super().list(request, *args, **kwargs)
        log.debug(
            f"{self.queryset.__class__.__name__}: {request.user} {len(response.data)}")
        return response


class UserMixin:

    @property
    def user(self) -> User:
        request = self.context.get("request")
        if request and hasattr(request, "user"):
            return request.user

"""
Basic building blocks for generic class based views.

We don't bind behaviour to http method handlers yet,
which allows mixin classes to be composed in interesting ways.
"""
import logging
from rest_framework import status
from rest_framework.response import Response
from rest_framework.settings import api_settings
from django.core.cache import cache
from django.contrib.auth.models import User

log = logging.getLogger(__name__)


class CreateModelMixin:
    """
    Create a model instance.
    """

    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

    def perform_create(self, serializer):
        serializer.save()

    def get_success_headers(self, data):
        try:
            return {'Location': str(data[api_settings.URL_FIELD_NAME])}
        except (TypeError, KeyError):
            return {}


class ListModelMixin:
    """
    List a queryset.
    """

    def list(self, request, *args, **kwargs):
        queryset = self.filter_queryset(self.get_queryset())

        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)


class RetrieveModelMixin:
    """
    Retrieve a model instance.
    """

    def retrieve(self, request, *args, **kwargs):
        instance = self.get_object()
        serializer = self.get_serializer(instance)
        return Response(serializer.data)


class UpdateModelMixin:
    """
    Update a model instance.
    """

    def update(self, request, *args, **kwargs):
        partial = kwargs.pop('partial', False)
        instance = self.get_object()
        serializer = self.get_serializer(instance, data=request.data, partial=partial)
        serializer.is_valid(raise_exception=True)
        self.perform_update(serializer)

        if getattr(instance, '_prefetched_objects_cache', None):
            # If 'prefetch_related' has been applied to a queryset, we need to
            # forcibly invalidate the prefetch cache on the instance.
            instance._prefetched_objects_cache = {}

        return Response(serializer.data)

    def perform_update(self, serializer):
        serializer.save()

    def partial_update(self, request, *args, **kwargs):
        kwargs['partial'] = True
        return self.update(request, *args, **kwargs)


class DestroyModelMixin:
    """
    Destroy a model instance.
    """

    def destroy(self, request, *args, **kwargs):
        instance = self.get_object()
        self.perform_destroy(instance)
        return Response(status=status.HTTP_204_NO_CONTENT)

    def perform_destroy(self, instance):
        instance.delete()


class ByUserMixin:

    def get_queryset(self) -> list:
        qs = super().get_queryset()
        user = self.request.user
        if self.action == "list":
            if user.is_anonymous:
                return qs.none()
            return qs.by_user(user).all()
        return qs


class TopLevelMixin:

    def get_queryset(self) -> list:
        qs = super().get_queryset()
        if self.action == "list":
            return qs.filter(parent__isnull=True).distinct().all()
        return qs


class CacheMixin:
    BY_USER_CACHE_TIMEOUT = 60 * 15

    def get_queryset(self) -> list:
        def get():
            return super(CacheMixin, self).get_queryset()
        if self.action == "list":
            return cache.get_or_set(
                self.queryset.model.cache_key(self.request.user),
                get,
                self.BY_USER_CACHE_TIMEOUT,
            )
        return get()


class ListMixin:

    def list(self, request, *args, **kwargs) -> list:
        response = super().list(request, *args, **kwargs)
        log.debug(
            f"{self.queryset.__class__.__name__}: {request.user} {len(response.data)}")
        return response


class UserMixin:

    @property
    def user(self) -> User:
        request = self.context.get("request")
        if request and hasattr(request, "user"):
            return request.user
Basic building blocks for generic class based views.  We don't bind behaviour to http method handlers yet, which allows mixin classes to be composed in interesting ways. Create a model instance. List a queryset. Retrieve a model instance. Update a model instance. # If 'prefetch_related' has been applied to a queryset, we need to # forcibly invalidate the prefetch cache on the instance. Destroy a model instance.