Search code examples
djangoresttastypie

Proper way to create more than one object with one POST request using Tastypie and Django


Let's say I have two Django models, ModelA and ModelB. The creation of an object of ModelA should trigger in some cases the creation of an object of ModelB. At the moment, without using Tastypie, I have the following view that controls the creation of A-objects:

def new_A_created_view(request):
    post_data = json.loads(request.body)
    ... parse post arguments ...

    a = ModelA.objects.create(...)
    if conditions_are_met(...):
       b = ModelB.objects.create(...)

Now I want to transition to a REST Api, and using TastyPie I have the following resource:

class ModelAResource(ModelResource):

    class Meta:
        queryset = ModelA.objects.all()
        authorization = Authorization()
        ...

    def hydrate(self, bundle):
        if bundle.request.META['REQUEST_METHOD'] == 'POST':
           ...compute objects attributes from received post data ...

I am aware of the following options to create the corresponding B object:

  1. Override save or post_save method. Override save method or add a post_save signal in the ModelA so the creation of the object B follows immediately the act of saving A. Problems I see in this approach:

    • The logic of creating the object ModelB could depend not on attributes present in A, but rather in parameters included in the POST request.
    • I'm losing the nice 'controller' logic in the views.py in favor of a more obscure logic inside the model.
  2. Override Resource.obj_create in ModelAResource so it creates a ModelA object and a ModelB object (if needed) based on the resource POST information. In this case, I feel as well that the creation of objects is being obscured in some way.

Any solution that I am missing? What would be the best way to do it?


Solution

  • Finally I prepended a url in the resource class, overriding the entrance point to the resource and directing it to a custom function of the class that performs all the logic needed. I deleted any hydrating from the resource as well.

    class ModelAResource(ModelResource):
    
        class Meta:
            queryset = ModelAResource.objects.all()
            authentication = MyOwnAuthentication()
            resource_name = 'modelAResource'
            allowed_methods = ['post']
    
        def prepend_urls(self):
            return [url(r"^(?P<resource_name>%s)%s$" %
                    (self._meta.resource_name, trailing_slash()),
                    self.wrap_view('process_resource'), name="process_resource")]
    
        def process_resource(self, request, **kwargs):
            self.method_check(request, allowed=['post'])
            self.is_authenticated(request)
    
            data = self.deserialize(request, request.body)
    
            param1 = data.get('param1name', '')
            param2 = data.get('param2name', '')
    
            ModelA.objects.create(...)
            if it_should_be_done(param1, param2):
                ModelB.objects.create(...)
    

    Of course this has its caveats as I have overridden the default url to access the API, but in my case that's not problematic as the API only exposes one operation (creation) on the resource. Anyway, the prepended url could be anything.