1

Lets say I have two models in 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')

What if I would like to add an Inventory and some properties to it from the frontend on the same page (form).

Then I would have to save the Inventory item first, then save the properties.

As I read in REST this should not be done with one resource (because this is a different resource, so now I have /inventory/:id/properties/): How to handle updates in a REST API?

What happens if something goes wrong during saving the properties? I can't rollback easily the saved Inventory item, so I end up with a half-saved object.

Also this is really hard to manage on the frontend, because if some error occurs during property saving, the frontend should not save the inventory again.

Kristof Rado
  • 713
  • 1
  • 8
  • 19

3 Answers3

1

For this scenario, you can use django transaction which will rollback your database transaction if any error occur in your transaction block. Something Like this-

from django.db import transaction

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

In the transaction block, if saving InventoryProperties gives any error then Inventory Table will be rollback.

MSI Shafik
  • 139
  • 6
  • The problem for me was not how to handle transactions, but how to do this in a RESTful way. Because as I said, usually in REST you would save this by doing a POST to 2 resources – Kristof Rado Jan 13 '22 at 08:41
0

Seems like InventoryProperties entity will never use without their Inventory relation. So, you can use JSONField Inventory.propetries instead of separate entity.

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']

This serializer would rewrite all props on every save in 1 SQL UPDATE query. As a bonus, you don't need to support InventoryProperties.order field.

Input example

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

Also, you can add method to access your propetries by 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
       

If you want to add validation of JSON you can use this approach

Yevhen Bondar
  • 4,357
  • 1
  • 11
  • 31
  • The problem with this for me is that it is really hard to maintain for a long time. I would like to have a seperate table for the properties. – Kristof Rado Jan 11 '22 at 17:14
0

The solution for me was that I created a separate endpoint where I can update nested and multiple models at the same time, while providing typical RESTful endpoints also. This endpoint uses atomic transaction so that if anything goes wrong in the business logic, nothing gets committed.

Kristof Rado
  • 713
  • 1
  • 8
  • 19