Как создать несколько объектов в методе создания сериализаторов?

#python #django #database #django-rest-framework

#python #django #База данных #django-rest-framework

Вопрос:

Я пытаюсь загрузить файл csv, а затем использовать его для заполнения таблицы в базе данных (создание нескольких объектов).

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)
  

Когда я загружаю файл и публикую его, я получаю эту ошибку:

Получил ошибку AttributeError при попытке получить значение для поля user в сериализаторе FileUploadSerializer . Поле сериализатора может быть названо неправильно и не соответствовать какому-либо атрибуту или ключу list экземпляра. Исходный текст исключения был: объект ‘list’ не имеет атрибута ‘user’.

Но я вижу, что таблица успешно заполнена в базе данных. Что я должен вернуть из create метода FileUploadSerializer ?

Комментарии:

1. FileUploadSerializer.create() предполагается повторный запуск только одного экземпляра модели.

2. @ArakkalAbu Мне нужно создавать объекты массово.

3. @ArakkalAbu, кстати, вы можете вернуть несколько

4. можете ли вы добавить фрагмент к данным, которые вы передаете своей конечной точке?

5. Я передаю файл в конечную точку api. Затем считывание данных из файла и создание объектов, переопределяющих метод создания класса сериализатора.

Ответ №1:

Хорошо, попробовав пример самостоятельно, я смог воспроизвести ошибки, я лучше понимаю, почему это происходит сейчас.

Во-первых, давайте поместим реализацию create() в класс view здесь

 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)
  

Исходная ошибка Got AttributeError when attempting to get a value for field user … etc произошло из create() -за того, что in FileUploadView возвращает serializer.data ожидающие поля user , а filedata but create() on FileUploadSerializer возвращает список объектов, так что теперь вы можете понять, почему это происходит.

Вы можете решить эту проблему, переопределив create() on FileUploadView и сериализовав возвращенные данные сериализатора с помощью a WorkDataSerializer , которые вы создадите

Например:

 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)
  

ИЛИ вы можете сделать это на уровне сериализатора — что я предпочитаю —

Например:

 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
  

Это должно работать без проблем, обратите внимание, что SerializerMethodField по умолчанию используется только для чтения

смотрите https://www.django-rest-framework.org/api-guide/fields/#serializermethodfield

Комментарии:

1. Мой голос за поиск проблемы. Я тоже понял это, и мне пришло в голову наивное решение — просто вернуть один объект из списка : return work_data_objects[0] .

2. Я предположил, что возврат всех созданных объектов является обязательным или что-то в этом роде, рад, что вы это поняли.

3. Я обновлю свой предыдущий ответ, чтобы никто не запутался позже

4. Нет, это не так. Но сейчас ищу чистое решение. Было бы здорово, если бы вы могли использовать решение ListSerialzer .

5. Я бы посоветовал, пожалуйста, протестировать ваше решение и опубликовать его только после того, как оно заработает. Я вижу много ошибок во втором решении.