6

I need to update categories in many Article in one request.

In ArticleViewSet I have:

def get_serializer_class(self):
    if self.action in ['partial_update', 'update']:
        return ArticlePostSerializer
    return ArticleSerializer

So ArticlePostSerializer need to be changed.

This is my serializers code:

class ArticleShortCategorySerializer(serializers.ModelSerializer):

    class Meta:
        model = Category
        fields = 'id', 'name'


class ArticleSerializer(serializers.ModelSerializer):
    categories = serializers.SerializerMethodField()

    def get_categories(self, obj):
        return ArticleShortCategorySerializer(obj.categories, many=True).data

    class Meta:
        model = Article
        read_only_fields = 'id'
        fields = ('categories', 'text') + read_only_fields


class ArticlePostSerializer(serializers.ModelSerializer):

    class Meta:
        model = Article
        fields = 'id', 'categories', 'text'

I tried to add:

class ArticlePostListSerializer(serializers.ListSerializer):

and

class Meta:
    list_serializer_class = ArticlePostListSerializer

But it doen't work. How to change this code to do multiple update. My json request

{
    [id: 90, categories: [10,12,14]],
    [id: 93, categories: [10,12,14]],
    [id: 95, categories: [10,12,14]]
}
pelcomppl
  • 575
  • 2
  • 6
  • 16
  • Use create mixins – Anup Yadav Dec 14 '17 at 09:20
  • Use create mixins.CreateModelMixin in view and override Create method in Serializers. OR you can do the updating work in get_queryset but that would be bad idea. – Anup Yadav Dec 14 '17 at 09:26
  • @AnupYadav could you give me a bit of example code, how to do it? I am new in djnago. I need to update records so should I use `mixins.CreateModelMixin` or `mixins.UpdateModelMixin`? – pelcomppl Dec 14 '17 at 09:35
  • Use either of one and check sample in answer as here in comments it not displaying correctly. – Anup Yadav Dec 14 '17 at 09:41
  • @AnupYadav so I not have to do any changes in `ArticleViewSet`? Changing serializers is enough? – pelcomppl Dec 14 '17 at 10:11
  • Actually I don't know your ArticleViewSet, but I've added my View set down in the code, please check that. – Anup Yadav Dec 14 '17 at 10:13

3 Answers3

1

Here is sample of CreateMixins OR UpdateMixins you requested.

======================= VIEW ================================

class OrderCreate(mixins.CreateModelMixin,viewsets.GenericViewSet):
    pagination_class = None

    def get_queryset(self):
        return []

    def get_serializer_class(self):
        return serializers.OrderSerializer

======================= Serializer =============================

class OrderDetailSerializer(serializers.ModelSerializer):
    class Meta:
        model = crm_models.OrderDetail
        fields = (
                'product',
                'quantity',
                'rate_per_unit',
                'order_quantity'
                )

class OrderSerializer(serializers.ModelSerializer):
    order_details = OrderDetailSerializer(many = True)
    class Meta:
        model = crm_models.OrderMaster
        fields = (
                'order',
                'invoice_number',
                'client',
                'beat_check',
                'target_customer',
                'order_editor_client_employee',
                'order_marked',
                'order_saved',
                'edit_marked',
                'edit_saved',
                'adhoc',
                'order_details'
                )

    def create(self, validated_data,*args,**kwargs):
        ordersdetails_data = validated_data.pop('order_details')
        user = None
        request = self.context.get("request")
        if request and hasattr(request, "user"):
            user = request.user
            validated_data['client'] = user.client
            validated_data['order_editor_client_employee'] = user
            validated_data['adhoc'] = validated_data['adhoc'] if 'adhoc' in validated_data else False

        orderObj = super(OrderSerializer, self).create(validated_data,*args,**kwargs)
        orderdetails = []
        for details in ordersdetails_data:
            orderdetails.append(crm_models.OrderDetail(
                product= details['product'],
                quantity = details['quantity'],
                rate_per_unit = details['rate_per_unit'],
                order_quantity = details['order_quantity'],
                order = orderObj
            ))
        crm_models.OrderDetail.objects.bulk_create(orderdetails)
        return orderObj

In Update view function name would be changed to update, you can find more documentation http://www.django-rest-framework.org/api-guide/generic-views/#createmodelmixin

Anup Yadav
  • 2,825
  • 3
  • 21
  • 30
  • I understand that in your code you can add `OrderDetail` to `Order`. But I need to update multiple `Order` records. – pelcomppl Dec 14 '17 at 14:05
  • You can achieve this using my logic, mostly you need to go for RAW work, like I did, basically you need to get request dict in which you get all data posted by client via JSON preferably. You can even add custom validations there. – Anup Yadav Dec 14 '17 at 14:08
  • What url should I send this request - the same as single update (with id)? – pelcomppl Dec 14 '17 at 14:32
1

I found K. Moe's answer to this question: Django Rest Framework POST Update if existing or create much easier to understand and implement. You only need to add a create method in the serializer and use mixins.CreateModelMixin, generics.GenericAPIView in the view. Then you can use a POST request, instead of PUT/PATCH. It allows to create AND update data stored in your database. My code for the view:

class ZipCodeList(mixins.CreateModelMixin, generics.GenericAPIView):    
    def post(self, request, format=None):
        is_many = isinstance(request.data, list)

        if is_many:
            serializer = ZipCodeSerializer(data=request.data, many=True)
            if serializer.is_valid():
                serializer.save()
                return Response(serializer.data, status=status.HTTP_201_CREATED)
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

        else:
            serializer = ZipCodeSerializer(data=request.data)
            if serializer.is_valid():
                serializer.save()
                return Response(serializer.data, status=status.HTTP_201_CREATED)
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Greg Holst
  • 874
  • 10
  • 23
1

@Greg Holst, why so much duplication? Why not:

class ZipCodeList(mixins.CreateModelMixin, generics.GenericAPIView):
    def post(self, request, format=None):
        serializer = ZipCodeSerializer(data=request.data, many=isinstance(request.data, list))
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

Anyway, that only worked for me for creating new objects, didn't work to create-or-update in one sweep (it kept telling me these objects already exist), so this is what I did:

class ContributorSyncListAPIView(ListAPIView):
    permission_classes = (isSuperUserPermission,)
    allowed_methods = ("GET", "PUT")
    lookup_field = "airtable_id"
    serializer_class = ContributorSyncSerializer  # Doesn't get used in Put, just in Get.
    model = Contributor

    def get_queryset(self):
        return self.model.objects.all()

    def put(self, request, format=None):
        objects = list(request.data)
        # objects is a list of OrderedDicts
        try:
            for o in objects:
                try:
                    instance = self.model.objects.get(
                        **{self.lookup_field: o.get(self.lookup_field)}
                    )
                    for key, value in o.items():
                        setattr(instance, key, value)
                except self.model.DoesNotExist:
                    instance = self.model(**o)
                instance.save()
            return Response(objects, status=status.HTTP_200_OK)
        except Exception as e:
            return Response({"detail": str(e)}, status=status.HTTP_400_BAD_REQUEST)

Note my code above is very light on validation just because it was for a process of syncing models from one environment to another by a superuser, different environments with an identical codebase; so the assumption was the data had already been validated when the data was entered into the first environment. For any other purpose you'd want to validate more. But this is what I had to do to handle a list of objects that may need to be created or updating, on an object-by-object basis.

Mic
  • 107
  • 2
  • 7