Django REST Framework создает и обновляет вложенные объекты - PullRequest
0 голосов
/ 24 августа 2018

Я пытаюсь создать или обновить вложенный объект, если объект существует, я попытался использовать метод create_or_update, теперь создание работает нормально, но обновление не удалось и сказал, что ПК уже существует.

МойМодель:

class ContentHotel(models.Model):
    hotel_id = models.IntegerField(unique=True, blank=True, primary_key=True)
    star = models.FloatField(blank=True, null=True)

    class Meta:
        managed = False
        db_table = 'content_hotels'
        ordering = ('hotel_id',)

    def __str__(self):
        return str(self.hotel_id)


class RateHotel(models.Model):
    rate_hotel_id = models.IntegerField(blank=True, unique=True, primary_key=True)
    content_hotel = models.ForeignKey(ContentHotel, on_delete=models.CASCADE, related_name='rate_hotels')
    source_code = models.CharField(max_length=20, blank=True, null=True)

    class Meta:
        managed = False
        db_table = 'rate_hotels'
        ordering = ('rate_hotel_id',)

    def __str__(self):
        return str(self.rate_hotel_id)

Мои сериализаторы:

# To handle RateHotel object operations
class RateHotelSerializer(serializers.ModelSerializer):

    class Meta:
        model = RateHotel
        fields = __all__


# To handle nested object operations
class RateHotelSerializerTwo(serializers.ModelSerializer):

    class Meta:
        model = RateHotel
        fields = __all__
        read_only_fields = ('content_hotel',)


class ContentHotelSerializer(serializers.ModelSerializer):
    rate_hotels = RateHotelSerializerTwo(many=True)

    class Meta:
        model = ContentHotel
        fields = __all__

    def create(self, validated_data):
        rate_hotels_data = validated_data.pop('rate_hotels')
        hotel_id = validated_data.pop('hotel_id')
        content_hotel, created = ContentHotel.objects.update_or_create(hotel_id=hotel_id, defaults={**validated_data})

        for rate_hotel_data in rate_hotels_data:
            rate_hotel_id = rate_hotel_data.pop('rate_hotel_id')
            RateHotel.objects.update_or_create(rate_hotel_id=rate_hotel_id, content_hotel=content_hotel,
                                               defaults=rate_hotel_data)

        return content_hotel

    def update(self, instance, validated_data):
        rate_hotels_data = validated_data.pop('rate_hotels')
        rate_hotels = list(instance.rate_hotels.all())
        for key in validated_data:
            instance.key = validated_data.get(key, instance.key)
        instance.save()

        for rate_hotel_data in rate_hotels_data:
            rate_hotel = rate_hotels.pop(0)
            for key in rate_hotel_data:
                rate_hotel.key = rate_hotel_data.get(key, rate_hotel.key)
            rate_hotel.save()

        return instance

JSON:

# Post Request - Create:
{
    "hotel_id": -1,
    "star": null,
    "rate_hotels": [{"rate_hotel_id": -1}]
}

# Post Response:
{
    "hotel_id": -1,
    "star": null,
    "rate_hotels": [
        {
            "content_hotel": -1,
            "rate_hotel_id": -1,
            "source_code": null,
        }
    ]
}

# Post Request - Update:
{
    "hotel_id": -1,
        "star": 2,
        "rate_hotels": [{"rate_hotel_id": -1, "source_code": "-1"}]
}

{
    "hotel_id": [
        "content hotel with this hotel id already exists."
    ],
    "rate_hotels": [
        {
            "rate_hotel_id": [
                "rate hotel with this rate hotel id already exists."
            ]
        }
    ],
    "status_code": 400
}

Если я отправлю запрос на размещение для обновления вложенного объекта, контент отеля будет частьюработает корректно, но часть отеля с тарифами все еще говорит, что 'rate_hotel_id' уже существует.

Может кто-нибудь помочь мне решить эту проблему?Я не могу найти связанный источник в Интернете, спасибо заранее!

Ответы [ 2 ]

0 голосов
/ 24 августа 2018

Ваш код create выглядит хорошо, но есть некоторые более серьезные проблемы с вашим кодом update, которые вам необходимо решить, прежде чем мы сможем узнать, в чем заключается настоящая проблема.

    for key in validated_data:
        instance.key = validated_data.get(key, instance.key)
    instance.save()

Здесьвы пытаетесь перебрать значения в проверенных данных и присвоить значения этого ключа этому атрибуту в модели.Что вы на самом деле делаете, так это устанавливаете атрибут key в модели на значение каждого ключа в validated_data.Чтобы лучше проиллюстрировать проблему, я дам вам пример, который исправляет проблему:

    for key in validated_data:
        setattr(instance, key, validated_data.get(key, getattr(instance, key))
    instance.save()

Здесь, вместо того, чтобы пытаться установить instance.key, мы используем встроенный метод python setattr, чтобы установить атрибутс именем, которое совпадает со значением переменной key со значением, которое мы извлекаем из validated_data.

Я не уверен, что исправление этой проблемы решит вашу проблему.Я бы удостоверился, что вы правильно отправляете свой запрос на правильный URL с правильными значениями заголовка, чтобы получить доступ к методу обновления.Возможно, Django Rest Framework предотвращает создание за кулисами, поскольку считает, что вы отправляете запрос как операцию создания, и уже находит первичные ключи.

0 голосов
/ 24 августа 2018

Хмммм, похоже, вы могли искать вариант использования «обновить, если существует, создать», и у Django есть метод get_or_create() для выполнения того, что вы хотите, см. Документацию здесь: https://docs.djangoproject.com/en/dev/ref/models/querysets/#get-or-create

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

id = 'some identifier that is unique to the model you wish to lookup'
content_hotel, created = RateHotel.objects.get_or_create(
    rate_hotel_id=rate_hotel_id, 
    content_hotel=content_hotel, 
    defaults=rate_hotel_data
)

if created:
   # means you have created a new content hotel, redirect a success here or whatever you want to do on creation!
else:
   # content hotel just refers to the existing one, this is where you can update your Model

Я бы также посоветовал удалить hotel_id и hotel_rate_id в качестве первичных ключей:

class ContentHotel(models.Model):
        hotel_id = models.IntegerField(unique=True, blank=True,)
        star = models.FloatField(blank=True, null=True)

        class Meta:
            managed = False
            db_table = 'content_hotels'
            ordering = ('hotel_id',)

        def __str__(self):
            return str(self.hotel_id)


    class RateHotel(models.Model):
        rate_hotel_id = models.IntegerField(blank=True, unique=True,)
        content_hotel = models.ForeignKey(ContentHotel, on_delete=models.CASCADE, related_name='rate_hotels')
        source_code = models.CharField(max_length=20, blank=True, null=True)

        class Meta:
            managed = False
            db_table = 'rate_hotels'
            ordering = ('rate_hotel_id',)

        def __str__(self):
            return str(self.rate_hotel_id)

Метод update_or_create() не принимает первичный ключ, но может принимать другие уникальные поля базы данных для выполнения обновления:

Итак, update_or_create() будет выглядеть так:

obj, created = Person.objects.update_or_create(
    first_name='John', last_name='Lennon',
    defaults={'first_name': 'Bob'},
)
...