From 4b691c402707775c3048a90531024f3bc5be6f91 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 20 Sep 2012 13:06:27 +0100 Subject: Change package name: djangorestframework -> rest_framework --- rest_framework/mixins.py | 97 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 rest_framework/mixins.py (limited to 'rest_framework/mixins.py') diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py new file mode 100644 index 00000000..3dd4a047 --- /dev/null +++ b/rest_framework/mixins.py @@ -0,0 +1,97 @@ +""" +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. + +Eg. Use mixins to build a Resource class, and have a Router class + perform the binding of http methods to actions for us. +""" +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) + if serializer.is_valid(): + self.object = serializer.object + self.object.save() + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, 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.object + self.object.save() + return Response(serializer.data) + return Response(serializer.errors, 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) + + +class MetadataMixin(object): + """ + Return a dicitonary of view metadata. + Should be mixed in with any `BaseView`. + + This mixin is typically used for the HTTP 'OPTIONS' method. + """ + def metadata(self, request, *args, **kwargs): + content = { + 'name': self.get_name(), + 'description': self.get_description(), + 'renders': self._rendered_media_types, + 'parses': self._parsed_media_types, + } + # TODO: Add 'fields', from serializer info. + # form = self.get_bound_form() + # if form is not None: + # field_name_types = {} + # for name, field in form.fields.iteritems(): + # field_name_types[name] = field.__class__.__name__ + # content['fields'] = field_name_types + return Response(content, status=status.HTTP_200_OK) -- cgit v1.2.3 From f4670c89969503919cd597529f19174e67acd388 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 20 Sep 2012 14:00:53 +0100 Subject: Hack out bunch of unneccesary private methods on View class --- rest_framework/mixins.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'rest_framework/mixins.py') diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py index 3dd4a047..33a363d3 100644 --- a/rest_framework/mixins.py +++ b/rest_framework/mixins.py @@ -84,8 +84,8 @@ class MetadataMixin(object): content = { 'name': self.get_name(), 'description': self.get_description(), - 'renders': self._rendered_media_types, - 'parses': self._parsed_media_types, + 'renders': [renderer.media_type for renderer in self.renderer_classes], + 'parses': [parser.media_type for parser in self.parser_classes], } # TODO: Add 'fields', from serializer info. # form = self.get_bound_form() -- cgit v1.2.3 From ee36e4ab0c0508a590c6b73a23ec82b7f1e49bd0 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 27 Sep 2012 21:51:46 +0100 Subject: Only display forms when user has permissions. #159 --- rest_framework/mixins.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'rest_framework/mixins.py') diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py index 33a363d3..fe12dc8f 100644 --- a/rest_framework/mixins.py +++ b/rest_framework/mixins.py @@ -88,8 +88,8 @@ class MetadataMixin(object): 'parses': [parser.media_type for parser in self.parser_classes], } # TODO: Add 'fields', from serializer info. - # form = self.get_bound_form() - # if form is not None: + # serializer = self.get_serializer() + # if serializer is not None: # field_name_types = {} # for name, field in form.fields.iteritems(): # field_name_types[name] = field.__class__.__name__ -- cgit v1.2.3 From 6fa589fefd48d98e4f0a11548b6c3e5ced58e31e Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sun, 30 Sep 2012 17:31:28 +0100 Subject: Pagination support --- rest_framework/mixins.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) (limited to 'rest_framework/mixins.py') diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py index fe12dc8f..167cd89a 100644 --- a/rest_framework/mixins.py +++ b/rest_framework/mixins.py @@ -7,6 +7,7 @@ which allows mixin classes to be composed in interesting ways. Eg. Use mixins to build a Resource class, and have a Router class perform the binding of http methods to actions for us. """ +from django.http import Http404 from rest_framework import status from rest_framework.response import Response @@ -30,9 +31,27 @@ class ListModelMixin(object): List a queryset. Should be mixed in with `MultipleObjectBaseView`. """ + empty_error = u"Empty list and '%(class_name)s.allow_empty' is False." + def list(self, request, *args, **kwargs): self.object_list = self.get_queryset() - serializer = self.get_serializer(instance=self.object_list) + + # 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 len(self.object_list) == 0: + error_args = {'class_name': self.__class__.__name__} + raise Http404(self.empty_error % error_args) + + # 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: + paginator, page, queryset, is_paginated = self.paginate_queryset(self.object_list, page_size) + serializer = self.get_pagination_serializer(page) + else: + serializer = self.get_serializer(instance=self.object_list) + return Response(serializer.data) -- cgit v1.2.3 From b89125ef53a2f9e246afd5eda5c8f404a714da76 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 2 Oct 2012 21:26:15 +0100 Subject: Update view docs slightly --- rest_framework/mixins.py | 1 + 1 file changed, 1 insertion(+) (limited to 'rest_framework/mixins.py') diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py index 167cd89a..d391755c 100644 --- a/rest_framework/mixins.py +++ b/rest_framework/mixins.py @@ -92,6 +92,7 @@ class DestroyModelMixin(object): return Response(status=status.HTTP_204_NO_CONTENT) +# TODO: Remove MetadataMixin, and implement on APIView.options() class MetadataMixin(object): """ Return a dicitonary of view metadata. -- cgit v1.2.3 From 7c303411a914430252f482ca24501bf02ba7193c Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 3 Oct 2012 11:08:02 +0100 Subject: Add Serializer.save() to clean up differences between serializer.object being either a DeserializedObject or a model instance --- rest_framework/mixins.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'rest_framework/mixins.py') diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py index d391755c..e3c7cf03 100644 --- a/rest_framework/mixins.py +++ b/rest_framework/mixins.py @@ -20,8 +20,7 @@ class CreateModelMixin(object): def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.DATA) if serializer.is_valid(): - self.object = serializer.object - self.object.save() + self.object = serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @@ -75,8 +74,7 @@ class UpdateModelMixin(object): self.object = self.get_object() serializer = self.get_serializer(data=request.DATA, instance=self.object) if serializer.is_valid(): - self.object = serializer.object - self.object.save() + self.object = serializer.save() return Response(serializer.data) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) -- cgit v1.2.3 From 2533c2452b383771f80e6d40c93ae4a5ef6a8cf7 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 5 Oct 2012 16:24:52 +0100 Subject: Support PUT for create --- rest_framework/mixins.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) (limited to 'rest_framework/mixins.py') diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py index e3c7cf03..46821f64 100644 --- a/rest_framework/mixins.py +++ b/rest_framework/mixins.py @@ -71,13 +71,38 @@ class UpdateModelMixin(object): Should be mixed in with `SingleObjectBaseView`. """ def update(self, request, *args, **kwargs): - self.object = self.get_object() + try: + self.object = self.get_object() + except Http404: + self.object = None + serializer = self.get_serializer(data=request.DATA, instance=self.object) if serializer.is_valid(): + if self.object is None: + obj = serializer.object + # TODO: Make ModelSerializers return regular instances, + # not DeserializedObject + if hasattr(obj, 'object'): + obj = obj.object + self.update_urlconf_attributes(serializer.object.object) self.object = serializer.save() return Response(serializer.data) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + def update_urlconf_attributes(self, obj): + """ + When update (re)creates an object, we need to set any attributes that + are tied to the URLconf. + """ + 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) + class DestroyModelMixin(object): """ -- cgit v1.2.3 From 52ba2e333375c6829fb89b6b43e4d19b2f2a86a4 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 8 Oct 2012 12:52:56 +0100 Subject: Fix #285 --- rest_framework/mixins.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) (limited to 'rest_framework/mixins.py') diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py index 46821f64..7cfbe030 100644 --- a/rest_framework/mixins.py +++ b/rest_framework/mixins.py @@ -77,16 +77,15 @@ class UpdateModelMixin(object): self.object = None serializer = self.get_serializer(data=request.DATA, instance=self.object) + if serializer.is_valid(): if self.object is None: - obj = serializer.object - # TODO: Make ModelSerializers return regular instances, - # not DeserializedObject - if hasattr(obj, 'object'): - obj = obj.object - self.update_urlconf_attributes(serializer.object.object) + # If PUT occurs to a non existant object, we need to set any + # attributes on the object that are implicit in the URL. + self.update_urlconf_attributes(serializer.object) self.object = serializer.save() return Response(serializer.data) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) def update_urlconf_attributes(self, obj): -- cgit v1.2.3 From f79ed6175df62f68dba9179f792f95b6ac6dca14 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 8 Oct 2012 14:13:15 +0100 Subject: Add RetrieveDestroyAPIView and remove Metadata mixin --- rest_framework/mixins.py | 25 ------------------------- 1 file changed, 25 deletions(-) (limited to 'rest_framework/mixins.py') diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py index 7cfbe030..29153e18 100644 --- a/rest_framework/mixins.py +++ b/rest_framework/mixins.py @@ -112,28 +112,3 @@ class DestroyModelMixin(object): self.object = self.get_object() self.object.delete() return Response(status=status.HTTP_204_NO_CONTENT) - - -# TODO: Remove MetadataMixin, and implement on APIView.options() -class MetadataMixin(object): - """ - Return a dicitonary of view metadata. - Should be mixed in with any `BaseView`. - - This mixin is typically used for the HTTP 'OPTIONS' method. - """ - def metadata(self, request, *args, **kwargs): - content = { - 'name': self.get_name(), - 'description': self.get_description(), - 'renders': [renderer.media_type for renderer in self.renderer_classes], - 'parses': [parser.media_type for parser in self.parser_classes], - } - # TODO: Add 'fields', from serializer info. - # serializer = self.get_serializer() - # if serializer is not None: - # field_name_types = {} - # for name, field in form.fields.iteritems(): - # field_name_types[name] = field.__class__.__name__ - # content['fields'] = field_name_types - return Response(content, status=status.HTTP_200_OK) -- cgit v1.2.3 From 8c360770c18ac38a2f4da81a3553fb40592558c4 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 25 Oct 2012 12:15:31 +0100 Subject: Add pre_save hook in generic views --- rest_framework/mixins.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) (limited to 'rest_framework/mixins.py') diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py index 29153e18..8873e4ae 100644 --- a/rest_framework/mixins.py +++ b/rest_framework/mixins.py @@ -20,10 +20,14 @@ class CreateModelMixin(object): def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.DATA) if serializer.is_valid(): + self.pre_save(serializer.object) self.object = serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + def pre_save(self, obj): + pass + class ListModelMixin(object): """ @@ -46,7 +50,8 @@ class ListModelMixin(object): # which may be `None` to disable pagination. page_size = self.get_paginate_by(self.object_list) if page_size: - paginator, page, queryset, is_paginated = self.paginate_queryset(self.object_list, 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(instance=self.object_list) @@ -79,20 +84,17 @@ class UpdateModelMixin(object): serializer = self.get_serializer(data=request.DATA, instance=self.object) if serializer.is_valid(): - if self.object is None: - # If PUT occurs to a non existant object, we need to set any - # attributes on the object that are implicit in the URL. - self.update_urlconf_attributes(serializer.object) + self.pre_save(serializer.object) self.object = serializer.save() return Response(serializer.data) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - def update_urlconf_attributes(self, obj): + def pre_save(self, obj): """ - When update (re)creates an object, we need to set any attributes that - are tied to the URLconf. + 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) -- cgit v1.2.3