Обновление нескольких ресурсов вместе в 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 конечные точки. Эта конечная точка использует атомарную транзакцию, так что если что-то пойдет не так в бизнес-логике, ничего не будет зафиксировано.