0

I am trying to upload a csv file and then using it to populate a table in the database (creating multiple objects).

serializers.py:

def instantiate_batch_objects(data_list, user):
    return [
        WorkData(
            work=db_obj['work'],
            recordTime=db_obj['recordTime'],
            user=user
        ) for db_obj in data_list
    ]


class FileUploadSerializer(serializers.ModelSerializer):
    filedata = serializers.FileField(write_only=True)

    class Meta:
        model = WorkData
        fields = ['user', 'filedata']

    def create(self, validated_data):
        file = validated_data.pop('filedata')
        data_list = csv_file_parser(file)        
        batch = instantiate_batch_objects(data_list, validated_data['user'])
        work_data_objects = WorkData.objects.bulk_create(batch)
        # print(work_data_objects[0])
        return work_data_objects

views.py:

class FileUploadView(generics.CreateAPIView):
    queryset = WorkData.objects.all()
    permission_classes = [IsAuthenticated]
    serializer_class = FileUploadSerializer

    # I guess, this is not need for my case.
    def get_serializer(self, *args, **kwargs):
        print(kwargs.get('data'))
        if isinstance(kwargs.get('data', {}), list):
            kwargs['many'] = True

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

models.py

class WorkData(models.Model):
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name='work_data',
    )
    work = models.IntegerField(blank=False, null=False)
    recordTime = models.DateTimeField(blank=False, null=True)

When I upload the file and post it I get this error:

Got AttributeError when attempting to get a value for field user on serializer FileUploadSerializer. The serializer field might be named incorrectly and not match any attribute or key on the list instance. Original exception text was: 'list' object has no attribute 'user'.

But I can see table is populated successfully in the database. What should I return from create method of FileUploadSerializer?

9
  • FileUploadSerializer.create() supposed to be retrun only a single model instance. Commented Oct 31, 2020 at 3:21
  • @ArakkalAbu I need to create objects in bulk. Commented Oct 31, 2020 at 8:05
  • @ArakkalAbu, BTW, you can return multiple Commented Oct 31, 2020 at 8:45
  • can you add a snippet to the data you are passing to your endpoint? Commented Oct 31, 2020 at 9:22
  • I am passing a file to the api endpoint. Then reading data from the file and creating objects overriding serializer class create method. Commented Oct 31, 2020 at 9:32

1 Answer 1

1

OK, after trying an example myself I was able to reproduce the errors, I have a better understanding of why this is happing now.

First, let's put the implementation of create() on the view class here

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(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

The original error of Got AttributeError when attempting to get a value for field user... etc happened because the create() in the FileUploadView is returning serializer.data which is expecting fields user and filedata but create() on FileUploadSerializer is returning a list of objects so you can see now why this is happening.

You can solve this by overriding create() on FileUploadView and serialize the returned serializer data with a WorkDataSerializer that you will create

For ex:

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)
        workData = WorkDataSerializer(data=serializer.data)
        return Response(workData.data, status=status.HTTP_201_CREATED, headers=headers)

OR, you can do it on serializer level - which I prefer -

For example:

class FileUploadSerializer(serializers.ModelSerializer):
    filedata = serializers.FileField(write_only=True)
    created_objects_from_file = serializers.SerializerMethodField()
        
    def get_created_objects_from_file(self, obj):
       file = self.validated_data.pop('filedata')
       data_list = csv_file_parser(file)        
       batch = instantiate_batch_objects(data_list, self.validated_data['user'])
       work_data_objects = WorkData.objects.bulk_create(batch)
       
       return WorkDataSerializer(work_data_objects, many = True).data
    
    
    class Meta:
        model = WorkData
        fields = ['user', 'filedata']



class WorkDataSerializer(serializers.Serializer):
         # fields of WorkData model you want to return

This should work with no problems, note that SerializerMethodField is read_only by default

see https://www.django-rest-framework.org/api-guide/fields/#serializermethodfield

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

9 Comments

My upvote for finding the problem. I too figured it out and the naive solution came to my mind is to just return one object from list: return work_data_objects[0].
I assumed that returning all the created objects is mandatory or something, glad that you figured it out.
I will update my previouse answer so nobody get confused later
No, it wasn't. But looking for a clean solution right now. It would be great if you can put solution using ListSerialzer.
I would suggest please test your solution and post it only after it works. I can see lots of errors in second solution.
|

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.