Ошибки Django REST при сериализации модели с помощью ManyToManyField

Я создал класс, моделирующий группу файлов в сборке программного продукта на сервере Django с использованием пакета Django-REST. Дизайн заключается в том, что группа файлов (экземпляр хранилища) должна иметь возможность назначаться нескольким экземплярам сборки (например, как «альфа», так и «бета» сборки с использованием одного и того же точного хранилища аудиофайлов). Однако во время создания депо оно создается как часть создания одиночной сборки на клиенте; только позже служебный скрипт позволит добавить существующее хранилище к другим сборкам.

Мне казалось естественным, что класс Depot должен представлять эту связь с помощью ManyToManyField. Проблема в том, что сериализатор, похоже, не знает, что делать с этим ManyToManyField. Я пробовал несколько обходных путей, но у каждого своя ошибка. Я пытался сделать так, чтобы мой DepotSerializer был либо rest_framework.serializers.Serializer, либо rest_framework.serializers.ModelSerializer, но это, похоже, не имеет отношения к этой проблеме.

Модели.ру:

class Depot(models.Model):
    name = models.CharField(max_length=64)
    builds = models.ManyToManyField(Build)

    TYPE_EXECUTABLE = 0
    TYPE_CORE = 1
    TYPE_STREAMING = 2
    depot_type = models.IntegerField(choices = (
        (TYPE_EXECUTABLE, 'Executable'),
        (TYPE_CORE, 'Core'),
        (TYPE_STREAMING, 'Streaming'),
    ))

    def __str__(self):
        return self.name

Просмотры.py:

class DepotCreate(mixins.CreateModelMixin,
                  generics.GenericAPIView):
    serializer_class = DepotSerializer
    queryset = Depot.objects.all()

    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)

Serializers.py версии 1:

class DepotSerializer(serializers.ModelSerializer):
    builds = serializers.PrimaryKeyRelatedField()

    class Meta:
        model = Depot
        fields = ('id', 'name', 'builds', 'depot_type')
        read_only_fields = ('id',)

    def validate(self, attrs):
        build = attrs['builds']
        if build == None:
            raise serializers.ValidationError("Build could not be found")

        for depot in build.depot_set.all():
            if depot.name == attrs['name']:
                raise serializers.ValidationError("Build already contains a depot \"{}\"".format(depot.name))

        return attrs

    def restore_object(self, attrs, instance=None):
        # existence of the build has already been validated
        return Depot(**attrs)

Эта версия приводит к следующей ошибке во время вызова init Depot:

Exception Type: TypeError
Exception Value:    
'builds' is an invalid keyword argument for this function
Exception Location: /webapps/cdp_admin_django/lib/python3.4/site-packages/django/db/models/base.py in __init__, line 417

Похоже, это указывает на то, что модель Depot не может обрабатывать параметр builds, несмотря на то, что в ней есть член ManyToManyField.

Serializers.py 'restore_object' версия 2:

def restore_object(self, attrs, instance=None):
    # existence of the build has already been validated
    build = attrs['builds']

    depotObj = Depot(name=attrs['name'], depot_type=attrs['depot_type'])
    depotObj.builds.add(build)
    return depotObj

Это дало мне ошибку:

Exception Type: ValueError
Exception Value:    
"<Depot: depot_test4>" needs to have a value for field "depot" before this many-to-many relationship can be used.
Exception Location: /webapps/cdp_admin_django/lib/python3.4/site-packages/django/db/models/fields/related.py in __init__, line 524

После небольшого исследования я обнаружил, что отношения ManyToMany могут доставить вам проблемы, если вы не сохраните запись MYSQL, прежде чем пытаться манипулировать этим полем. Следовательно, restore_object версии 3:

def restore_object(self, attrs, instance=None):
    # existence of the build has already been validated
    build = attrs['builds']

    depotObj = Depot(name=attrs['name'], depot_type=attrs['depot_type'])
    depotObj.save()
    depotObj.builds.add(build)
    return depotObj

Это успешно создает запись таблицы для этого экземпляра, но в итоге выдает следующую ошибку:

Exception Type: IntegrityError
Exception Value:    
(1062, "Duplicate entry '5' for key 'PRIMARY'")
Exception Location: /webapps/cdp_admin_django/lib/python3.4/site-packages/MySQLdb/connections.py in defaulterrorhandler, line 38

Эта ошибка возникает во время вызова rest_framework/mixins.py для serializer.save(force_insert=True). Похоже, что предполагается принудительное создание новой записи в таблице, что, по-видимому, противоречит моему предыдущему вызову Model.save.

Кто-нибудь знает правильный подход к такому дизайну? Я чувствую, что это не может быть такой необычной структурой таблицы.

EDIT 20/10/2014: После приведенного ниже предложения я экспериментировал с написанием нового ModelSerializer для одной из моих моделей; по большей части из-за таких проблем с порядком операций я отказался от использования ModelSerializer и выполнил всю обработку полей данных в объекте в views.py, читая serializer.data.

Наличие PrimaryKeyRelatedField(many=True) в справке ModelSerializer DID. Примечательно, что мне удалось создать экземпляр сериализатора с существующими моделями и получить правильный файл serializer.data. Однако у меня все еще есть проблема, когда restore_object может делать все, кроме создания нового экземпляра модели и передачи значения ManyToManyField. Я все еще получаю «TypeError: '[PrimaryKeyRelatedField name]' является недопустимым аргументом ключевого слова для этой функции», если я передаю поле функции инициализации модели. Я все еще не могу сохранить модель до того, как библиотека REST сделает это сама. Кроме того, в этом режиме сериализатор заполняет файл serializer.data значениями модели, а не значениями, указанными во входных данных. Поэтому, если вы не используете значение атрибута PrimaryKeyRelatedField в restore_object, оно отбрасывается.

Похоже, мне нужно переопределить ModelSerializer.save на какое-то предварительное сохранение, применить ввод ManyToMany и пост-сохранение, но мне потребуются значения attrs, чтобы я мог применить и изменить ManyToManyField в это время. Я понимаю, что сериализатор имеет поле init_data для просмотра исходных входных данных, но в случае, когда сериализатор используется для десериализации списка данных в список новых объектов, я не Думаю, есть способ отследить, какой сериализатор.init_data соответствует какому сериализатору.объекту.


person Sasquatchua    schedule 04.08.2014    source источник
comment
Лучшим решением, которое я придумал, было установить значение идентификатора сборки для depotObj в depotObj.initial_build_id, чтобы передать его в views.py. Базовый класс Django REST CreateModelMixin хранит созданный экземпляр в self.object, поэтому я могу взять self.object.initial_build_id и применить там логику связывания.   -  person Sasquatchua    schedule 26.08.2014
comment
Вы пробовали флаг many=True на своем поле? это выглядит обязательным в документации и отсутствует в приведенном выше коде django-rest -framework.org/api-guide/   -  person Anentropic    schedule 07.10.2014
comment
Это отличный момент. Когда я писал этот пост, я очень рано изучал Django и занимался этим проектом. Сейчас я, к сожалению, нахожусь в такой ситуации, когда не могу экспериментировать с изменением интерфейса REST. Кроме того, дизайн немного изменился, поэтому приведенный выше код в настоящее время не существует. Я был бы рад проверить это при первой же возможности.   -  person Sasquatchua    schedule 18.10.2014


Ответы (1)


В вашей версии сериализатора 1 вам не нужно добавлять

сборки = сериализаторы.PrimaryKeyRelatedField()

поскольку сериализатор модели создаст это для вас. На самом деле, если вы посмотрите на пример документации (http://www.django-rest-framework.org/api-guide/relations/) вы увидите, что PrimaryKeyRelatedField применяется, когда есть FK «к» текущей модели (не отношение M2M).

Я бы удалил это из сериализатора и посмотрел, что происходит.

person Pierre Alex    schedule 28.11.2014
comment
Да, кажется, что мои ранние усилия по Django делали намного больше ручного кодирования, чем необходимо. С тех пор я смог использовать сериализаторы, ссылающиеся на модели, владеющие ManyToManyField, ничего не переопределяя. Теперь у меня осталось желание переписать большую часть этого кода... - person Sasquatchua; 06.12.2014
comment
Оглядываясь назад, я пытался перевести поле ввода клиента «сборка» (единственное число) в запись M2M. Таким образом, хотя предлагаемое изменение технически больше не обеспечивает перевод полей, оно явно не стоило затраченных усилий. - person Sasquatchua; 06.12.2014