Search code examples
djangotastypiedjango-mptt

Serializing django-mptt trees in Tastypie


How do I serialize django-mptt trees in Tastypie?

I want to use django-mptt's cache_tree_children(). I've tried applying in different Tastypie hooks, but it throws an error.


Solution

  • Without the cache_tree_children method you could probably have your children serialized by simply hooking up a ToManyField with full=True pointing at the children property:

    class MenuResource(ModelResource):
    
        children = fields.ToManyField('self', 'children', null=True, full=True)
        parent = fields.ToOneField('self', 'parent', null=True)
    
        class Meta:
            queryset = Menu.objects.all()
    

    To implement the cache_tree_children function you could write your own ToManyField subclass that overrides the standard dehydrate function. Please note, that I only tested this solution very superficially:

    def dehydrate(self, bundle):
        if not bundle.obj or not bundle.obj.pk:
        if not self.null:
            raise ApiFieldError("The model '%r' does not have a primary key and can not be used in a ToMany context." % bundle.obj)
    
            return []
    
        the_m2ms = None
        previous_obj = bundle.obj
        attr = self.attribute
    
        if isinstance(self.attribute, basestring):
            attrs = self.attribute.split('__')
            the_m2ms = bundle.obj
    
            for attr in attrs:
                previous_obj = the_m2ms
                try:
                    the_m2ms = getattr(the_m2ms, attr, None)
                except ObjectDoesNotExist:
                    the_m2ms = None
    
                if not the_m2ms:
                    break
    
        elif callable(self.attribute):
            the_m2ms = self.attribute(bundle)
    
        if not the_m2ms:
            if not self.null:
                raise ApiFieldError("The model '%r' has an empty attribute '%s' and doesn't allow a null value." % (previous_obj, attr))
    
            return []
    
        self.m2m_resources = []
        m2m_dehydrated = []
    
        # There goes your ``cache_tree_children``
        for m2m in cache_tree_children(the_m2ms.all()):
            m2m_resource = self.get_related_resource(m2m)
            m2m_bundle = Bundle(obj=m2m, request=bundle.request)
            self.m2m_resources.append(m2m_resource)
            m2m_dehydrated.append(self.dehydrate_related(m2m_bundle, m2m_resource))
    
        return m2m_dehydrated
    

    One of the main advantages of this method is that you don't have to care about detail / list view constraints / differences no more. You even can parameterize this aspect of your resource further down until you got some kind of default behavior that fits your needs. Field-based, that is. Which I think is cool.