""" 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 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 = u"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): try: self.object = self.get_object() created = False except Http404: self.object = None created = True serializer = self.get_serializer(self.object, data=request.DATA, files=request.FILES) if serializer.is_valid(): self.pre_save(serializer.object) self.object = serializer.save() status_code = created and status.HTTP_201_CREATED or status.HTTP_200_OK return Response(serializer.data, status=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. 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)