Non-PK Identifiers for Tastypie URLs

I've been using Tastypie at work for a few weeks now, and while it's been generally great, I've had my share of frustrations with it. The most recent one involved a cookbook recipe that didn't quite work. Following the recipe, I got Tastypie to resolve my URLs, but I couldn't for the life of me get it to return a non-blank resource_uri. Because I am using Tastypie with Backbone, the frontend code relies on these URIs.

If you are using Tastypie 0.9.11, you will have to override get_resource_uri. The trick is to make sure you plug in all of the following into Django's urlresolver resolve:

  1. api_name - The name you used for Tastypie's Api class. Put it in Meta and grab it as self._meta.api_name. This couples the API with the Resource. Oh well.
  2. resource_name - Grab it as self._meta.resource_name
  3. The new key that you defined as a replacement for pk. In the cookbook example it is called username.

ModelResource (Tastypie v0.9.11):

class CompanyResource(ModelResource):
    class Meta:
        resource_name = 'companies'
        queryset = Company.objects.all()
    def override_urls(self):
        return [
            url(r"^(?P<resource_name>%s)/(?P<domain>[\w\d_.-]+)/$" %
                        self._meta.resource_name,
                    self.wrap_view('dispatch_detail'),
                    name="api_dispatch_detail"),
        ]
    def get_resource_uri(self, bundle_or_obj):
        kwargs = {
            'resource_name': self._meta.resource_name,
            'api_name': self._meta.api_name
        }
        if isinstance(bundle_or_obj, Bundle):
            kwargs['domain'] = bundle_or_obj.obj.domain
        else:
            kwargs['domain'] = bundle_or_obj.domain
        return self._build_reverse_url("api_dispatch_detail", kwargs=kwargs)

If you are using a later version of Tastypie, you can simplify it further. I started off using Tastypie master, but had to go back to v0.9.11 because Validation was broken.

ModelResource (Tastypie master):

class CompanyResource(ModelResource):
    class Meta:
        resource_name = 'companies'
        api_name = 'v1'
        detail_uri_name = 'domain'
        queryset = Company.objects.all()
    def prepend_urls(self):
        return [
            url(r"^(?P<resource_name>%s)/(?P<domain>[\w\d_.-]+)/$" %
                    self._meta.resource_name,
                self.wrap_view('dispatch_detail'),
                name="api_dispatch_detail"),
        ]

Resource (Tastypie master):

class CompanyResource(Resource):
    class Meta:
        resource_name = 'companies'
        api_name = 'v1'
    def obj_get(self, request=None, **kwargs):
        return Company.objects.get(domain=kwargs['domain'])
    def prepend_urls(self):
        return [
            url(r"^(?P<resource_name>%s)/(?P<domain>[\w\d_.-]+)/$" %
                    self._meta.resource_name,
                self.wrap_view('dispatch_detail'),
                name="api_dispatch_detail"),
        ]
    # This (along with _meta.api_name and _meta.resource_name) will
    # need to be plugged into the above URL
    def detail_uri_kwargs(self, bundle_or_obj):
        if isinstance(bundle_or_obj, Bundle):
            return { 'domain': bundle_or_obj.obj.domain }
        else:
            return { 'domain': bundle_or_obj.domain }

I created a Tastypie issue. Hope this saved you some frustration.

Cheers!