2

In Django Rest Framework ListSerializer when I want to save the validated data to the database by calling instance.save() I'm getting an error saying queryset object has no attribute save.

ListSerializer class:

class NoAccessDetailsListSerializer(serializers.ListSerializer):

    # This will be called when there is list of objects
    #here instance is list of queryset and validated_data is the list of json object
    def update(self, instance, validated_data):

        ret = []
        for index, data in enumerate(validated_data):

            #checking if row is already chosen
            if(instance[index].row_chosen):

                # do not update the info to db
                # just append the data to ret
                ret.append(instance[index])

            else:
                instance.id = instance[index].id
                instance.row_chosen = validated_data[index].get(
                    'row_chosen')
                instance.user_working = validated_data[index].get(
                    'user_working')

                ret.append(instance)

                instance.save()

        return ret

Serializer Class

class NoAccessDetailsSerializer(serializers.ModelSerializer):


    id = serializers.IntegerField(read_only=False)

    class Meta:
        model = NoAccessDetails
        list_serializer_class = NoAccessDetailsListSerializer
        fields = ("id", "row_chosen",
                  "user_working")

    def update(self, instance, validated_data):
        instance.id = instance.id
        instance.row_chosen = validated_data.get(
            'row_chosen')
        instance.user_working = validated_data.get(
            'user_working ')

        instance.save()
        return instance

Basically in ListSerializer I'm checking if the row is chosen already in the DB. If True then I just append the instance data to a dictionary else I want to update the data to the DB and append the updated data to a list and return it.

Here in the ListSerializer I'm passing filtered queryset from the APIView class as instance and validated_data is a list of validated data.

Sample JSON data which I will pass to the APIView class:

[
        {
            "id": 1,
            "row_chosen": true,
            "user_working": "John"
        },
        {
            "id": 1,
            "row_chosen": true,
            "user_working": "David"
        },
]

When I pass the JSON data, it will properly filter out the rows from DB and pass the queryset as instance and JSON data to the serializer class.

# here list_of_id is the ids which are there in the JSON object. i.e [1,2]

filtered_id_data= NoAccessDetails.objects.filter(
                id__in=list_of_id)

            serializer = NoAccessDetailsSerializer(filtered_id_data,
                                                   data=request.data,
                                                   many=True,
                                                   )

The ListSerializer update() is working but when it runs else block and tries to update the data it gives me an error queryset object has no attribute save. Whereas in the serializer's update() it runs the instance.save() and updates the data for the single object. I'm not sure where I'm making the mistake. Please help me with this.

Update:

I changed instance.save() to instance[index].save() in ListSerializer class. Now the queryset object has no attribute save has been fixed. Even though when I use instance[index].save() I'm unable to save the data in the data base.

Models:

class NoAccessDetails(models.Model):
    20 CharFields
    ...
    ...
    user_working = models.ForeignKey(
        UserProfile, on_delete=models.CASCADE, blank=True, null=True)
    row_chosen = models.BooleanField(default=False)

class UserProfile(models.Model):
    user_id = models.CharField(primary_key=True, max_length=10)
    user_name = models.CharField(max_length=100)
    user_email = models.EmailField()
    is_admin = models.BooleanField(default=False)

Here in the NoAccessDetail model, I've kept user_working null true because the data to this model will be coming from a different source. Initially while importing the data the user_working will be null. While updating the data from an API call, I'm validating the JSON data.

5
  • It is a ListSerializer, so the instance here is a list i.e. a queryset, not an instance or object. Commented Sep 28, 2019 at 19:45
  • Yes it is a list of queryset. <QuerySet [<NoAccessDetails:>, <NoAccessDetails: >]> Commented Sep 28, 2019 at 19:50
  • @Mehran am I wrong anywhere, Could you please correct me? Commented Sep 28, 2019 at 19:50
  • Basically you need to loop through the instance (since it is a list) and call the .save() on each item :). I have posted an answer citing the code in the docs Commented Sep 28, 2019 at 19:55
  • I tried instance[index].save() but this time it will not give me any error but instead, it will not save the object in database Commented Sep 28, 2019 at 19:58

1 Answer 1

0

To call the .save() method, you have to call it on an instance of a Model, not on a QuerySet of the model. According to DRF Docs,

class BookListSerializer(serializers.ListSerializer):
  def update(self, instance, validated_data):
    # Maps for id->instance and id->data item.
    book_mapping = {book.id: book for book in instance}
    data_mapping = {item['id']: item for item in validated_data}

    # Perform creations and updates.
    ret = []
    for book_id, data in data_mapping.items():
        book = book_mapping.get(book_id, None)
        if book is None:
            ret.append(self.child.create(data))
        else:
            ret.append(self.child.update(book, data))

    # Perform deletions.
    for book_id, book in book_mapping.items():
        if book_id not in data_mapping:
            book.delete()

    return ret

You can see they are using a book_mapping. This is creating a dictionary where the key is the book's id and the value is the instance of the book.

Hope this helps!

EDIT Check the line just below the 'else:' block. You see you need to use .get() to get the object of the model you want to update, and then use the .save() method.

RE-EDIT Using instance[index].save() should also work. I think you need to call obj.save() before appending to ret.

class NoAccessDetailsListSerializer(serializers.ListSerializer):
    def update(self, instance, validated_data):
        ...
        ...
        else:
            obj = NoAccessDetails.objects.get(id=instance[index].id)
            # or obj = instance[index]
            obj.row_chosen = validated_data[index].get(
                'row_chosen')
            obj.user_working = validated_data[index].get(
                'user_working')

            print('Instance data ',
                  obj.row_chosen,
                  obj.user_working)
            obj.save() # call this before appending to ret
            ret.append(obj)


    return ret

RE-RE-EDIT

I updated the snippet according to docs.

class NoAccessDetailsListSerializer(serializers.ListSerializer):

# This will be called when there is list of objects
# here instance is list of queryset and validated_data is the list of json object
def update(self, instance, validated_data):

    ret = []

    obj_mapping = {obj.id: obj for obj in instance}
    data_mapping = {item['id']: item for item in validated_data}

    for obj_id, data in data_mapping.items():
        obj = obj_mapping.get(obj_id, None)
        if not obj:
            continue

        if obj.row_chosen:
            ret.append(obj)
        else:
            obj.row_chosen = data['row_chosen']
            obj.user_working = data['user_working']
            obj.save()
            ret.append(obj)

    return ret
Sign up to request clarification or add additional context in comments.

23 Comments

In my code I'm doing like this NoAccessDetailsSerializer(filtered_id_data, data=request.data, many=True ) where filtered_id_data is the queryset. Instead of passing the queryset what I need to pass?
Sure! Let me know if it worked! I personally have never really bothered using ListSerializer and always stuck with ModelSerializer or basic Serializer (if not connected with any Model)
And wanted to know if you had a list of objects which needs to be updated how would you do? In the documentation itself, they have mentioned to go with the ListSerializer.
hey @Mehran the above code actually worked. But still, instance[index].save() is not saving the data in the database. Whereas in the update() in ModelSerializer saves the data by calling instance.save(). Do you have any idea?
Hey @Mehran! I don't know really, all of a sudden it started working fine. Now the user_working and row_chosen data are getting saved in the database. Thank you, my friend. Really much appreciated for helping me throughout this issue. Thank you :)
|

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.