Обновление нескольких ресурсов вместе в Django DRF

Допустим, у меня есть две модели в Django:

class Inventory(models.Model):
    created_at = models.DateTimeField(auto_now_add=True)
    added_by = models.ForeignKey(User, on_delete=models.SET("anonymous"),
                                 blank=True, null=True)
    name = models.CharField(max_length=100, unique=True)
    nickname = models.CharField(max_length=100, blank=True, null=True)
class InventoryProperties(models.Model):
    key = models.CharField(max_length=100)
    value = models.CharField(max_length=100)
    order = models.IntegerField()
    item = models.ForeignKey(Inventory, on_delete=models.CASCADE, related_name='properties')

Что если я хочу добавить инвентарь и некоторые свойства к нему из фронтенда на той же странице (форма).

Тогда мне придется сначала сохранить элемент инвентаря, а затем сохранить свойства.

Как я прочитал в REST, это не должно делаться с одним ресурсом (потому что это другой ресурс, так что теперь у меня есть /inventory/:id/properties/): Как обрабатывать обновления в REST API?

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

Кроме того, этим очень трудно управлять на фронтенде, потому что если во время сохранения свойств произошла какая-то ошибка, фронтенд не должен сохранять инвентарь снова.

Похоже, что сущность InventoryProperties никогда не будет использоваться без своего отношения Inventory. Поэтому вы можете использовать JSONField Inventory.propetries вместо отдельной сущности.

class Inventory(models.Model):
    props = models.JSONField(default=list)
    name = models.CharField(max_length=100, unique=True)


class InventorySerializer(serializers.ModelSerializer):
    class Meta:
        model = Inventory
        fields = ['name', 'props']

Этот сериализатор будет переписывать все реквизиты при каждом сохранении за 1 SQL UPDATE запрос. В качестве бонуса, вам не нужно поддерживать InventoryProperties.order поле.

Пример ввода

{
   "name": "inv1",
   "props": [
      {"key": "size", "value": 30},
      {"key": "weight", "value": 16},
   ]
}

Также вы можете добавить метод для доступа к вашим свойствам key

class Inventory(models.Model):
    def get_prop(self, key: str):
        for prop in self.props:
            if prop["key"] == key:
                return prop["value"]
        return None
       

Если вы хотите добавить проверку JSON, вы можете использовать это подход

Для этого сценария вы можете использовать транзакцию django, которая откатит транзакцию базы данных, если в блоке транзакции произойдет какая-либо ошибка. Что-то вроде этого-

from django.db import transaction

with transaction.atomic():
    Inventory.objects.create(**kwargs)
    InventoryProperties.objects.create(**kwargs)

В блоке транзакции, если сохранение InventoryProperties даст какую-либо ошибку, то таблица Inventory будет откачена.

Решением для меня стало создание отдельной конечной точки, где я могу обновлять вложенные и несколько моделей одновременно, предоставляя при этом типичные RESTful конечные точки. Эта конечная точка использует атомарную транзакцию, так что если что-то пойдет не так в бизнес-логике, ничего не будет зафиксировано.

Вернуться на верх