Django Serializers - как обновить поле PrimaryKeyRelatedField вложенного отношения
У меня есть следующие модели
Модель компании:
class Company(SafeDeleteModel):
category = models.ForeignKey(Category, on_delete=models.CASCADE, null=True, blank=True)
code = models.CharField(max_length=10, null=True)
name = models.CharField(max_length=100)
addresses = GenericRelation('Address')
Модель адреса:
В этой модели у меня есть общее отношение от адреса к компании. Компания может иметь множество адресов.
class Address(SafeDeleteModel):
content_type = models.ForeignKey(ContentType, related_name='model_addresses', on_delete=models.CASCADE)
object_id = models.PositiveIntegerField(null=True)
content_object = GenericForeignKey('content_type', 'object_id')
name = models.CharField(max_length=100, null=True, blank=True)
street = models.CharField(max_length=200, null=True, blank=True)
external_number = models.CharField(max_length=20, null=True, blank=True)
internal_number = models.CharField(max_length=20, null=True, blank=True)
colony = models.ForeignKey(Colony, on_delete=models.CASCADE, null=True, blank=True)
locality = models.ForeignKey(Locality, on_delete=models.CASCADE, null=True, blank=True)
municipality = models.ForeignKey(Municipality, on_delete=models.CASCADE, null=True, blank=True)
state = models.ForeignKey(State, on_delete=models.CASCADE, null=True, blank=True)
country = models.ForeignKey(Country, on_delete=models.CASCADE, null=True, blank=True)
zip_code = models.CharField(max_length=20, null=True, blank=True)
Сериализатор адресов:
class AddressSerializer(serializers.ModelSerializer):
id = serializers.IntegerField()
country = CountrySerializer(read_only = True)
country_id = serializers.PrimaryKeyRelatedField(queryset = Country.objects.all(), source="country")
colony = ColonySerializer(read_only = True)
colony_id = serializers.PrimaryKeyRelatedField(source="colony", queryset = Colony.objects.all(), required=True)
locality = LocalitySerializer(read_only = True)
locality_id = serializers.PrimaryKeyRelatedField(queryset = Locality.objects.all(), source="locality", allow_null=True)
municipality = MunicipalitySerializer(read_only = True)
municipality_id = serializers.PrimaryKeyRelatedField(queryset = Municipality.objects.all(), source="municipality", allow_null=True)
state = StateSerializer(read_only = True)
state_id = serializers.PrimaryKeyRelatedField(queryset = State.objects.all(), source="state", allow_null=True)
class Meta:
model = Address
fields = '__all__'
В сериализаторе, country
, colony
, locality
, municipality
, state
- это объекты других Моделей, и я добавил PrimaryKeyRelatedField
с окончанием *_id
, чтобы получить id каждого из них и использовать его для отправки из front-end, если есть какие-либо изменения.
Сериализатор компании:
class CompanySerializer(serializers.ModelSerializer):
category = CategorySerializer(read_only=True)
category_id = serializers.PrimaryKeyRelatedField(queryset = Category.objects.all(), source='category', allow_null=True)
addresses = AddressSerializer(many=True, read_only=False)
class Meta:
model = Company
fields = '__all__'
def create(self, validated_data):
errors = {}
addresses = validated_data.pop('addresses')
if errors:
raise serializers.ValidationError(errors)
obj = Company.objects.create(**validated_data)
address_list = []
for address in addresses:
address_id = address['id']
address.pop('id', None)
address_list.append(Address.objects.create(**address))
obj.addresses.add(*address_list)
return obj
def update(self, instance, validated_data):
errors = {}
if 'addresses' in validated_data:
addresses = validated_data.pop('addresses')
else:
addresses = []
instance = super().update(instance, validated_data)
address_list = []
for address in addresses:
address_id = address['id']
if address_id != 0:
address_instance = Address.objects.get(id = address_id)
address_instance.zip_code = address['zip_code']
address_instance.street = address['street']
address_instance.external_number = address['external_number']
address_instance.internal_number = address['internal_number']
address_instance.colony_name = address['colony_name']
address_instance.colony_id = address['colony_id']
address_instance.country_id = address['country_id']
address_instance.references = address['references']
address_instance.save()
elif address_id == 0:
address.pop('id', None)
address_list.append(Address.objects.create(**address))
instance.addresses.add(*address_list)
return instance
JSON запрос на обновление компании и адресов
{
"id":25,
"category":{"id":1,"code":"002","name":"Category B"},
"category_id":1,
"name":"COMPANY SA DE CV",
"addresses":[
{
"id":1,
"country_id":2,
"colony":{"id":1,"code":"001","name":"Buenos Aires"},
"colony_id":1,
"locality_id":1,
"municipality_id":1,
"state_id":1,
"street":"",
"external_number":"",
"internal_number":"",
"zip_code":"56343",
"content_type":36,
"object_id":25
}]
}
Моя проблема в том, что если я посылаю (PATCH или PUT) в запросе colony_id
, country_id
или другие PrimaryKeyRelatedField
, я получаю ошибку, так как они не появляются в validated_data
.
Я пробовал с read_only=False
, write_only=True
в этих полях
...
File "/Users/obedramales/Sites/webegin-project/webegin/app/serializers.py", line 214, in update
address_instance.colony_id = address['colony_id']
KeyError: 'colony_id'
Это когда он был обновлен из компании, но если я обновляю только адрес напрямую, то все работает нормально.
Я хотел бы знать, как правильно поступить, пожалуйста?
Простите, если мой английский не очень хорош, это первый раз, когда я участвую здесь.
Заранее спасибо за помощь!
Вам следует использовать поля, определенные в классе Address
в models.py
...
for address in addresses:
address_id = address['id']
if address_id != 0:
address_instance = Address.objects.get(id = address_id)
try:
country = Country.objects.get(id = address['country_id'])
address_instance.country = country
address_instance.save()
except Country.DoesNotExist:
raise serializers.ValidationError({'country_id': 'invalid'})
elif address_id == 0:
address.pop('id', None)
Я попробовал изменить следующую строку кода
address_instance.country_id = address['country_id']
to
address_instance.country = address['country']
и это работает, Когда я посылаю country_id
обновляет правильный id, но необходимо, чтобы country_id
было объявлено
country = CountrySerializer(read_only = True)
country_id = serializers.PrimaryKeyRelatedField(source="country", queryset = Country.objects.all())