From 6e21915934686cc7d46c8144403c933fa6fd2375 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 3 Sep 2012 17:49:22 +0100 Subject: [PATCH] First pass at mixins & generic views --- djangorestframework/mixins.py | 64 +++++++++++++++++++++++++ djangorestframework/views.py | 72 +++++++++++++++++++++++++++- docs/tutorial/3-class-based-views.md | 43 ++++++++++------- 3 files changed, 162 insertions(+), 17 deletions(-) diff --git a/djangorestframework/mixins.py b/djangorestframework/mixins.py index e69de29bb..2721f59ef 100644 --- a/djangorestframework/mixins.py +++ b/djangorestframework/mixins.py @@ -0,0 +1,64 @@ +from djangorestframework import status +from djangorestframework.response import Response + + +class CreateModelMixin(object): + """ + Create a model instance. + Should be mixed in with any `APIView` + """ + def create(self, request, *args, **kwargs): + serializer = self.get_serializer(data=request.DATA) + if serializer.is_valid(): + self.object = serializer.object + self.object.save() + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.error_data, status=status.HTTP_400_BAD_REQUEST) + + +class ListModelMixin(object): + """ + List a queryset. + Should be mixed in with `MultipleObjectBaseView`. + """ + def list(self, request, *args, **kwargs): + self.object_list = self.get_queryset() + serializer = self.get_serializer(instance=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(instance=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): + self.object = self.get_object() + serializer = self.get_serializer(data=request.DATA, instance=self.object) + if serializer.is_valid(): + self.object = serializer.deserialized + self.object.save() + return Response(serializer.data) + return Response(serializer.error_data, status=status.HTTP_400_BAD_REQUEST) + + +class DestroyModelMixin(object): + """ + Destroy a model instance. + Should be mixed in with `SingleObjectBaseView`. + """ + def destroy(self, request, *args, **kwargs): + self.object = self.get_object() + self.object.delete() + return Response(status=status.HTTP_204_NO_CONTENT) diff --git a/djangorestframework/views.py b/djangorestframework/views.py index 389f2044d..1939eed22 100644 --- a/djangorestframework/views.py +++ b/djangorestframework/views.py @@ -11,12 +11,14 @@ from django.http import Http404 from django.utils.html import escape from django.utils.safestring import mark_safe from django.views.decorators.csrf import csrf_exempt +from django.views.generic.detail import SingleObjectMixin +from django.views.generic.list import MultipleObjectMixin from djangorestframework.compat import View as _View, apply_markdown from djangorestframework.response import Response from djangorestframework.request import Request from djangorestframework.settings import api_settings -from djangorestframework import parsers, authentication, permissions, status, exceptions +from djangorestframework import parsers, authentication, permissions, status, exceptions, mixins __all__ = ( @@ -281,3 +283,71 @@ class APIView(_View): field_name_types[name] = field.__class__.__name__ content['fields'] = field_name_types raise Response(content, status=status.HTTP_200_OK) + +# TODO: .get_serializer() + + +### Abstract view classes, that do not provide any method handlers ### + +class MultipleObjectBaseView(MultipleObjectMixin, APIView): + """ + Base class for views onto a queryset. + """ + pass + + +class SingleObjectBaseView(SingleObjectMixin, APIView): + """ + Base class for views onto a model instance. + """ + pass + + +### Concrete view classes, that provide existing method handlers ### + +class ListAPIView(mixins.ListModelMixin, + MultipleObjectBaseView): + """ + Concrete view for listing a queryset. + """ + def get(self, request, *args, **kwargs): + return self.list(request, *args, **kwargs) + + +class RootAPIView(mixins.ListModelMixin, + mixins.CreateModelMixin, + MultipleObjectBaseView): + """ + Concrete view for listing a queryset or creating a model instance. + """ + def get(self, request, *args, **kwargs): + return self.list(request, *args, **kwargs) + + def post(self, request, *args, **kwargs): + return self.create(request, *args, **kwargs) + + +class DetailAPIView(mixins.RetrieveModelMixin, + SingleObjectBaseView): + """ + Concrete view for retrieving a model instance. + """ + def get(self, request, *args, **kwargs): + return self.retrieve(request, *args, **kwargs) + + +class InstanceAPIView(mixins.RetrieveModelMixin, + mixins.UpdateModelMixin, + mixins.DestroyModelMixin, + SingleObjectBaseView): + """ + Concrete view for retrieving, updating or deleting a model instance. + """ + def get(self, request, *args, **kwargs): + return self.retrieve(request, *args, **kwargs) + + def put(self, request, *args, **kwargs): + return self.update(request, *args, **kwargs) + + def delete(self, request, *args, **kwargs): + return self.destroy(request, *args, **kwargs) diff --git a/docs/tutorial/3-class-based-views.md b/docs/tutorial/3-class-based-views.md index 616a60586..21255a68e 100644 --- a/docs/tutorial/3-class-based-views.md +++ b/docs/tutorial/3-class-based-views.md @@ -57,7 +57,7 @@ So far, so good. It looks pretty similar to the previous case, but we've got be comment = serializer.deserialized comment.save() return Response(serializer.data) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + return Response(serializer.error_data, status=status.HTTP_400_BAD_REQUEST) def delete(self, request, pk, format=None): comment = self.get_object(pk) @@ -79,27 +79,38 @@ We can compose those mixin classes, to recreate our existing API behaviour with from blog.models import Comment from blog.serializers import CommentSerializer - from djangorestframework import mixins, views + from djangorestframework import mixins + from djangorestframework import views - class CommentRoot(mixins.ListModelQuerysetMixin, - mixins.CreateModelInstanceMixin, - views.BaseRootAPIView): + + class CommentRoot(mixins.ListModelMixin, + mixins.CreateModelMixin, + views.MultipleObjectBaseView): model = Comment serializer_class = CommentSerializer - get = list - post = create + def get(self, request, *args, **kwargs): + return self.list(request, *args, **kwargs) - class CommentInstance(mixins.RetrieveModelInstanceMixin, - mixins.UpdateModelInstanceMixin, - mixins.DestroyModelInstanceMixin, - views.BaseInstanceAPIView): + def post(self, request, *args, **kwargs): + return self.create(request, *args, **kwargs) + + + class CommentInstance(mixins.RetrieveModelMixin, + mixins.UpdateModelMixin, + mixins.DestroyModelMixin, + views.SingleObjectBaseView): model = Comment serializer_class = CommentSerializer - get = retrieve - put = update - delete = destroy + def get(self, request, *args, **kwargs): + return self.retrieve(request, *args, **kwargs) + + def put(self, request, *args, **kwargs): + return self.update(request, *args, **kwargs) + + def delete(self, request, *args, **kwargs): + return self.destroy(request, *args, **kwargs) ## Reusing generic class based views @@ -109,11 +120,11 @@ That's a lot less code than before, but we can go one step further still. REST from blog.serializers import CommentSerializer from djangorestframework import views - class CommentRoot(views.RootAPIView): + class CommentRoot(views.RootModelView): model = Comment serializer_class = CommentSerializer - class CommentInstance(views.InstanceAPIView): + class CommentInstance(views.InstanceModelView): model = Comment serializer_class = CommentSerializer