Django Tastypie - Inserting parent and child objects in a single atomic transaction

39 Views Asked by At

I am trying to implement REST API using PostgreSQL, Tastypie and Django. I managed to implement [GET] API endpoint, but I'm having troubles with implementing [POST] API operation. The cause of my troubles is the fact that I have a table "parent" which can have many child objects defined in Django. The definition of models is below:

class Parent(models.Model):
    title = models.CharField(max_length=200, null=False, blank=False, unique=True)
    owner = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
    is_private = models.BooleanField(null=False, blank=False, default=False)
    class Meta:
        db_table = 'test_parent'

class Child(models.Model):
    title = models.CharField(max_length=200, null=False, blank=False)
    parent = models.ForeignKey(Parent, on_delete=models.CASCADE, null=False, blank=False, related_name='components')
    is_mandatory = models.BooleanField(null=False, blank=False, default=False)
    is_visible_in_table = models.BooleanField(null=False, blank=False, default=False)
    is_visible_in_form = models.BooleanField(null=False, blank=False, default=False)
    options = models.JSONField(null=True, blank=False)
    note = models.TextField(null=True, blank=True)

    class Meta:
        db_table = 'test_child'

This is the definition of Tastypie resources:

class ChildResource(ModelResource):
    parent = fields.ToOneField('app.api.resources.test.ParentResource', attribute='parent')

    class Meta:
        list_allowed_methods = ('get', 'post',)
        detail_allowed_methods = ('get', 'post', 'patch', 'delete',)
        authorization = AnonymousCanPostAuthorization()
        authentication = MultiAuthentication(
            ApiKeyAuthentication(),
            CookieBasicAuthentication(),
            Authentication(),
        )
        resource_name = 'test/child'
        queryset = Child.objects.prefetch_related('component').filter(is_active=True)
        always_return_data = True

        filtering = {
            "id": ('exact',),
            "components": ALL_WITH_RELATIONS,
        }

class ParentResource(ModelResource):
    owner = fields.ToOneField(
        UserResource,
        attribute='owner',
        full=True,
        null=True
    )
    components = fields.ToManyField(
        ChildResource,
        attribute='components',
        full=True,
        null=True
    )

    class Meta:
        resource_name = 'test/parent'
        queryset = Parent.objects.prefetch_related('components').filter(is_active=True)
        always_return_data = True
        list_allowed_methods = ('get', 'post',)
        detail_allowed_methods = ('get', 'post', 'patch', 'delete',)
        authorization = AnonymousCanPostAuthorization()
        authentication = MultiAuthentication(
            ApiKeyAuthentication(),
            CookieBasicAuthentication(),
            Authentication(),
        )

        filtering = {
            "id": ('exact',),
        }

As said, [GET] is working fine and I get the parent with nested component within 'components' attribute which is what I want.

With the implementation I'm having several issues:

  1. I cannot make an insert without specifying parent attribute (resource_uri in fact). But I don't know this at the moment of inserting since it will be assigned upon inserting parent object. If I try to insert without that attribute, or with null value or with empty string I get an error, but If I add any valid resource URI to any other parent object, the insert succeeds and child objects get the correct IDs after insert (not the one I defined in JSON). What is the correct way of handling this issue of specifying parent resource URI?
  2. In case of an error during executing insert statements, I get partial insert. For instance, in child objects, a parent URI was not provided and I got an exception. But the parent was inserted. This is not how I would like it to be implemented. I would like to have an atomic transaction where everything (parent and child objects) or nothing is inserted. How to implement atomic transaction with Tastypie and Django?

Below is the JSON object I'm sending to the API endpoint using [POST] operation.

{
    "title": "Parent object",
    "owner": "/api/v1/user/2",
    "is_private": true,
    "components": [{
        "is_mandatory": false,
        "is_visible_in_form": true,
        "is_visible_in_table": false,
        "note": null,
        "parent": "",
        "options": null,
        "title": "Child element 1"
    }, {
        "is_mandatory": false,
        "is_visible_in_form": true,
        "is_visible_in_table": false,
        "note": null,
        "parent": "",
        "options": null,
        "title": "Child element 2"
    }]
}

I tried to make different inserts with parent resource URI with values null and empty string. this was unsuccessful, but when I used existing resource URI that was successful and correct. Child resources were related to the newly added parent object?!

Regarding atomic transactions I am not sure how to implement this. I know that Django has some level of support for atomic transactions, but not sure how to integrate this into tastypie code.

0

There are 0 best solutions below