0

I have the following code

serializer.py

class ContactSerializer(serializers.ModelSerializer):

    def __init__(self, *args, **kwargs):
        many = kwargs.pop('many', True)
        many = True
        super(ContactSerializer, self).__init__(many=many, *args, **kwargs)

    class Meta:
        model = Contact

    def validate(self, attrs):
        # Check that the user in contact isn't the same as the parent user
        if 'user' in attrs.keys() and 'parent_user' in attrs.keys() and attrs['user']:
            if attrs['user'].pk == attrs['parent_user'].pk:
                raise serializers.ValidationError("You can't add yourself as a contact")
        return attrs

views.py

class ContactViewSet(viewsets.ModelViewSet):

    queryset = Contact.objects.all()
    serializer_class = ContactSerializer
    authentication_classes = (TokenAuthentication,)
    permission_classes = (permissions.IsAuthenticated, )

    def get_queryset(self):
        return Contact.objects.filter(parent_user=self.request.user.pk)

    def create(self, request):
        if isinstance(request.DATA, list):
            for i, contact_data in enumerate(request.DATA):
                # Get the user id from the request
                request.DATA[i]['parent_user'] = self.request.user.pk

                # Check if the contact is aleardy a user
                if 'email' in contact_data:
                    try:
                        request.DATA[i]['user'] = User.objects.get(email=contact_data['email']).pk
                    except User.DoesNotExist, e:
                        pass

        return super(ContactViewSet, self).create(request)

Now the problem with this is that when I send a list of contacts in POST None of the objects are created, it just sends the error for the invalid objects. For example: POST

[
  {
    "first_name": "Eyad",
    "last_name": "tttttt",
    "email": "[email protected]"
  },
  {
    "first_name": "Eyad",
    "last_name": "mmmmmm",
    "email": "[email protected]"
  }
]

The return

[
    {},
    {
        "non_field_errors": [
            "You can't add yourself as a contact"
        ]
    }
]

How can I make this return something like this:

[
    {
        "created": true
    },
    {
        "non_field_errors": [
            "You can't add yourself as a contact"
        ]
    }
]

This way when the API is called, valid objects are created and the API caller won't have to send those objects again.

2 Answers 2

2

I created a subclass of the CreateModelMixin to change the create method.

class BulkCreateModelMixin(mixins.CreateModelMixin):
    """
    Create valid objects and return errors for invalid ones.
    """

    def create(self, request, *args, **kwargs):
        # The initial serializer
        serializer = self.get_serializer(data=request.DATA)
        return_list = []

        for item in zip(serializer.errors, serializer.init_data):
            # If item doesn't have errors
            if not item[0]:

                # Create a an individual serializer for the valid object and save it
                object_serializer = self.get_serializer(data=[item[1]])
                if object_serializer.is_valid():
                    self.pre_save(object_serializer.object)
                    self.object = object_serializer.save(force_insert=True)
                    self.post_save(self.object, created=True)

                    return_list.append(object_serializer.data[0])
            else:
                return_list.append(item[0])

        # Status code
        if serializer.errors:
            return_status = status.HTTP_206_PARTIAL_CONTENT
        else:
            return_status = status.HTTP_201_CREATED

        return Response(return_list, status=return_status)

Then add the new mixin to the viewset

class ContactViewSet(viewsets.ModelViewSet, BulkCreateModelMixin):
    ..........
    ..........
Sign up to request clarification or add additional context in comments.

1 Comment

Does anyone know of a better solution to this problem, now in 2019?
0

I solve this problem with another way. Create 2 mixins, one for view (override create method, another to ListSerializer (override to_internal_value method).

So i have:

class CreateListMixin:
    """Allows bulk creation of a resource."""

    def get_serializer(self, *args, **kwargs):
        if isinstance(kwargs.get('data', {}), list):
            kwargs['many'] = True

        return super().get_serializer(*args, **kwargs)

    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return Response({"result": serializer.data, "errors": serializer.errors_list},
                        status=status.HTTP_202_ACCEPTED, headers=headers)


class ErrorsListSerializerMixin:
    error_list = []

    def to_internal_value(self, data):
        """
        List of dicts of native values <- List of dicts of primitive datatypes.
        """
        if not isinstance(data, list):
            message = self.error_messages['not_a_list'].format(
                input_type=type(data).__name__
            )
            raise ValidationError({
                api_settings.NON_FIELD_ERRORS_KEY: [message]
            }, code='not_a_list')
        if not self.allow_empty and len(data) == 0:
            message = self.error_messages['empty']
            raise ValidationError({
                api_settings.NON_FIELD_ERRORS_KEY: [message]
            }, code='empty')

        ret = []
        errors = []

        for item in data:
            try:
                validated = self.child.run_validation(item)
            except ValidationError as exc:
                errors.append({"error": exc.detail, "data": item})
            else:
                ret.append(validated)

        if any(errors):
            self.errors_list = errors
            # raise ValidationError(errors)
        return ret

views.py

class SKUMatchesView(CreateListMixin, viewsets.ModelViewSet):
......................
......................

serializers.py

class SKUMatchesListSerializer(ErrorsListSerializerMixin, serializers.ListSerializer):
.....................
.....................

So, when i post for example this data:

[{          "sku_customer_uid": "13864-20000000428265",
            "company": 1,
            "code": "123456784654654",
            "marketplace": "ozon"
},
{          "sku_customer_uid": "13864-2000000042824",
            "company": 1,
            "code": "123456789*8",
            "marketplace": "ozon"
}
]

i receive same reply:

{
  "result": [
    {
      "id": 8821,
      "sku_customer_uid": null,
      "company": 1,
      "code": "123456784654654",
      "marketplace": "ozon"
    }
  ],
  "errors": [
    {
      "error": {
        "non_field_errors": [
          "Wrong code"
        ]
      },
      "data": {
        "sku_customer_uid": "13864-2000000042824",
        "company": 1,
        "code": "123456789*8",
        "marketplace": "ozon"
      }
    }
  ]
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.