Skip to content

Commit

Permalink
Merge pull request miki725#29 from miki725/drf3
Browse files Browse the repository at this point in the history
DRF3 support
  • Loading branch information
miki725 committed Feb 9, 2015
2 parents 9db7753 + 32e2247 commit eeead95
Show file tree
Hide file tree
Showing 19 changed files with 455 additions and 123 deletions.
8 changes: 7 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,14 @@ python:
- "2.7"
- "pypy"

env:
- "DRF='djangorestframework<3'"
- "DRF='djangorestframework>=3'"

# command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors
install: pip install -r requirements-dev.txt
install:
- pip install $DRF
- pip install -r requirements-dev.txt

# command to run tests, e.g. python setup.py test
script: make check
1 change: 1 addition & 0 deletions AUTHORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Contributors
* Davide Mendolia - https://github.com/davideme
* Kevin Brown - https://github.com/kevin-brown
* Martin Cavoj - https://github.com/macav
* Matthias Erll - https://github.com/merll
* Mjumbe Poe - https://github.com/mjumbewu
* Thomas Wajs - https://github.com/thomasWajs
* Xavier Ordoquy - https://github.com/xordoquy
10 changes: 9 additions & 1 deletion HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@
History
-------

0.1.4 (2014-02-01)
0.2 (2015-02-09)
~~~~~~~~~~~~~~~~

* Added DRF3 support. Please note that DRF2 is still supported.
Now we support both DRF2 and DRF3!
* Fixed an issue when using viewsets, single resource update was not working due
to ``get_object()`` overwrite in viewset.

0.1.4 (2015-02-01)
~~~~~~~~~~~~~~~~~~

* Added base model viewset.
Expand Down
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ COVER_FLAGS=${COVER_CONFIG_FLAGS} ${COVER_REPORT_FLAGS}

help:
@echo "install - install all requirements including for testing"
@echo "install-quite - same as install but pipes all output to /dev/null"
@echo "clean - remove all artifacts"
@echo "clean-build - remove build artifacts"
@echo "clean-pyc - remove Python file artifacts"
Expand All @@ -23,6 +24,9 @@ help:
install:
pip install -r requirements-dev.txt

install-quite:
pip install -r requirements-dev.txt > /dev/null

clean: clean-build clean-pyc clean-test-all

clean-build:
Expand Down
47 changes: 42 additions & 5 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ within the framework. That is the purpose of this project.
Requirements
------------

* Python (2.6, 2.7 and 3.3)
* Python 2.7+
* Django 1.3+
* Django REST Framework >= 2.2.5 (when bulk features were added to serializers), < 3.0
* Django REST Framework >= 2.2.5 (when bulk features were added to serializers)
* Django REST Framework >= 3.0.0 (DRF-bulk supports both DRF2 and DRF3!)

Installing
----------
Expand All @@ -42,9 +43,21 @@ Example
The bulk views (and mixins) are very similar to Django REST Framework's own
generic views (and mixins)::

from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView
from rest_framework_bulk import (
BulkListSerializer,
BulkSerializerMixin,
ListBulkCreateUpdateDestroyAPIView,
)

class FooSerializer(BulkSerializerMixin, ModelSerializer):
class Meta(object):
model = FooModel
# only necessary in DRF3
list_serializer_class = BulkListSerializer

class FooView(ListBulkCreateUpdateDestroyAPIView):
model = FooModel
queryset = FooModel.objects.all()
serializer_class = FooSerializer

The above will allow to create the following queries

Expand Down Expand Up @@ -85,7 +98,7 @@ The above will allow to create the following queries
Router
------

The bulk router can map automatically the bulk actions::
The bulk router can automatically map the bulk actions::

from rest_framework_bulk.routes import BulkRouter

Expand All @@ -98,6 +111,30 @@ The bulk router can map automatically the bulk actions::
router = BulkRouter()
router.register(r'users', UserViewSet)

DRF3
----

Django REST Framework made many API changes which included major changes
in serializers. As a result, please note the following in order to use
DRF-bulk with DRF3:

* You must specify custom ``list_serializer_class`` if your view(set)
will require update functionality (when using ``BulkUpdateModelMixin``)
* DRF3 removes read-only fields from ``serializer.validated_data``.
As a result, it is impossible to correlate each ``validated_data``
in ``ListSerializer`` with a model instance to update since ``validated_data``
will be missing the model primary key since that is a read-only field.
To deal with that, you must use ``BulkSerializerMixin`` mixin in your serializer
class which will add the model primary key field back to the ``validated_data``.
By default ``id`` field is used however you can customize that field
by using ``update_lookup_field`` in the serializers ``Meta``::

class FooSerializer(BulkSerializerMixin, ModelSerializer):
class Meta(object):
model = FooModel
list_serializer_class = BulkListSerializer
update_lookup_field = 'slug'

Notes
-----

Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
django
djangorestframework<3
djangorestframework
3 changes: 2 additions & 1 deletion rest_framework_bulk/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
__version__ = '0.1.4'
__version__ = '0.2'
__author__ = 'Miroslav Shubernetskiy'

try:
from .generics import * # noqa
from .mixins import * # noqa
from .serializers import * # noqa
except Exception:
pass
1 change: 1 addition & 0 deletions rest_framework_bulk/drf2/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from __future__ import print_function, unicode_literals
122 changes: 122 additions & 0 deletions rest_framework_bulk/drf2/mixins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
from __future__ import unicode_literals, print_function
from django.core.exceptions import ValidationError
from rest_framework import status
from rest_framework.mixins import CreateModelMixin
from rest_framework.response import Response


__all__ = [
'BulkCreateModelMixin',
'BulkDestroyModelMixin',
'BulkUpdateModelMixin',
]


class BulkCreateModelMixin(CreateModelMixin):
"""
Either create a single or many model instances in bulk by using the
Serializers ``many=True`` ability from Django REST >= 2.2.5.
.. note::
This mixin uses the same method to create model instances
as ``CreateModelMixin`` because both non-bulk and bulk
requests will use ``POST`` request method.
"""

def create(self, request, *args, **kwargs):
bulk = isinstance(request.DATA, list)

if not bulk:
return super(BulkCreateModelMixin, self).create(request, *args, **kwargs)

else:
serializer = self.get_serializer(data=request.DATA, many=True)
if serializer.is_valid():
for obj in serializer.object:
self.pre_save(obj)
self.object = serializer.save(force_insert=True)
for obj in self.object:
self.post_save(obj, created=True)
return Response(serializer.data, status=status.HTTP_201_CREATED)

return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


class BulkUpdateModelMixin(object):
"""
Update model instances in bulk by using the Serializers
``many=True`` ability from Django REST >= 2.2.5.
"""

def get_object(self, queryset=None):
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field

if any((lookup_url_kwarg in self.kwargs,
self.pk_url_kwarg in self.kwargs,
self.slug_url_kwarg in self.kwargs)):
return super(BulkUpdateModelMixin, self).get_object(queryset)

# If the lookup_url_kwarg (or other deprecated variations)
# are not present, get_object() is most likely called
# as part of metadata() which by default simply checks
# for object permissions and raises permission denied if necessary.
# Here we don't need to check for general permissions
# and can simply return None since general permissions
# are checked in initial() which always gets executed
# before any of the API actions (e.g. create, update, etc)
return

def bulk_update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)

# restrict the update to the filtered queryset
serializer = self.get_serializer(self.filter_queryset(self.get_queryset()),
data=request.DATA,
many=True,
partial=partial)

if serializer.is_valid():
try:
for obj in serializer.object:
self.pre_save(obj)
except ValidationError as err:
# full_clean on model instances may be called in pre_save
# so we have to handle eventual errors.
return Response(err.message_dict, status=status.HTTP_400_BAD_REQUEST)
self.object = serializer.save(force_update=True)
for obj in self.object:
self.post_save(obj, created=False)
return Response(serializer.data, status=status.HTTP_200_OK)

return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

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


class BulkDestroyModelMixin(object):
"""
Destroy model instances.
"""

def allow_bulk_destroy(self, qs, filtered):
"""
Hook to ensure that the bulk destroy should be allowed.
By default this checks that the destroy is only applied to
filtered querysets.
"""
return qs is not filtered

def bulk_destroy(self, request, *args, **kwargs):
qs = self.get_queryset()
filtered = self.filter_queryset(qs)
if not self.allow_bulk_destroy(qs, filtered):
return Response(status=status.HTTP_400_BAD_REQUEST)

for obj in filtered:
self.pre_delete(obj)
obj.delete()
self.post_delete(obj)
return Response(status=status.HTTP_204_NO_CONTENT)
15 changes: 15 additions & 0 deletions rest_framework_bulk/drf2/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from __future__ import print_function, unicode_literals


__all__ = [
'BulkListSerializer',
'BulkSerializerMixin',
]


class BulkSerializerMixin(object):
pass


class BulkListSerializer(object):
pass
1 change: 1 addition & 0 deletions rest_framework_bulk/drf3/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from __future__ import print_function, unicode_literals
Loading

0 comments on commit eeead95

Please sign in to comment.