[Answered ]-Dynamically create Resources for multiple django models in tastypie

1👍

A Resource is ‘just’ a python class so you could simply create a base resource and inherit it with only the queryset and resource_name Meta attributes defined.

You could probably automate naming too by fiddling with the Resource class’s __new__ method or create a custom classmethod, but I’m not sure the effort will gain you much.

Registering the classes to the api can be automated in numerous ways, of which one could be:

for name, obj in inspect.getmembers(sys.modules['resources']):
    if inspect.isclass(obj):  # might want to add a few exclusions
        v1_api.register(obj())

where ‘resources’ is the name of the module containing resources, but this is kind of implicit..

1👍

create a custom base model derived from models.Model. In that model add an empty Resource class that has no object and follow the instructions in the Tastypie manuals:

https://django-tastypie.readthedocs.io/en/latest/non_orm_data_sources.html

Derive all models in your application from the newly created custom model and you have it.

In order for it to work you will need some magic. I am using a separate class that acts as a middleware between the ORM and the business layer(the model adapter), but I would say it should be possible to go without it.

What got me struggling was the way to pass the correct class in the init of the apiResource.

I ended overriding the init and registering the resource like that:

v1_api.register(BusinessClientContact.apiResource(BusinessClientContact))

Unfortunately there is no way to inject content into the meta class of the resource. You can get it to work, but as you start adding resources you will find out that the interpreter does not create separate instances of the class.Meta and the self._meta unless you do no make explicit changes in the code. The latter leaves all attempts to modify the _meta structure dynamically broken, since you work on the same _meta instance regardless of the fact that each of your resources is a different instance. The workaround I use is to inherit the apiResourceBase class in each of the model classes which I want to expose through the api and manually configure the Meta class. The code in the model class ends looking like that:

class apiResource(SystemModelBase.apiResourceBase):
    class Meta:
        resource_name = 'client_contacts'
        #more stuff here

Depending on the design you go for, you may expand the localized apiResource to enable custom views and filters.

A sample implementation of the resource class itself looks like this:

class apiResourceBase(Resource):        
    def __init__(self,f_modelClass):
        self.m_objMetaModel = f_modelClass.create_model_adapter()
        super(SystemModelBase.apiResource,self).__init__()

        #obj_create
        #obj_update
        #obj_delete_list
        #obj_delete
        #rollback

    def detail_uri_kwargs(self, bundle_or_obj):
        kwargs = {}
        if isinstance(bundle_or_obj, Bundle):
            kwargs['pk'] = bundle_or_obj.obj.id
        else:
            kwargs['pk'] = bundle_or_obj.id
        return kwargs
    def obj_get_list(self, bundle, **kwargs): 
        return self.get_object_list(bundle.request) 
    def get_object_list(self,request): 
        return self.m_objMetaModel.REST_IMPL.get_object_list(request)   
    def obj_get(self, bundle, **kwargs): 
        self.m_objMetaModel.load(kwargs['pk'],True)
        return self.m_objMetaModel.instance
    def dehydrate(self, bundle):
        return self.m_objMetaModel.REST_IMPL.dehydrate(bundle)

the actual implementation here (get_object_list and dehydrate) is out of scope but, it may help someone so I will add it:

    def get_object_list(self,request):
        l_clientID =  request.GET['client']
        l_filterDict = {}
        l_filterDict['client_account__id']=l_clientID
        return self.query_filter(l_filterDict)
    def dehydrate(self,bundle):
        l_objInstance = bundle.obj
        l_apiDict = {}
        l_apiDict['resource_uri'] = bundle.data['resource_uri']
        l_apiDict['id']           = l_objInstance.id
        l_apiDict['name']         = l_objInstance.user_account.name
        l_apiDict['email']        = l_objInstance.user_account.email
        l_apiDict['phone']        = l_objInstance.user_account.phone
        l_apiDict['additional_contacts'] = l_objInstance.user_account.additional_contacts
        l_apiDict['is_active']           = l_objInstance.user_account.user.is_active
        bundle.data = l_apiDict
        return bundl

this is a proof of concept code, in a production you can pretty much replicate the logic Tastypie uses to load models and expose those.

👤tstoev

Leave a comment