Промежуточная таблица DRF - размещение данных в промежуточной таблице

В моем проекте БД есть отношения "многие-ко-многим", и у меня возникают проблемы с аспектом POST. В настоящее время у меня есть таблица под названием Loads, Containers и Container_Loads (это промежуточная таблица).

Мой вопрос заключается в следующем: Я хочу иметь возможность отправить POST запрос в промежуточную таблицу ContainerLoad и просто обновить эту таблицу значениями, которые ей требуются, а именно: ID груза (PK таблицы Load), ID контейнера (PK таблицы Container) и # of pallets (уникальное поле для промежуточной таблицы). Я могу получать/извлекать записи просто отлично, но когда я пытаюсь отправить POST запрос с полезной нагрузкой, такой как

{
        "id":3,
        "pallets":"4",
        "containerNumberId":5,
        "loadNumberId":53
}

(где containerNumberID и loadNumberID - существующие ключи в соответствующих таблицах), кажется, что мой код хочет создать совершенно новую запись Load (поскольку он запрашивает остальные поля модели Load), тогда как я хочу просто создать запись в промежуточной таблице без создания новой записи в таблице Load.

Итак, для целей моего проекта, груз может находиться на многих контейнерах [представьте, что он разделен, потому что весь он не поместился на одном], а контейнер может принадлежать многим грузам

Моя models.py выглядит следующим образом:

class ContainerLoad(models.Model):
    id = models.AutoField(primary_key=True)
    load_number = models.ForeignKey(Load,on_delete=models.CASCADE)
    container_number = models.ForeignKey(Container,on_delete=models.CASCADE)
    pallets = models.CharField(blank=True,null=True,default=0,max_length=20)

    class Meta:
        db_table = 'ContainerLoad'


#load model shortened for brevity
class Load(models.Model):

    id = models.AutoField(primary_key=True)
    load_container_number = models.ManyToManyField(Container, through='ContainerLoad',through_fields=('load_number','container_number'))

    class Meta:
        db_table = "Load"

class Container(models.Model):
    id = models.AutoField(primary_key=True)
    container_number = models.CharField(max_length=15)
    in_use = models.BooleanField()
    

    class Meta:
        db_table = "Container"

мой serializers.py в настоящее время выглядит следующим образом, закомментированная часть - это моя попытка заставить POST работать)

class ContainerLoadSerializer(WritableNestedModelSerializer):

    # load_number_id = LoadSerializer(read_only=False)
    # container_number_id = ContainerSerializer(read_only=False)


    class Meta:
        model = ContainerLoad
        fields = "__all__"
        depth = 2

class LoadSerializer(WritableNestedModelSerializer):

    primary_driver = DriverSerializer(read_only=False)
    second_driver = DriverSerializer(allow_null=True,read_only=False)
    third_driver = DriverSerializer(allow_null=True,read_only=False)
    bnsf_container_number = ContainerSerializer(read_only=False)
    pickup_location = LocationSerializer(read_only=False)
    delivery_location = LocationSerializer(read_only=False)
    broker = BrokerSerializer(read_only=False)
    booked_by = EmployeeSerializer(read_only=False) 

    class Meta:
        model = Load
        fields = '__all__'
        depth = 1

class ContainerSerializer(serializers.ModelSerializer):
    container_number = serializers.CharField()
    in_use = serializers.BooleanField()

    class Meta:
        model = Container
        fields = '__all__'
        depth = 1

И наконец views.py

class ContainerLoadViews(APIView):
    def get(self, request, id=None):
        if id:
            container = ContainerLoad.objects.get(id=id)
            serializer = ContainerLoadSerializer(container)
            return Response({"status": "success", "data": serializer.data}, status=status.HTTP_200_OK)
        else:
            containers = ContainerLoad.objects.all()
            serializer = ContainerLoadSerializer(containers, many=True)
            return Response({"status": "success", "data": serializer.data}, status=status.HTTP_200_OK)
    
    def post(self, request):
        serializer = ContainerLoadSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response({"status": "success", "data": serializer.data}, status=status.HTTP_200_OK)
        else:
            return Response({"status": "Error", "data": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)

Если вы посмотрите на код метода save класса BaseSerializer, то увидите следующее:

if self.instance is not None:
    self.instance = self.update(self.instance, validated_data)
    assert self.instance is not None, (
        '`update()` did not return an object instance.'
    )
else:
    self.instance = self.create(validated_data)
    assert self.instance is not None, (
        '`create()` did not return an object instance.'
    )

Как вы не передаете instance в функции post:

serializer = ContainerLoadSerializer(data=request.data)

Сохранение всегда вызывает создание. Вы должны сделать что-то вроде.

try:
    instance = ContainerLoad.object.get(id=request.data['id'])
except: 
    instance = None

serializer = ContainerLoadSerializer(instance=instance, data = request.data)

У вас depth установлено значение 2 в вашем ContainerLoadSerializer классе Meta, что говорит сериализатору генерировать вложенное представление ваших моделей.

https://www.django-rest-framework.org/api-guide/serializers/#specifying-nested-serialization

По умолчанию ModelSerializer использует первичные ключи для отношений, но вы также можете легко генерировать вложенные представления, используя опцию depth:

Опция depth должна быть установлена в целочисленное значение, которое указывает глубину отношений, которые должны быть пройдены перед возвратом к плоскому представлению.

Если вы удалите атрибут depth, сериализатор по умолчанию будет ожидать значение первичного ключа, что является желаемым поведением.

Ваш сериализатор должен выглядеть примерно так:

class ContainerLoadSerializer(serializers.ModelSerializer):

    class Meta:
        model = ContainerLoad
        fields = "__all__"

Решением было то, что мне нужен был вложенный ответ при чтении данных из таблицы ContainerLoad, но простая функция записи (не вложенная) при POST данных.

Решением было использование методов to_representation и to_internal_value (https://www.django-rest-framework.org/api-guide/serializers/#overriding-serialization-and-deserialization-behavior), доступных в DRF, чтобы переопределить поведение сериализаторов. Вот код, который теперь работает для GET и POST запросов и больше не запрашивает поля, связанные с моделями Load или Container при вставке данных.

class ContainerSerializer(serializers.ModelSerializer):
    container_number = serializers.CharField()
    in_use = serializers.BooleanField()

    class ContainerFieldSerializer(serializers.Field):
        def to_internal_value(self,value):
            return Container.objects.get(id=value)
        
        def to_representation(self,instance):
            return ContainerSerializer(instance=instance).data

    class Meta:
        model = Container
        fields = '__all__'
        depth = 1

Я сделал то же самое для Load Serializer.

и затем для моего сериализатора ContainerLoad я просто присваиваю поля FK новым классам, которые я создал:

class ContainerLoadSerializer(serializers.ModelSerializer):

    cl_container = ContainerSerializer.ContainerFieldSerializer()
    cl_load = LoadSerializer.LoadFieldSerializer()

    class Meta:
        model = ContainerLoad
        fields = "__all__"
        depth = 2
Вернуться на верх