Aashay Amballi
Aashay Amballi

Reputation: 1411

instance.save() is not saving the model in ListSerializer Django Rest Framework

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.

Upvotes: 2

Views: 4530

Answers (1)

Mehran
Mehran

Reputation: 1304

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

Upvotes: 0

Related Questions