Промежуточная таблица 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