""" 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. """ from __future__ import unicode_literals from django.http import Http404 from rest_framework import status from rest_framework.response import Response class CreateModelMixin(object): """ Create a model instance. Should be mixed in with any `BaseView`. """ def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.DATA, files=request.FILES) if serializer.is_valid(): self.pre_save(serializer.object) self.object = serializer.save() headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) def get_success_headers(self, data): try: return {'Location': data['url']} except (TypeError, KeyError): return {} def pre_save(self, obj): pass class ListModelMixin(object): """ List a queryset. Should be mixed in with `MultipleObjectAPIView`. """ empty_error = "Empty list and '%(class_name)s.allow_empty' is False." def list(self, request, *args, **kwargs): queryset = self.get_queryset() self.object_list = self.filter_queryset(queryset) # Default is to allow empty querysets. This can be altered by setting # `.allow_empty = False`, to raise 404 errors on empty querysets. allow_empty = self.get_allow_empty() if not allow_empty and not self.object_list: class_name = self.__class__.__name__ error_msg = self.empty_error % {'class_name': class_name} raise Http404(error_msg) # Pagination size is set by the `.paginate_by` attribute, # which may be `None` to disable pagination. page_size = self.get_paginate_by(self.object_list) if page_size: packed = self.paginate_queryset(self.object_list, page_size) paginator, page, queryset, is_paginated = packed serializer = self.get_pagination_serializer(page) else: serializer = self.get_serializer(self.object_list) return Response(serializer.data) class RetrieveModelMixin(object): """ Retrieve a model instance. Should be mixed in with `SingleObjectBaseView`. """ def retrieve(self, request, *args, **kwargs): self.object = self.get_object() serializer = self.get_serializer(self.object) return Response(serializer.data) class UpdateModelMixin(object): """ Update a model instance. Should be mixed in with `SingleObjectBaseView`. """ def update(self, request, *args, **kwargs): partial = kwargs.pop('partial', False) try: self.object = self.get_object() success_status_code = status.HTTP_200_OK except Http404: self.object = None success_status_code = status.HTTP_201_CREATED serializer = self.get_serializer(self.object, data=request.DATA, files=request.FILES, partial=partial) if serializer.is_valid(): self.pre_save(serializer.object) self.object = serializer.save() return Response(serializer.data, status=success_status_code) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) def pre_save(self, obj): """ Set any attributes on the object that are implicit in the request. """ # pk and/or slug attributes are implicit in the URL. pk = self.kwargs.get(self.pk_url_kwarg, None) if pk: setattr(obj, 'pk', pk) slug = self.kwargs.get(self.slug_url_kwarg, None) if slug: slug_field = self.get_slug_field() setattr(obj, slug_field, slug) # Ensure we clean the attributes so that we don't eg return integer # pk using a string representation, as provided by the url conf kwarg. if hasattr(obj, 'full_clean'): obj.full_clean() class DestroyModelMixin(object): """ Destroy a model instance. Should be mixed in with `SingleObjectBaseView`. """ def destroy(self, request, *args, **kwargs): obj = self.get_object() obj.delete() return Response(status=status.HTTP_204_NO_CONTENT)