Giter Club home page Giter Club logo

django-rest-framework-bulk's Introduction

Django REST Framework Bulk

image

image

Django REST Framework bulk CRUD view mixins.

Overview

Django REST Framework comes with many generic views however none of them allow to do bulk operations such as create, update and delete. To keep the core of Django REST Framework simple, its maintainer suggested to create a separate project to allow for bulk operations within the framework. That is the purpose of this project.

Requirements

  • Python>=2.7
  • Django>=1.3
  • Django REST Framework >= 3.0.0
  • REST Framework >= 2.2.5 (only with Django<1.8 since DRF<3 does not support Django1.8)

Installing

Using pip:

$ pip install djangorestframework-bulk

or from source code:

$ pip install -e git+http://github.com/miki725/django-rest-framework-bulk#egg=djangorestframework-bulk

Example

The bulk views (and mixins) are very similar to Django REST Framework's own generic views (and mixins):

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):
    queryset = FooModel.objects.all()
    serializer_class = FooSerializer

The above will allow to create the following queries

# list queryset
GET
# create single resource
POST
{"field":"value","field2":"value2"}     <- json object in request data
# create multiple resources
POST
[{"field":"value","field2":"value2"}]
# update multiple resources (requires all fields)
PUT
[{"field":"value","field2":"value2"}]   <- json list of objects in data
# partial update multiple resources
PATCH
[{"field":"value"}]                     <- json list of objects in data
# delete queryset (see notes)
DELETE

Router

The bulk router can automatically map the bulk actions:

from rest_framework_bulk.routes import BulkRouter

class UserViewSet(BulkModelViewSet):
    model = User

    def allow_bulk_destroy(self, qs, filtered):
        """Don't forget to fine-grain this method"""

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

Most API urls have two URL levels for each resource:

  1. url(r'foo/', ...)
  2. url(r'foo/(?P<pk>\d+)/', ...)

The second url however is not applicable for bulk operations because the url directly maps to a single resource. Therefore all bulk generic views only apply to the first url.

There are multiple generic view classes in case only a certail bulk functionality is required. For example ListBulkCreateAPIView will only do bulk operations for creating resources. For a complete list of available generic view classes, please take a look at the source code at generics.py as it is mostly self-explanatory.

Most bulk operations are pretty safe in terms of how they operate, that is you explicitly describe all requests. For example, if you need to update 3 specific resources, you have to explicitly identify those resources in the request's PUT or PATCH data. The only exception to this is bulk delete. Consider a DELETE request to the first url. That can potentially delete all resources without any special confirmation. To try to account for this, bulk delete mixin allows to implement a hook to determine if the bulk delete request should be allowed:

class FooView(BulkDestroyAPIView):
    def allow_bulk_destroy(self, qs, filtered):
        # custom logic here

        # default checks if the qs was filtered
        # qs comes from self.get_queryset()
        # filtered comes from self.filter_queryset(qs)
        return qs is not filtered

By default it checks if the queryset was filtered and if not will not allow the bulk delete to complete. The logic here is that if the request is filtered to only get certain resources, more attention was payed hence the action is less likely to be accidental. On how to filter requests, please refer to Django REST docs. Either way, please use bulk deletes with extreme caution since they can be dangerous.

django-rest-framework-bulk's People

Contributors

bounder avatar davideme avatar miki725 avatar mjumbewu avatar thomaswajs avatar xordoquy avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

django-rest-framework-bulk's Issues

Unique constraint fails when trying to bulk update

I'm trying to use the new bulk update that uses DRF 3. When I try to bulk update with a model that uses a unique_together constraint I get an error: 'QuerySet' object has no attribute 'pk'

Here is the code

#the model
class Answer(models.Model):
    user = models.ForeignKey(User, null=False, blank=False)
    parameter = models.ForeignKey(Parameter, null=False, blank=False)
    answer_number = models.FloatField(null=True, blank=True)
    answer_date = models.DateField(null=True, blank=True)
    answer_boolean = models.BooleanField(default=False)
    answer_currency = models.DecimalField(null=True, blank=True, max_digits=24, decimal_places=8)

    class Meta:
        unique_together = ("user", "parameter")

#the serializer
class AnswerSerializer(BulkSerializerMixin, serializers.ModelSerializer):
    answer = NoValidationField()

    class Meta:
        model = Answer
        fields = ['id', 'answer', 'parameter', 'user']
        list_serializer_class = BulkListSerializer

#the view
class AnswerList(ListBulkCreateUpdateAPIView):
    queryset = Answer.objects.all()
    serializer_class = AnswerSerializer

I tried to debug the problem and I think it is a validation problem. Plase help.

TY

BulkSerializerMixin makes Serializers difficult to test

BulkSerializerMixin.to_internal_values(...) does this:

        request_method = getattr(getattr(self.context.get('view'), 'request'), 'method', '')

So, given the serializer code:

from rest_framework import serializers
from rest_framework_bulk import BulkListSerializer, BulkSerializerMixin
class SimpleSerializer(BulkSerializerMixin, serializers.Serializer):
    simple_field = serializers.CharField()
    class Meta:
        list_serializer_class = BulkListSerializer

And a test case:

from django.test import TestCase
from myproject.serializers import SimpleSerializer
class SimpleSerializerTestCase(TestCase):

    def test_single(self):
        data = {
            "simple_field": "foo",
        }
        serializer = SimpleSerializer(data=data)
        self.assertTrue(serializer.is_valid())

    def test_multiple(self):
        data = [
            {"simple_field": "foo"},
            {"simple_field": "bar"},
        ]
        serializer = SimpleSerializer(data=data, many=True)
        self.assertTrue(serializer.is_valid())

both of these tests will fail with

Traceback (most recent call last):
...
File ".../local/lib/python2.7/site-packages/rest_framework_bulk/drf3/serializers.py", line 19, in to_internal_value
request_method = getattr(getattr(self.context.get('view'), 'request'), 'method', '')
AttributeError: 'NoneType' object has no attribute 'request'

One can initialize the Serializer with a mock, to get rid of the problem:

        import mock
        view = mock.Mock()
        view.request.method = "POST"
        serializer = SimpleSerializer(data=data, context={"view": view})
        self.assertTrue(serializer.is_valid())

but ideally, a Serializer should be testable in isolation, with no notion of a view involved.

I suggest one or more of:

  • Make to_internal_value() look at instance(s) somehow to determine which of the cases is appropriate. (I honestly have no clue if this is possible)
  • Ignore the lookup field if there is no view. Also not ideal for testing.
  • Add a note to the docs.
  • Add test cases to the project which tests against Serializers in isolation to discover other issues.

My apologies if I missed something obvious.

POST on Browsable API fails because of get_initial

The get_initial of a serializer normally looks like this:

def get_initial(self):
    if hasattr(self, 'initial_data'):
        return OrderedDict([
            (field_name, field.get_value(self.initial_data))
            for field_name, field in self.fields.items()
            if (field.get_value(self.initial_data) is not empty) and
            not field.read_only
        ])
     
    return OrderedDict([
        (field.field_name, field.get_initial())
        for field in self.fields.values()
        if not field.read_only
    ])

This breaks with AttributeError: 'list' object has no attribute 'get' if initial data is a list, such as after a POST on the browsable api (using the raw data tab to submit multiple objects).

I've fixed this in my code by switching get_initial on BulkSerializerMixin to:

def get_initial(self):
    return OrderedDict([
        (field.field_name, field.get_initial())
        for field in self.fields.values()
        if not field.read_only
    ])

A better solution might be to not set initial_data during a POST, but this was easier for me to find.

AttributeError: 'NoneType' object has no attribute 'request'

I've added BulkSerializerMixin to a view set (and set list_serializer_class to BulkListSerializer),
trying to PATCH a single object (which should not touch bulk operations at all), raises this error:
AttributeError: 'NoneType' object has no attribute 'request'
may be related to #39
using Django 1.10 with 3.4.6

Is it possible to support Python 3.2?

For those of us still on Ubuntu 12.04 LTS, we've only got access to Python 2.7 and 3.2.

Here's the error I get when trying to pip install djangorestframework-bulk :

Downloading/unpacking djangorestframework-bulk
  Running setup.py egg_info for package djangorestframework-bulk

Requirement already satisfied (use --upgrade to upgrade): django in /home/paul/.virtualenvs/api/lib/python3.2/site-packages (from djangorestframework-bulk)
Requirement already satisfied (use --upgrade to upgrade): djangorestframework in /home/paul/.virtualenvs/api/lib/python3.2/site-packages (from djangorestframework-bulk)
Installing collected packages: djangorestframework-bulk
  Running setup.py install for djangorestframework-bulk
    warning: install_data: setup script did not provide a directory for 'rest_framework_bulk' -- installing right in '/home/paul/.virtualenvs/api'

    error: can't copy 'rest_framework_bulk': doesn't exist or not a regular file
    Complete output from command /home/paul/.virtualenvs/api/bin/python3 -c "import setuptools;__file__='/home/paul/.virtualenvs/api/build/djangorestframework-bulk/setup.py';exec(compile(open(__file__).read().replace('\r\n', '\n'), __file__, 'exec'))" install --single-version-externally-managed --record /tmp/pip-4qgzps-record/install-record.txt --install-headers /home/paul/.virtualenvs/api/include/site/python3.2:
    running install

running build

running build_py

running install_lib

running install_data

warning: install_data: setup script did not provide a directory for 'rest_framework_bulk' -- installing right in '/home/paul/.virtualenvs/api'



error: can't copy 'rest_framework_bulk': doesn't exist or not a regular file

----------------------------------------
Command /home/paul/.virtualenvs/api/bin/python3 -c "import setuptools;__file__='/home/paul/.virtualenvs/api/build/djangorestframework-bulk/setup.py';exec(compile(open(__file__).read().replace('\r\n', '\n'), __file__, 'exec'))" install --single-version-externally-managed --record /tmp/pip-4qgzps-record/install-record.txt --install-headers /home/paul/.virtualenvs/api/include/site/python3.2 failed with error code 1 in /home/paul/.virtualenvs/api/build/djangorestframework-bulk
Storing complete log in /home/paul/.pip/pip.log

routes.py missing by installing by pip

Hello,

When installing by pip with the command "pip install djangorestframework-bulk" the file routes.py is not present in the sources.

By adding it manually from the sources to the installed folder, it does work!
I guess I should add it in the project itself and not in the sources till this is fixed.

Regards,
Alex

compatibility with mongodb (specifically with https://github.com/umutbozkurt/django-rest-framework-mongoengine)

The update method of a bulkSerializer is not working if the id is not of type string/unicode for example a bson.objectid.ObjectId

mine solution was to force the unicode type in the BulkListSerializer

class BulkListSerializer(ListSerializer):
update_lookup_field = 'id'

def update(self, queryset, all_validated_data):
    id_attr = getattr(self.child.Meta, 'update_lookup_field', 'id')
    all_validated_data_by_id = {
        i.pop(id_attr): i
        for i in all_validated_data
    }

    if not all((bool(i) and not inspect.isclass(i)
                for i in all_validated_data_by_id.keys())):
        raise ValidationError('')

    # since this method is given a queryset which can have many
    # model instances, first find all objects to update
    # and only then update the models
    objects_to_update = queryset.filter(**{
        '{}__in'.format(id_attr): all_validated_data_by_id.keys(),
    })

    if len(all_validated_data_by_id) != objects_to_update.count():
        raise ValidationError('Could not find all objects to update.')

    updated_objects = []
    for obj in objects_to_update:
        obj_id = getattr(obj, id_attr)
        #here
        obj_validated_data = all_validated_data_by_id.get(unicode(obj_id))
        # use model serializer to actually update the model
        # in case that method is overwritten
        updated_objects.append(self.child.update(obj, obj_validated_data))

    return updated_objects

I think is not a "portable solution" but make the job for me.

TypeError: as_view() takes exactly 1 argument (3 given)

Hi,
I'm use Django 1.9 and DRF3.
I tried follow the tutorial to create my ListBulkCreateUpdateDestroyAPIView.
I had problem to register the route.

I changed to BulkModelViewSet and fix my problem.

Can you help me to user ListBulkCreateUpdateDestroyAPIView?

tests or sample implementation?

Without a test suite, it's difficult for me to understand how to actually use this. Could someone post a sample implementation for BulkUpdateAPIView?

Bulk Serializer work with HyperLinkedModelSerializer('url')

Greetings,
I liked the framework and have been using it, but wondering if the framework supports HyperLinkedModelSerializer fields when doing an update.
I went ahead and made some changes to get this to work, but wondering if it is already supported.

thanks
vjnt

AttributeError: type object 'ContactsViewSet' has no attribute 'get_extra_actions'

Guys, I am not able to raise the server due to this error after I have inserted the necessary classes for the use of Bulk.

Unhandled exception in thread started by <function check_errors.<locals>.wrapper at 0x7f8493c1d620>
Traceback (most recent call last):
  File "/var/www/django/WebWhatsAppExploitServer/venv/lib64/python3.6/site-packages/django/utils/autoreload.py", line 225, in wrapper
    fn(*args, **kwargs)
  File "/var/www/django/WebWhatsAppExploitServer/venv/lib64/python3.6/site-packages/django/core/management/commands/runserver.py", line 117, in inner_run
    self.check(display_num_errors=True)
  File "/var/www/django/WebWhatsAppExploitServer/venv/lib64/python3.6/site-packages/django/core/management/base.py", line 379, in check
    include_deployment_checks=include_deployment_checks,
  File "/var/www/django/WebWhatsAppExploitServer/venv/lib64/python3.6/site-packages/django/core/management/base.py", line 366, in _run_checks
    return checks.run_checks(**kwargs)
  File "/var/www/django/WebWhatsAppExploitServer/venv/lib64/python3.6/site-packages/django/core/checks/registry.py", line 71, in run_checks
    new_errors = check(app_configs=app_configs)
  File "/var/www/django/WebWhatsAppExploitServer/venv/lib64/python3.6/site-packages/django/core/checks/urls.py", line 13, in check_url_config
    return check_resolver(resolver)
  File "/var/www/django/WebWhatsAppExploitServer/venv/lib64/python3.6/site-packages/django/core/checks/urls.py", line 23, in check_resolver
    return check_method()
  File "/var/www/django/WebWhatsAppExploitServer/venv/lib64/python3.6/site-packages/django/urls/resolvers.py", line 396, in check
    for pattern in self.url_patterns:
  File "/var/www/django/WebWhatsAppExploitServer/venv/lib64/python3.6/site-packages/django/utils/functional.py", line 37, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
  File "/var/www/django/WebWhatsAppExploitServer/venv/lib64/python3.6/site-packages/django/urls/resolvers.py", line 533, in url_patterns
    patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
  File "/var/www/django/WebWhatsAppExploitServer/venv/lib64/python3.6/site-packages/django/utils/functional.py", line 37, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
  File "/var/www/django/WebWhatsAppExploitServer/venv/lib64/python3.6/site-packages/django/urls/resolvers.py", line 526, in urlconf_module
    return import_module(self.urlconf_name)
  File "/opt/rh/rh-python36/root/usr/lib64/python3.6/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 994, in _gcd_import
  File "<frozen importlib._bootstrap>", line 971, in _find_and_load
  File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 665, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 678, in exec_module
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "/var/www/django/WebWhatsAppExploitServer/APICAMPAIGN/urls.py", line 19, in <module>
    path('api/', include('bots_api.urls')),
  File "/var/www/django/WebWhatsAppExploitServer/venv/lib64/python3.6/site-packages/django/urls/conf.py", line 34, in include
    urlconf_module = import_module(urlconf_module)
  File "/opt/rh/rh-python36/root/usr/lib64/python3.6/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 994, in _gcd_import
  File "<frozen importlib._bootstrap>", line 971, in _find_and_load
  File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 665, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 678, in exec_module
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "/var/www/django/WebWhatsAppExploitServer/bots_api/urls.py", line 48, in <module>
    path('',bulk_router.get_urls()),
  File "/var/www/django/WebWhatsAppExploitServer/venv/lib64/python3.6/site-packages/rest_framework/routers.py", line 363, in get_urls
    urls = super(DefaultRouter, self).get_urls()
  File "/var/www/django/WebWhatsAppExploitServer/venv/lib64/python3.6/site-packages/rest_framework/routers.py", line 261, in get_urls
    routes = self.get_routes(viewset)
  File "/var/www/django/WebWhatsAppExploitServer/venv/lib64/python3.6/site-packages/rest_framework/routers.py", line 176, in get_routes
    extra_actions = viewset.get_extra_actions()
AttributeError: type object 'ContactsViewSet' has no attribute 'get_extra_actions'

Here's a snippet of what I'm doing.

(app)/urls.py

urlpatterns = [
    path('api/', include('bots_api.urls')),
]

bots_api/urls.py

bulk_routes = [
    ('contacts/list', ContactsViewSet, 'Contacts List'),
    ('contacts/details', ContactsDetailsViewSet, 'Contacts List'),
    ('contacts/blacklist', ContactBlackListViewSet, 'Contacts Black List')
]
bulk_router = BulkRouter()

for route in bulk_routes:
    bulk_router.register(route[0],route[1],route[2])
urlpatterns = [
    path('',include(bulk_router.urls)),
]

serializers/contacts.py

class ContactsSerializer(BulkSerializerMixin, serializers.ModelSerializer):
    class Meta(object):
        from bots_api.models import Contacts
        model = Contacts
        fields = ('__all__')
        list_serializer_class = BulkListSerializer

views/contacts.py

class ContactsViewSet(ListBulkCreateUpdateDestroyAPIView):
    from bots_api.lib.serializers.contacts import ContactsSerializer
    serializer_class = ContactsSerializer

    filter_backends = (filters.OrderingFilter,
                       filters.SearchFilter,
                       DjangoFilterBackend)

    search_fields = ['phone_number', 'phone_region', 'phone_type', 'tag', 'created',
                     'updated']

    def get_queryset(self):

        contact_id = self.request.query_params.get('id', None)
        client_id = self.request.query_params.get('client', None)
        number = self.request.query_params.get('number', None)
        active = self.request.query_params.get('active', None)

        queryset = self.ContactsSerializer.Meta.Contacts.objects.all()

        if contact_id:
            queryset = queryset.filter(id=contact_id)

        if client_id:
            queryset = queryset.filter(client_id=client_id)

        if number:
            queryset = queryset.filter(phone_number__contains=number)

        if not active is None:
            queryset = queryset.filter(active=active)

        return queryset

I've been having this problem since yesterday, does anyone know where I might be going wrong?

POST 400 (Bad Request)

python 3.7.3
django 2.2.3
djangorestframework 3.9.4
djangorestframework-bulk 0.2.1

class Milestone(models.Model): project = models.ForeignKey(Project, on_delete=models.CASCADE) position = models.IntegerField(default=0) name = models.CharField(max_length=100, default='')

class MilestoneSerializer(BulkSerializerMixin, serializers.ModelSerializer): class Meta: model = Milestone fields = ['id','project', 'position', 'name',] list_serializer_class = BulkListSerializer

class MilestoneViewSet(BulkModelViewSet): queryset = Milestone.objects.all() serializer_class = MilestoneSerializer

work when posting one object:
data={'project':4,'position':21,'name':'test'}
but it does not work posting two objects:
data=[{'project':4,'position':21,'name':'test'},{'project':4,'position':22,'name':'test'}]

Can someone explain if it is a bug or I miss something ?

AttributeError: 'QuerySet' object has no attribute 'pk' when doing bulk update

When trying to PATCH with a list of dicts, I'm getting this error:

ERROR: test_bulk_update (api.tests.test_api.test_api_listings_bulk.TestBulkOperationsOnListings)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "E:\Development\django_projects\api\api.git\api\tests\test_api\test_api_listings_bulk.py", line 92, in test_bulk_update
    response = self.client.patch("/listings/all/", updated_listings, format="json")
  File "e:\Development\django_projects\api\lib\site-packages\rest_framework\test.py", line 315, in patch
    path, data=data, format=format, content_type=content_type, **extra)
  File "e:\Development\django_projects\api\lib\site-packages\rest_framework\test.py", line 220, in patch
    return self.generic('PATCH', path, data, content_type, **extra)
  File "e:\Development\django_projects\api\lib\site-packages\rest_framework\test.py", line 237, in generic
    method, path, data, content_type, secure, **extra)
  File "e:\Development\django_projects\api\lib\site-packages\django\test\client.py", line 416, in generic
    return self.request(**r)
  File "e:\Development\django_projects\api\lib\site-packages\rest_framework\test.py", line 288, in request
    return super(APIClient, self).request(**kwargs)
  File "e:\Development\django_projects\api\lib\site-packages\rest_framework\test.py", line 240, in request
    request = super(APIRequestFactory, self).request(**kwargs)
  File "e:\Development\django_projects\api\lib\site-packages\django\test\client.py", line 501, in request
    six.reraise(*exc_info)
  File "e:\Development\django_projects\api\lib\site-packages\django\utils\six.py", line 686, in reraise
    raise value
  File "e:\Development\django_projects\api\lib\site-packages\django\core\handlers\exception.py", line 41, in inner
    response = get_response(request)
  File "e:\Development\django_projects\api\lib\site-packages\django\core\handlers\base.py", line 187, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "e:\Development\django_projects\api\lib\site-packages\django\core\handlers\base.py", line 185, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "e:\Development\django_projects\api\lib\site-packages\django\views\decorators\csrf.py", line 58, in wrapped_view
    return view_func(*args, **kwargs)
  File "e:\Development\django_projects\api\lib\site-packages\rest_framework\viewsets.py", line 90, in view
    return self.dispatch(request, *args, **kwargs)
  File "e:\Development\django_projects\api\lib\site-packages\rest_framework\views.py", line 489, in dispatch
    response = self.handle_exception(exc)
  File "e:\Development\django_projects\api\lib\site-packages\rest_framework\views.py", line 449, in handle_exception
    self.raise_uncaught_exception(exc)
  File "e:\Development\django_projects\api\lib\site-packages\rest_framework\views.py", line 486, in dispatch
    response = handler(request, *args, **kwargs)
  File "e:\Development\django_projects\api\lib\site-packages\rest_framework_bulk\drf3\mixins.py", line 79, in partial_bulk_update
    return self.bulk_update(request, *args, **kwargs)
  File "e:\Development\django_projects\api\lib\site-packages\rest_framework_bulk\drf3\mixins.py", line 73, in bulk_update
    serializer.is_valid(raise_exception=True)
  File "e:\Development\django_projects\api\lib\site-packages\rest_framework\serializers.py", line 718, in is_valid
    self._validated_data = self.run_validation(self.initial_data)
  File "e:\Development\django_projects\api\lib\site-packages\rest_framework\serializers.py", line 596, in run_validation
    value = self.to_internal_value(data)
  File "e:\Development\django_projects\api\lib\site-packages\rest_framework\serializers.py", line 635, in to_internal_value
    validated = self.child.run_validation(item)
  File "e:\Development\django_projects\api\lib\site-packages\rest_framework\serializers.py", line 431, in run_validation
    value = self.to_internal_value(data)
  File "e:\Development\django_projects\api\lib\site-packages\rest_framework_bulk\drf3\serializers.py", line 16, in to_internal_value
    ret = super(BulkSerializerMixin, self).to_internal_value(data)
  File "e:\Development\django_projects\api\lib\site-packages\rest_framework\serializers.py", line 461, in to_internal_value
    validated_value = field.run_validation(primitive_value)
  File "e:\Development\django_projects\api\lib\site-packages\rest_framework\fields.py", line 776, in run_validation
    return super(CharField, self).run_validation(data)
  File "e:\Development\django_projects\api\lib\site-packages\rest_framework\fields.py", line 524, in run_validation
    self.run_validators(value)
  File "e:\Development\django_projects\api\lib\site-packages\rest_framework\fields.py", line 538, in run_validators
    validator(value)
  File "e:\Development\django_projects\api\lib\site-packages\rest_framework\validators.py", line 81, in __call__
    queryset = self.exclude_current_instance(queryset)
  File "e:\Development\django_projects\api\lib\site-packages\rest_framework\validators.py", line 75, in exclude_current_instance
    return queryset.exclude(pk=self.instance.pk)
AttributeError: 'QuerySet' object has no attribute 'pk'

Relevant code:

class ListingSerializer(BulkSerializerMixin, serializers.ModelSerializer):
    slug = serializers.ReadOnlyField()
    images = ListingImageSerializer(many=True, read_only=True)

    class Meta:
        model = Listing
        list_serializer_class = BulkListSerializer
        fields = '__all__'

class BaseListingViewSet(generics.BulkModelViewSet):
    queryset = Listing.objects.all()
    serializer_class = ListingSerializer

POSTing data works fine, by the way, it's only PATCH that generates this error.

I'm using

  • Django==1.11.7
  • djangorestframework==3.7.3
  • djangorestframework-bulk==0.2.1

Allow for validation of the entire collection

When creating, I have the need to validate the entire collection of items as a whole. For instance, there should be validation to ensure there are exactly three items present. At the moment, I do not see an easy way to accomplish this.

Create or update view?

Hi
Is there a way to have a list of items, and have them created or updated based on the existence of a PK?

Thanks

Django problems

def login_view(request):
    next = request.GET.get('next')
    title = 'Вхід'
    form = UserLoginForms(request.POST or None)
    if form.is_valid():
        username = form.changed_data.get('username')
        password = form.changed_data.get('password')
        user = authenticate(username=username, password=password)
        login(request, user)
        if next:
            return redirect(next)
        return redirect('/')

    return render(request, "form.html", {"form": form, "title": title})


def register_view(request):
    next = request.GET.get('next')
    title = 'Регістрація'
    form = UserRegisterForm(request.POST or None)
    if form.is_valid():
        user = form.save(commit=False)
        password = form.cleaned_data.get('password')
        user.set_password()
        user.save()
        new_user = authenticate(username=user.username, password=password)
        login(request, new_user)
        if next:
            return redirect(next)
        return redirect('/')

    context = {
        'form': form,
        'title': title,
    }
    return render(request, "form.html", context)

'list' object has no attribute 'get'
'list' object has no attribute 'get'

AttributeError: 'list' object has no attribute 'get' on POST with multiple objects

Hi, I have problems when I try to make bulk POST (with more than one objects) to django-rest-framework. Then I saw your project but again I don't have success, when I am trying to POST multiple objects I always receive AttributeError: 'list' object has no attribute 'get'. I tried to fix it but without success and I made a repo with basic example of my project.

The repo is here and the request that I am trying is something like this (I am sending it from the django-rest web view)

[{
    "name": "Some test name", 
    "degree": "-", 
    "short": "-", 
    "email": "[email protected]", 
    "department": "-"
},
{
    "name": "Trugvai..", 
    "degree": "-", 
    "short": "-", 
    "email": "[email protected]", 
    "department": "-"
}]

Thanks!

URL syntax for Bulk modifications

Although the Bulk Actions are present in my project I haven't used them to their full potential yet.
I'm beginning my tests with bulk deletes. Mymodel inherits from BulkModelViewSet.

In my urls.py file I define the URL used to interact with my model:

router.register(r"v1/mymodels", mymodels_views_v1.MyModelViewSet)

this allows me to GET, POST, PUT and DELETE on the URL:
www.my-api.com/v1/mymodels/{{mymodel_id}}

Can I use this same URL for bulk operations? If so, what is the correct syntax?
eg: www.my-api.com/v1/mymodels/[{{mymodel_id1}},{{mymodel_id2}}]
If not, what changes should I make?

Thanks

Bulk patch fails with unique_together models

I ran into an issue with a model I was trying to bulk patch that also has a unique_together constraint. Traceback is below. The issue here is that the UniqueTogetherValidator expects to be called from a serializer with a single related instance (i.e. a normal update serializer). But the serializer is in fact instantiated with a queryset.

TBH I don't entirely understand why serializer.instance is being set to a queryset. But anyway.

Traceback (most recent call last):
  File ".../django/core/handlers/base.py", line 149, in get_response
    response = self.process_exception_by_middleware(e, request)
  File ".../django/core/handlers/base.py", line 147, in get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File ".../django/views/decorators/csrf.py", line 58, in wrapped_view
    return view_func(*args, **kwargs)
  File ".../rest_framework/viewsets.py", line 87, in view
    return self.dispatch(request, *args, **kwargs)
  File ".../rest_framework/views.py", line 466, in dispatch
    response = self.handle_exception(exc)
  File ".../rest_framework/views.py", line 463, in dispatch
    response = handler(request, *args, **kwargs)
  File ".../rest_framework_bulk/drf3/mixins.py", line 79, in partial_bulk_update
    return self.bulk_update(request, *args, **kwargs)
  File ".../rest_framework_bulk/drf3/mixins.py", line 73, in bulk_update
    serializer.is_valid(raise_exception=True)
  File ".../rest_framework/serializers.py", line 213, in is_valid
    self._validated_data = self.run_validation(self.initial_data)
  File ".../rest_framework/serializers.py", line 557, in run_validation
    value = self.to_internal_value(data)
  File ".../rest_framework/serializers.py", line 593, in to_internal_value
    validated = self.child.run_validation(item)
  File ".../rest_framework/serializers.py", line 409, in run_validation
    self.run_validators(value)
  File ".../rest_framework/fields.py", line 498, in run_validators
    validator(value)
  File ".../rest_framework/validators.py", line 142, in __call__
    queryset = self.exclude_current_instance(attrs, queryset)
  File ".../rest_framework/validators.py", line 135, in exclude_current_instance
    return queryset.exclude(pk=self.instance.pk)
AttributeError: 'QuerySet' object has no attribute 'pk'

My workaround was to take advantage of the 'id' attribute that the bulk serializer keeps track of and write a custom unique_together validator.

# validators.py
from rest_framework.validators import UniqueTogetherValidator


class BulkUniqueTogetherValidator(UniqueTogetherValidator):
    def exclude_current_instance(self, attrs, queryset):
        if attrs.get('id'):
            return queryset.exclude(pk=attrs['id'])
        return queryset
# serializers.py
from .validators import BulkUniqueTogetherValidator


class ThingSerializer(serializers.HyperlinkedModelSerializer):
    <...properties>

    def get_unique_together_validators(self):
        return [BulkUniqueTogetherValidator(
            queryset=Thing.objects.all(),
            fields=('field1', 'field2'),
        )]

Exception "'BulkListSerializerObject' is not iterable" when using API browser

Posting to a BulkModelViewSet using the DRF api browser causes the following exception to be thrown. NB: the data has already been written to the db successfully before the exception happens:

TypeError at /usage_records/
'BulkListSerializer' object is not iterable
Request Method: POST
Request URL:    http://127.0.0.1:8000/usage_records/
Django Version: 1.7.4
Exception Type: TypeError
Exception Value:    
'BulkListSerializer' object is not iterable
Exception Location: /Users/richardc/virtualenvs/rating_sytstem/lib/python2.7/site-packages/django/template/defaulttags.py in render, line 161
Python Executable:  /Users/richardc/virtualenvs/rating_sytstem/bin/python
Python Version: 2.7.8
Python Path:    
['/Volumes/Data/git/rating-system/rating_system_site',
 '/Users/richardc/virtualenvs/rating_sytstem/lib/python2.7/site-packages/setuptools-0.6c11-py2.7.egg',
 '/Users/richardc/virtualenvs/rating_sytstem/lib/python2.7/site-packages/pip-1.0.2-py2.7.egg',
 '/Users/richardc/virtualenvs/rating_sytstem/lib/python27.zip',
 '/Users/richardc/virtualenvs/rating_sytstem/lib/python2.7',
 '/Users/richardc/virtualenvs/rating_sytstem/lib/python2.7/plat-darwin',
 '/Users/richardc/virtualenvs/rating_sytstem/lib/python2.7/plat-mac',
 '/Users/richardc/virtualenvs/rating_sytstem/lib/python2.7/plat-mac/lib-scriptpackages',
 '/Users/richardc/virtualenvs/rating_sytstem/lib/python2.7/lib-tk',
 '/Users/richardc/virtualenvs/rating_sytstem/lib/python2.7/lib-old',
 '/Users/richardc/virtualenvs/rating_sytstem/lib/python2.7/lib-dynload',
 '/usr/local/Cellar/python/2.7.8_2/Frameworks/Python.framework/Versions/2.7/lib/python2.7',
 '/usr/local/Cellar/python/2.7.8_2/Frameworks/Python.framework/Versions/2.7/lib/python2.7/plat-darwin',
 '/usr/local/Cellar/python/2.7.8_2/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-tk',
 '/usr/local/Cellar/python/2.7.8_2/Frameworks/Python.framework/Versions/2.7/lib/python2.7/plat-mac',
 '/usr/local/Cellar/python/2.7.8_2/Frameworks/Python.framework/Versions/2.7/lib/python2.7/plat-mac/lib-scriptpackages',
 '/Users/richardc/virtualenvs/rating_sytstem/lib/python2.7/site-packages']
Server time:    Thu, 12 Mar 2015 13:33:11 +1100

The line of code that throws the exception is in the django template library, and it is doing:

values=list(values)

The values object is my serializer object:

Usage_Record_Serializer(context={u'view': <rating_system.views.rest.Usage_Record_ViewSet object>, u'request': <rest_framework.request.Request object>, u'format': None}, data=[{u'event_description': u'Test Event', u'event_value': 122, u'tags': u'PURCHAGES, VISA', u'reference_id': 20, u'external_service': 1, u'external_system': 2, u'external_service_key': u'Etx', u'event_date_time': u'2015-03-02T00:10:13.869285Z'}, {u'event_description': u'Test Event', u'event_value': 122, u'tags': u'PURCHAGES, VISA', u'reference_id': 21, u'external_service': 1, u'external_system': 2, u'external_service_key': u'Etx', u'event_date_time': u'2015-03-02T00:10:13.869285Z'}], many=True)

If I just browse to http://127.0.0.1:8000/usage_records/ again, the template renders correctly, with these new records included.

The basic code is:

class Usage_Record_Serializer(BulkSerializerMixin, serializers.ModelSerializer):
    class Meta(object):
        model = Usage_Record
        list_serializer_class = BulkListSerializer
        read_only_fields = ('id', 'upload_date_time',)

class Usage_Record_ViewSet(BulkModelViewSet):
    queryset = Usage_Record.objects.all()
    serializer_class = Usage_Record_Serializer
    permission_classes = (permissions.IsAuthenticated,)

bulk_router.register(r'usage_records', rest.Usage_Record_ViewSet)

If I use postman or curl, no error is thrown ... so this does seem to be some bad interaction with the DRF api browser.

Any thoughts on why this happens?

Thanks,
Richard

PUT/PATCH support is broken

I've tried creating a very simple API endpoint following the README:

from django.db import models

from rest_framework import serializers
from rest_framework_bulk import BulkListSerializer, BulkSerializerMixin, ListBulkCreateUpdateAPIView


class Foo(models.Model):
    name = models.CharField(max_length=20)
    class Meta:
        app_label = "foobar"


class FooSerializer(BulkSerializerMixin, serializers.ModelSerializer):
    class Meta(object):
        model = Foo
        list_serializer_class = BulkListSerializer

class FooView(ListBulkCreateUpdateAPIView):
    queryset = Foo.objects.all()
    serializer_class = FooSerializer

I've configured a URL for FooView and am able to load the built in rest_framework html page for this API endpoint. I've also created a few instances of the Foo model for testing.

However, when I attempt to make an HTTP PUT/PATCH request to this endpoint to update one of the existing instances, I get a successful return code but no update actually occurs.

curl 'http://127.0.0.1:8000/foo/' -X PUT -H 'Cookie: csrftoken=M8jkbJtUKwjKB7zya8JYvxqJVxkRaPgG' -H 'X-CSRFToken: M8jkbJtUKwjKB7zya8JYvxqJVxkRaPgG' --data 'id=1&name=D' --compressed

The response is simply an empty list [] and the instance with id=1 retains its old name.

I've created a sample project to reproduce this, and would really appreciate any help. I am unable to determine if this is a bug in DRF, DRF-bulk, or a misunderstanding on my part.

[Update]
I tried testing a POST request, like so, and it did create a new object:

curl 'http://127.0.0.1:8000/foo/' -X POST -H 'Cookie: csrftoken=M8jkbJtUKwjKB7zya8JYvxqJVxkRaPgG' -H 'X-CSRFToken: M8jkbJtUKwjKB7zya8JYvxqJVxkRaPgG' --data 'name=D' --compressed

The response for this request is: {"id":4,"name":"D"}

However, trying to create a new object with PUT does not work:

curl 'http://127.0.0.1:8000/foo/' -X PUT -H 'Cookie: csrftoken=M8jkbJtUKwjKB7zya8JYvxqJVxkRaPgG' -H 'X-CSRFToken: M8jkbJtUKwjKB7zya8JYvxqJVxkRaPgG' --data 'name=E'

The response for this is: []

I believe that PUT/PATCH support for django_rest_framework-bulk is broken at this point. Python package info is:

Django==1.8
djangorestframework==3.4.4
djangorestframework-bulk==0.2.1

Slow PUTs

Bulk PUT requests to my services are taking between 30 and 40 seconds to complete successfully. Bulk POST requests complete successfully in under 1 second. I've tried submitting various numbers of objects, between 1 and 20, using both methods and the response times don't seem to vary much.

Why do you think the PUTs are taking so long?

I'm using Python 3.4, Django 1.7, DRF 2.4.4, DRF-bulk 0.1.3, with PostgreSQL 9.3 and the default Django web development server on the backend.

Key error 'id' when doing PUT request

[DESCRIPTION]
When making a PUT request to a BulkModelViewSet, using a simple DRF ModelSerializer with all fields automatically generated - no field specified by hand -and with list_serializer_class attribute being BulkListSerializer the application crashes at this line and raises a KeyError for 'id' key.

[REASON]
When DRF ListSerializer passes validated_data to the update() method the given validated_data don't contain the id of the object.

[FIX]
DRF don't return id in validated_data anymore unless the field is explicitly declared in the serializer (id = serializers.IntegerField()) so you have to do that in order for it to work. I don't believe we should add it to DRF-bulk BulkListSerializer though, since pk might not be the id field, what do you think? Raising an exception if the 'id' key is not in validated_data might be a better solution.

[ENVIRONMENT]
- Debian GNU/Linux 8 (8.2)
- Django Version: 1.5.6
- Django-rest-framework Version: 3.2.4
- Python Version: 2.7.8

[TRACEBACK]

File "/home/adelaby/.virtualenvs/contexte/local/lib/python2.7/site-packages/django/core/handlers/base.py" in get_response
  115.                         response = callback(request, *callback_args, **callback_kwargs)
File "/home/adelaby/.virtualenvs/contexte/local/lib/python2.7/site-packages/django/views/decorators/csrf.py" in wrapped_view
  77.         return view_func(*args, **kwargs)
File "/home/adelaby/.virtualenvs/contexte/local/lib/python2.7/site-packages/rest_framework/viewsets.py" in view
  87.             return self.dispatch(request, *args, **kwargs)
File "/home/adelaby/.virtualenvs/contexte/local/lib/python2.7/site-packages/rest_framework/views.py" in dispatch
  466.             response = self.handle_exception(exc)
File "/home/adelaby/.virtualenvs/contexte/local/lib/python2.7/site-packages/rest_framework/views.py" in dispatch
  463.             response = handler(request, *args, **kwargs)
File "/home/adelaby/Development/contexte-site/contexte/libs/utils/views/mixins.py" in bulk_update
  42.         self.perform_bulk_update(serializer)
File "/home/adelaby/Development/contexte-site/contexte/apps/users/views/rest.py" in perform_bulk_update
  131.         super(UsersViewSet, self).perform_bulk_update(serializer)
File "/home/adelaby/.virtualenvs/contexte/src/djangorestframework-bulk/rest_framework_bulk/drf3/mixins.py" in perform_bulk_update
  85.         return self.perform_update(serializer)
File "/home/adelaby/.virtualenvs/contexte/src/djangorestframework-bulk/rest_framework_bulk/drf3/mixins.py" in perform_update
  82.         serializer.save()
File "/home/adelaby/.virtualenvs/contexte/local/lib/python2.7/site-packages/rest_framework/serializers.py" in save
  643.             self.instance = self.update(self.instance, validated_data)
File "/home/adelaby/.virtualenvs/contexte/src/djangorestframework-bulk/rest_framework_bulk/drf3/serializers.py" in update
  43.             for i in all_validated_data
File "/home/adelaby/.virtualenvs/contexte/src/djangorestframework-bulk/rest_framework_bulk/drf3/serializers.py" in <dictcomp>
  43.             for i in all_validated_data

README.md Wrong pip command ?

To install it from git, you set the command line to
$ pip install -r git+http://github.com/miki725/django-rest-framework-bulk#egg=djangorestframework-bulk
Shouldn't it be
$ pip install [-e] git+http://github.com/miki725/django-rest-framework-bulk#egg=djangorestframework-bulk>

I think -r stands for a requirements.txt file, am I right ?

Support for DRF 3.0

Hello,
DRF has introduced some significant changes in some of the logic regarding the Serializer.
For example, there is no more pre_save, but perform_create and perform_update instead that this package relies upon.
Another thing is request.DATA that is being deprecated and serializer.object that became serializer.validated_data.

Is there a workkaround for the time being so that we could still use DRF 3.0 with DRF-bulk?

Thanks!

UUIDs are not handled

BulkSerializerMixin uses get_value() to get the id of an object, but doesn't use to_internal_value() to convert the id to the proper internal value. This works fine for integer ids, but it breaks for UUIDs because the all_validated_data_by_id in BulkListSerializer.update constructs a dict by string id and then tries to get values for instance ids (which are UUIDs and thus not in the dictionary.)

testing with api client

I'm trying to wrote test for my application ( I have bulk creation on one api ) but iI'm blocked because the rest_framework api client does not allow me to execute post with a list as data
error:

Traceback (most recent call last):
  File "/Users/andrea/Documents/workspace/MakrShakrPortbl/ingredient/tests.py", line 79, in test_buld_ingridient_operation
    creation_response = self.client.post(reverse(self.ingredienttListViewName),self.ingredient_data_list)
  File "/Users/andrea/Documents/workspace/MakrShakrPortbl/venv/lib/python2.7/site-packages/rest_framework/test.py", line 168, in post
    path, data=data, format=format, content_type=content_type, **extra)
  File "/Users/andrea/Documents/workspace/MakrShakrPortbl/venv/lib/python2.7/site-packages/rest_framework/test.py", line 89, in post
    data, content_type = self._encode_data(data, format, content_type)
  File "/Users/andrea/Documents/workspace/MakrShakrPortbl/venv/lib/python2.7/site-packages/rest_framework/test.py", line 64, in _encode_data
    ret = renderer.render(data)
  File "/Users/andrea/Documents/workspace/MakrShakrPortbl/venv/lib/python2.7/site-packages/rest_framework/renderers.py", line 678, in render
    return encode_multipart(self.BOUNDARY, data)
  File "/Users/andrea/Documents/workspace/MakrShakrPortbl/venv/lib/python2.7/site-packages/django/test/client.py", line 168, in encode_multipart
    for (key, value) in data.items():
AttributeError: 'list' object has no attribute 'items' 

I'm working on osx yosemite 10.10.3
pip freeze with virtualenv actived:

Django==1.8.2
-e https://github.com/umutbozkurt/django-rest-framework-mongoengine#egg=django_rest_framework_mongoengine-master
djangorestframework==3.1.3
djangorestframework-bulk==0.2.1
mongoengine==0.9.0
pymongo==2.8.1
wsgiref==0.1.2

no problem on creation,update reported

Optional non-bulk updates

Is there a reason that the BulkUpdateModelMixin does not have the same "check for bulk" switch that the BulkCreate does?

If not, would you mind a pull request to add it in?

permission_classes in ListBulkCreateUpdateAPIView

I have a permisson class like this

from rest_framework import status, permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
    """
    Custom permission to only allow owners of an object to edit it.
    """

    def has_object_permission(self, request, view, obj):
        # Read permissions are allowed to any request,
        # so we'll always allow GET, HEAD or OPTIONS requests.
        if request.method in permissions.SAFE_METHODS:
            return True

        # Write permissions are only allowed to the owner of the snippet.
        return obj.user == request.user

and my ViewSet:

class CompanyView(ListBulkCreateUpdateAPIView):
     serializer_class = company_serializer.CompanySerializer
     permission_classes = (IsOwnerOrReadOnly,)

But When I make a POST/PUT request to insert or update many records, My ViewSet does not use Permission class to check permissions.
How to add permission classes to Bulk ViewSet ?

Project status

This project seems to be quite heavily used, but has not received any updates since 2015. @miki725 is it dead?

Error importing mixins using `from rest_framework_bulk import mixins as bulk_mixins`

Background
I needed to write my own view, using the provided bulk mixins.

Problem
I first tried importing using from rest_framework_bulk import mixins as bulk_mixins. Then, got the following error AttributeError: 'module' object has no attribute 'BulkCreateModelMixin' when trying to use bulk_mixins.BulkCreateModelMixin.

In the Django shell:

>>> from rest_framework_bulk import mixins as bulk_mixins
>>> dir(bulk_mixins)
['CreateModelMixin', 'DestroyModelMixin', 'Http404', 'ListModelMixin', 'Response', 'RetrieveModelMixin', 'UpdateModelMixin', 'ValidationError', '__builtins__', '__doc__', '__file__', '__name__', '__package__', '_get_validation_exclusions', 'api_settings', 'clone_request', 'status', 'unicode_literals', 'warnings']

Whereas:

>>> from rest_framework_bulk.generics import bulk_mixins
>>> dir(bulk_mixins)
['BulkCreateModelMixin', 'BulkDestroyModelMixin', 'BulkUpdateModelMixin', 'CreateModelMixin', 'Response', 'ValidationError', '__builtins__', '__doc__', '__file__', '__name__', '__package__', 'print_function', 'status', 'unicode_literals']

Solution

from rest_framework_bulk.generics import bulk_mixins

Having fixed the above problem, I stumped with the following:

  1. Make a POST request passing an array of objects.
  2. It saves correctly, but the response throws this error: 'RelationsList' object has no attribute 'priority'.
  3. After that, make a GET request and the previously updated data renders correctly.

When a non-bulk POST request (with just one object, not a list of objects) is made, it saves successfully and the response throws no error.

In my view I have:

      def get_serializer_class(self):
          if self.request.method == 'GET':
              return NestedSelectedPlaceSerializer
          return SelectedPlaceSerializer

My serializer classes are:

class SelectedPlaceSerializer(serializers.ModelSerializer):
    class Meta:
        model =  SelectedPlace
        fields = ('user', 'place', 'priority')

class NestedSelectedPlaceSerializer(serializers.ModelSerializer):
    place = PlaceSerializer()

    class Meta:
        model = SelectedPlace
        fields = ('place', 'priority')

Traceback

Environment:


Request Method: POST
Request URL: https://127.0.0.1:8443/api/places/selected

Traceback:
File "/home/user/venv/project1/local/lib/python2.7/site-packages/django/core/handlers/base.py" in get_response
  139.                 response = response.render()
File "/home/user/venv/project1/local/lib/python2.7/site-packages/django/template/response.py" in render
  105.             self.content = self.rendered_content
File "/home/user/venv/project1/local/lib/python2.7/site-packages/rest_framework/response.py" in rendered_content
  59.         ret = renderer.render(self.data, media_type, context)
File "/home/user/venv/project1/local/lib/python2.7/site-packages/rest_framework/renderers.py" in render
  592.         context = self.get_context(data, accepted_media_type, renderer_context)
File "/home/user/venv/project1/local/lib/python2.7/site-packages/rest_framework/renderers.py" in get_context
  542.         raw_data_post_form = self.get_raw_data_form(view, 'POST', request)
File "/home/user/venv/project1/local/lib/python2.7/site-packages/rest_framework/renderers.py" in get_raw_data_form
  492.                 content = renderer.render(serializer.data, accepted, context)
File "/home/user/venv/project1/local/lib/python2.7/site-packages/rest_framework/serializers.py" in data
  573.                 self._data = self.to_native(obj)
File "/home/user/venv/project1/local/lib/python2.7/site-packages/rest_framework/serializers.py" in to_native
  349.             value = field.field_to_native(obj, field_name)
File "/home/user/venv/project1/local/lib/python2.7/site-packages/rest_framework/fields.py" in field_to_native
  334.         return super(WritableField, self).field_to_native(obj, field_name)
File "/home/user/venv/project1/local/lib/python2.7/site-packages/rest_framework/fields.py" in field_to_native
  198.             value = get_component(value, component)
File "/home/user/venv/project1/local/lib/python2.7/site-packages/rest_framework/fields.py" in get_component
  56.         val = getattr(obj, attr_name)

Exception Type: AttributeError at /api/places/selected/
Exception Value: 'RelationsList' object has no attribute 'priority'

server error if update_lookup_field is missing from the provided data

a 400 bad request would be much better.

I think it's because id_field.get_value(data) here https://github.com/miki725/django-rest-framework-bulk/blob/master/rest_framework_bulk/drf3/serializers.py#L26 returns an rest_framework.fields.empty object.

versions:
djangorestframework==3.1.1
djangorestframework-bulk==0.2

Traceback (most recent call last):
  File "/home/flc/.virtualenvs/drfbulktest/local/lib/python2.7/site-packages/django/core/handlers/base.py", line 132, in get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/home/flc/.virtualenvs/drfbulktest/local/lib/python2.7/site-packages/django/views/decorators/csrf.py", line 58, in wrapped_view
    return view_func(*args, **kwargs)
  File "/home/flc/.virtualenvs/drfbulktest/local/lib/python2.7/site-packages/rest_framework/viewsets.py", line 85, in view
    return self.dispatch(request, *args, **kwargs)
  File "/home/flc/.virtualenvs/drfbulktest/local/lib/python2.7/site-packages/rest_framework/views.py", line 452, in dispatch
    response = self.handle_exception(exc)
  File "/home/flc/.virtualenvs/drfbulktest/local/lib/python2.7/site-packages/rest_framework/views.py", line 449, in dispatch
    response = handler(request, *args, **kwargs)
  File "/home/flc/.virtualenvs/drfbulktest/local/lib/python2.7/site-packages/rest_framework_bulk/drf3/mixins.py", line 79, in partial_bulk_update
    return self.bulk_update(request, *args, **kwargs)
  File "/home/flc/.virtualenvs/drfbulktest/local/lib/python2.7/site-packages/rest_framework_bulk/drf3/mixins.py", line 74, in bulk_update
    self.perform_bulk_update(serializer)
  File "/home/flc/.virtualenvs/drfbulktest/local/lib/python2.7/site-packages/rest_framework_bulk/drf3/mixins.py", line 85, in perform_bulk_update
    return self.perform_update(serializer)
  File "/home/flc/.virtualenvs/drfbulktest/local/lib/python2.7/site-packages/rest_framework/mixins.py", line 73, in perform_update
    serializer.save()
  File "/home/flc/.virtualenvs/drfbulktest/local/lib/python2.7/site-packages/rest_framework/serializers.py", line 598, in save
    self.instance = self.update(self.instance, validated_data)
  File "/home/flc/.virtualenvs/drfbulktest/local/lib/python2.7/site-packages/rest_framework_bulk/drf3/serializers.py", line 51, in update
    '{}__in'.format(id_attr): all_validated_data_by_id.keys(),
  File "/home/flc/.virtualenvs/drfbulktest/local/lib/python2.7/site-packages/django/db/models/query.py", line 679, in filter
    return self._filter_or_exclude(False, *args, **kwargs)
  File "/home/flc/.virtualenvs/drfbulktest/local/lib/python2.7/site-packages/django/db/models/query.py", line 697, in _filter_or_exclude
    clone.query.add_q(Q(*args, **kwargs))
  File "/home/flc/.virtualenvs/drfbulktest/local/lib/python2.7/site-packages/django/db/models/sql/query.py", line 1301, in add_q
    clause, require_inner = self._add_q(where_part, self.used_aliases)
  File "/home/flc/.virtualenvs/drfbulktest/local/lib/python2.7/site-packages/django/db/models/sql/query.py", line 1328, in _add_q
    current_negated=current_negated, connector=connector, allow_joins=allow_joins)
  File "/home/flc/.virtualenvs/drfbulktest/local/lib/python2.7/site-packages/django/db/models/sql/query.py", line 1200, in build_filter
    condition = self.build_lookup(lookups, col, value)
  File "/home/flc/.virtualenvs/drfbulktest/local/lib/python2.7/site-packages/django/db/models/sql/query.py", line 1096, in build_lookup
    return final_lookup(lhs, rhs)
  File "/home/flc/.virtualenvs/drfbulktest/local/lib/python2.7/site-packages/django/db/models/lookups.py", line 96, in __init__
    self.rhs = self.get_prep_lookup()
  File "/home/flc/.virtualenvs/drfbulktest/local/lib/python2.7/site-packages/django/db/models/lookups.py", line 134, in get_prep_lookup
    return self.lhs.output_field.get_prep_lookup(self.lookup_name, self.rhs)
  File "/home/flc/.virtualenvs/drfbulktest/local/lib/python2.7/site-packages/django/db/models/fields/__init__.py", line 729, in get_prep_lookup
    return [self.get_prep_value(v) for v in value]
  File "/home/flc/.virtualenvs/drfbulktest/local/lib/python2.7/site-packages/django/db/models/fields/__init__.py", line 985, in get_prep_value
    return int(value)
TypeError: int() argument must be a string or a number, not 'classobj'```

try/except/pass in __init__.py

Hi

Is this necessary?

I recently ran into a circular import issue where drf-bulk imports drf which in turn imports some of our code(via DEFAULT_PAGINATION_CLASS in DRF) and it hid the actual error which occurred deep inside our code.

README bug

The README explains how to use the router as follows:

from rest_framework_bulk.routes import BulkRouter

class UserViewSet(BulkModelViewSet):
    model = User

    def allow_bulk_destroy(self, qs, filtered):
        """Don't forget to fine-grain this method"""

router = BulkRouter()
router.register(r'users', UserViewSet)

However, BulkModelViewSet is not imported, neither is it defined in the example.

Similarly, the example registers UserViewSet, while the example above defines FooView - IMO it would be helpful if the example stayed in context.

Use bulk_create for bulk creation

DRF creates objects individually - which ends up being pretty slow if you post, say, 200 items to a "bulk" endpoint. Still better than the alternative, but still not great. It would be nice to use bulk_create instead.

TypeError: as_view() takes 1 positional argument but 2 were given

Unhandled exception in thread started by <function check_errors.<locals>.wrapper at 0x102470840>
Traceback (most recent call last):
  File "/Users/n/Sites/VEUNDMINT_TUB_Brueckenkurs/venv/lib/python3.5/site-packages/django/utils/autoreload.py", line 226, in wrapper
    fn(*args, **kwargs)
  File "/Users/n/Sites/VEUNDMINT_TUB_Brueckenkurs/venv/lib/python3.5/site-packages/django/core/management/commands/runserver.py", line 121, in inner_run
    self.check(display_num_errors=True)
  File "/Users/n/Sites/VEUNDMINT_TUB_Brueckenkurs/venv/lib/python3.5/site-packages/django/core/management/base.py", line 385, in check
    include_deployment_checks=include_deployment_checks,
  File "/Users/n/Sites/VEUNDMINT_TUB_Brueckenkurs/venv/lib/python3.5/site-packages/django/core/management/base.py", line 372, in _run_checks
    return checks.run_checks(**kwargs)
  File "/Users/n/Sites/VEUNDMINT_TUB_Brueckenkurs/venv/lib/python3.5/site-packages/django/core/checks/registry.py", line 81, in run_checks
    new_errors = check(app_configs=app_configs)
  File "/Users/n/Sites/VEUNDMINT_TUB_Brueckenkurs/venv/lib/python3.5/site-packages/django/core/checks/urls.py", line 14, in check_url_config
    return check_resolver(resolver)
  File "/Users/n/Sites/VEUNDMINT_TUB_Brueckenkurs/venv/lib/python3.5/site-packages/django/core/checks/urls.py", line 24, in check_resolver
    for pattern in resolver.url_patterns:
  File "/Users/n/Sites/VEUNDMINT_TUB_Brueckenkurs/venv/lib/python3.5/site-packages/django/utils/functional.py", line 35, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
  File "/Users/n/Sites/VEUNDMINT_TUB_Brueckenkurs/venv/lib/python3.5/site-packages/django/urls/resolvers.py", line 310, in url_patterns
    patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
  File "/Users/n/Sites/VEUNDMINT_TUB_Brueckenkurs/venv/lib/python3.5/site-packages/django/utils/functional.py", line 35, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
  File "/Users/n/Sites/VEUNDMINT_TUB_Brueckenkurs/venv/lib/python3.5/site-packages/django/urls/resolvers.py", line 303, in urlconf_module
    return import_module(self.urlconf_name)
  File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 986, in _gcd_import
  File "<frozen importlib._bootstrap>", line 969, in _find_and_load
  File "<frozen importlib._bootstrap>", line 958, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 673, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 665, in exec_module
  File "<frozen importlib._bootstrap>", line 222, in _call_with_frames_removed
  File "/Users/n/Sites/VEUNDMINT_TUB_Brueckenkurs/src/server/veundmint/veundmint/urls.py", line 33, in <module>
    url(r'^', include(router.urls)),
  File "/Users/n/Sites/VEUNDMINT_TUB_Brueckenkurs/venv/lib/python3.5/site-packages/rest_framework/routers.py", line 81, in urls
    self._urls = self.get_urls()
  File "/Users/n/Sites/VEUNDMINT_TUB_Brueckenkurs/venv/lib/python3.5/site-packages/rest_framework/routers.py", line 355, in get_urls
    urls = super(DefaultRouter, self).get_urls()
  File "/Users/n/Sites/VEUNDMINT_TUB_Brueckenkurs/venv/lib/python3.5/site-packages/rest_framework/routers.py", line 261, in get_urls
    view = viewset.as_view(mapping, **route.initkwargs)
TypeError: as_view() takes 1 positional argument but 2 were given

My code is exactly like in the example in the README

# Routers provide an easy way of automatically determining the URL conf.
router = BulkRouter()
router.register(r'server-action', WebsiteActionViewSet)
router.register(r'score', ScoreViewSet, base_name='scores')
router.register(r'whoami', UserViewSet, base_name='whoami')
router.register(r'foo', FooViewSet)

urlpatterns = [
    #url(r'^', include('veundmint_base.urls')),
    url(r'^', include(router.urls)),
    url(r'^admin/', admin.site.urls),
    url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
    url(r'^api-token-auth/', obtain_jwt_token),
]

when I do

router.register(r'foo', FooViewSet.as_view(), base_name='foo')

The API endpoint foo/ is not available

It would be great to have a simple_app that also works in django 1.8 or restframework > 3

Wheel support

http://pythonwheels.com/

Right now, only the tar.gz file is being distributed on PyPI for djangorestframework-bulk, which isn't that much of an issue, but it does add some time to installing the package.

This package should be compatible with the Wheel format, considering it doesn't appear to have any C dependencies and it is compatible with both Python 2 and 3. As a result, you should only need to generate a universal wheel and then everyone (on all systems) will get the ability to install djangorestframework-bulk with just the wheel, without having to do any extra work.

is it possible to override put?

Im trying to bulk upload data using PUT, but am getting keyError ID, this is expected as I do not have the ID in the JSON data.

however there is another unique field in the JSON Data and I could use that to upload, is it possible to do this?

Model:

ID - unique auto increment
PDID - another unique value
NAME
Mobile

JSON
[{"Name": "Billy Bob", "Mobile": "015525 58165", "PDID": "PA8AMY1"}, {"Name": "John Smith", "Mobile": "159815588, "PDID": "PQ0FT0P"}]

Thanks

Default allow_bulk_destroy implementation does the opposite of its documentation

class FooView(BulkDestroyAPIView):
def allow_bulk_destroy(self, qs, filtered):
# custom logic here

    # default checks if the qs was filtered
    # qs comes from self.get_queryset()
    # filtered comes from self.filter_queryset(qs)
    return qs is not filtered

By default it checks if the queryset was filtered and if not will not allow the bulk delete to complete.

I understood this to mean bulk_destroy will only apply to filtered querysets. However, the default implementation returns true when the queryset is not filtered and false when it is.

Then BulkDestroyModelMixin logic only allows unfiltered querysets to be destroyed.

Is this correct and intentional?

Bug when using bulk create

I get this error:

AttributeError at /api/activities/

'RelationsList' object has no attribute 'id'

When doing a bulk create:

[{"activity_type":"text","datetime":"2014-02-26T18:06:00","device_id":10950},{"activity_type":"text","datetime":"2014-02-26T18:05:13","device_id":10948}]

Using this setup:

class ActivitySerializer(serializers.ModelSerializer):
    class Meta:
        exclude = ('user', )
        model = Activity

class UserViewSetMixin(object):
    def get_queryset(self):
        user = self.request.user
        qs = super(UserViewSetMixin, self).get_queryset()
        return qs.filter(user=user)

    def pre_save(self, obj):
        obj.user = self.request.user

class ActivityViewSet(UserViewSetMixin, ListBulkCreateUpdateDestroyAPIView):
    model = Activity
    permission_classes = (permissions.IsAuthenticated, IsUser, )
    serializer_class = ActivitySerializer

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.