Search code examples
pythondjangotastypie

Extending tastypie API with related objects


I'm new to Django, Tastypie and asking questions here.

I've got a Django application with an API using Tastypie. If I make a GET request to /api/v1/ou/33/, my API returns the object with the id==33, which is ok.

{
  "child_ou_uri": "/api/v1/ou/33/child_ou/",
  "displayname": "Mother",
  "id": 33,
  "inherit": true,
  "name": "Mother",
  "resource_uri": "/api/v1/ou/33/"
}

The thing is, I'm trying to extend the API so that it returns related objects via the child_ou_uri URI from the above object. The children are the same type of objects as their parents. The model has an attribute parent_id pointing to the pk of its parent.

My OuResource looks like this:

class OuResource(ModelResource):

    class Meta:
        queryset = OU.objects.all()
        resource_name = 'ou'
        list_allowed_methods = ['get']
        detail_allowed_methods = ['get']
        filtering = {
            'name': ['icontains'],
        }

        authentication = SessionAuthentication()
        authorization = OperatorLocationAuthorization()

    def get_child_ou(self, request, **kwargs):
        self.method_check(request, ['get', ])

        ous = OuResource().get_list(request, parent_id=kwargs['pk'])

        return ous

    def prepend_urls(self):

        return [
            url(r'^(?P<resource_name>%s)/(?P<pk>\w[\w/-]*)/child_ou%s$' % (self._meta.resource_name, '/'),
            self.wrap_view('get_child_ou'),
            name='api_get_child_ou')
        ]

    def dehydrate(self, bundle):
        kwargs = dict(api_name='v1', resource_name=self._meta.resource_name, pk=bundle.data['id'])

        bundle.data['child_ou_uri'] = reverse('api_get_child_ou', kwargs=kwargs)

        return bundle

When I navigate to /api/v1/ou/33/child_ou/ I'd like to get the list of child objects which have their attribute parent_id set to 33, but instead I get ALL of my objects without any filtering at all, equivalent to me navigating to /api/v1/ou/.

{
  "meta": {
    "limit": 20,
    "next": "/api/v1/ou/?offset=20&limit=20&format=json",
    "offset": 0,
    "previous": null,
    "total_count": 29
  },
  "objects": [
    {
      "child_ou_uri": "/api/v1/ou/33/child_ou/",
      "displayname": "Mother",
      "id": 33,
      "inherit": true,
      "name": "Mother",
      "resource_uri": "/api/v1/ou/33/"
    },
    {
      "child_ou_uri": "/api/v1/ou/57/child_ou/",
      "displayname": "Mothers 1st child",
      "id": 57,
      "inherit": true,
      "name": "Child 1",
      "resource_uri": "/api/v1/ou/57/"
    },
    {
      "child_ou_uri": "/api/v1/ou/58/child_ou/",
      "displayname": "Mothers 2nd child",
      "id": 58,
      "inherit": true,
      "name": "Child 2",
      "resource_uri": "/api/v1/ou/58/"
    }
  ]
}

What am I missing here?

[SOLUTION]

Gareth's answer put me on the right track. I altered my OuResource to look like below. This allows me to navigate to a url like /api/v1/ou/33/child_ous/ which returns a custom json of the child objects.

class OuResource(ModelResource):
    class Meta:
        queryset = OU.objects.all()
        resource_name = 'ou'
        list_allowed_methods = ['get']
        detail_allowed_methods = ['get']
        filtering = {
            'name': ['icontains'],
        }

        authentication = SessionAuthentication()
        authorization = OperatorLocationAuthorization()

    def prepend_urls(self):
        return [
            url(r"^(?P<resource_name>%s)/(?P<ou_id>\d+)/child_ous%s$" % (self._meta.resource_name, trailing_slash()), self.wrap_view('child_ous'), name="api_child_ous"),
        ]

    def child_ous(self, request, **kwargs):
        self.method_check(request, allowed=['get'])
        self.is_authenticated(request)
        self.throttle_check(request)

        ous = list(OU.objects.filter(parent_id=kwargs['ou_id']))

        data = []
        for x in ous:
            data.append({
                'id' : x.id,
                'name' : x.name,
                'parent_id' : x.parent_id
            })

        return JsonResponse(data, safe=False)

Solution

  • First, check out the tastypie documentation on creating a search.

    It'd be easier to do this without nesting, e.g. /api/v1/ou_related/?to=58, but nesting may be desireable for its expressiveness.

    For nested search with pagification, look at creating another resource, OuSearchResource. That resource would override authorized_read_list (and maybe get_list) to pass along the necessary details.