Question

I want to create a view using tastypie to expose certain objects of the same type, but with the following two three twists:

  1. I need to get the objects using three separate queries;
  2. I need to add a field which doesn't exist in the underlying model, and the value of that field depends on which of the queries it came from; and
  3. The data will be per-user (so I need to hook in to one of the methods that gets a request).

I'm not clear on how to hook into the tastypie lifecycle to accomplish this. The recommended way for adding a "virtual" field is in the dehydrate method, which only knows about the bundle it's operating on.

Even worse, there's no official way to join querysets.

My problem would go away if I could get tastypie to accept something other than a queryset. In that case I could pass it a list of subclasses of my object, with the additional field added.

I'm open to any other sensible solution.

Edit: Added twist 3 - per-user data.

Was it helpful?

Solution 3

OK, so this is my solution. Code is below.

Points to note:

  1. The work is basically all done in obj_get_list. That's where I run my queries, having access to the request.
  2. I can return a list from obj_get_list.
  3. I would probably have to override all of the other obj_* methods corresponding to the other operations (like obj_get, obj_create, etc) if I wanted them to be available.
  4. Because I don't have a queryset in Meta, I need to provide an object_class to tell tastypie's introspection what fields to offer.
  5. To expose my "virtual" attribute (which I create in obj_get_list), I need to add a field declaration for it.
  6. I've commented out the filters and authorisation limits because I don't need them right now. I'd need to implement them myself if I needed them.

Code:

from tastypie.resources import ModelResource
from tastypie import fields
from models import *
import logging

logger = logging.getLogger(__name__)


class CompanyResource(ModelResource):
    role = fields.CharField(attribute='role')


    class Meta:
        allowed_methods = ['get']
        resource_name = 'companies'
        object_class = CompanyUK
        # should probably have some sort of authentication here quite soon


    #filters does nothing. If it matters, hook them up
    def obj_get_list(self, request=None, **kwargs):
#         filters = {}

#         if hasattr(request, 'GET'):
#             # Grab a mutable copy.
#             filters = request.GET.copy()

#         # Update with the provided kwargs.
#         filters.update(kwargs)
#         applicable_filters = self.build_filters(filters=filters)

        try:
            #base_object_list = self.get_object_list(request).filter(**applicable_filters)
            def add_role(role):
                def add_role_company(link):
                    company = link.company
                    company.role = role
                    return company
                return add_role_company

            director_of = map(add_role('director'), DirectorsIndividual.objects.filter(individual__user=request.user))
            member_of   = map(add_role('member'),   MembersIndividual.objects.filter(individual__user=request.user))
            manager_of  = map(add_role('manager'),  CompanyManager.objects.filter(user=request.user))

            base_object_list = director_of + member_of + manager_of
            return base_object_list #self.apply_authorization_limits(request, base_object_list)
        except ValueError, e:
            raise BadRequest("Invalid resource lookup data provided (mismatched type).")

OTHER TIPS

In the last version you should override the dehydrate method, e.g.

def dehydrate(self, bundle):
    bundle.data['full_name'] = bundle.obj.get_full_name()
    return bundle

Stumbled over similar problem here. In my case, items in the list could be "checked" by user.

  • When an item is retrieved by AJAX, its checked status is returned with the resource as a normal field.
  • When an item is saved to the server, "checked" field from the resource is stored in user's session.

First I thought hydrate() and dehydrate() methods to be the best match for this job, but turned out there are problems with accessing request object in these. So I went with alter_data_to_serialize() and obj_update(). I think there's no need to override obj_create(), since item can't be checked when it's first created, I think.

Here is the code, but note that it hasn't been properly tested yet.

class ItemResource(ModelResource):
    def get_object_checked_status(self, obj, request):
        if hasattr(request, 'session'):
            session = request.session
            session_data = session.get(get_item_session_key(obj), dict())
            return session_data.get('checked', False)
        return False

    def save_object_checked_status(self, obj, data, request):
        if hasattr(request, 'session'):
            session_key = get_item_session_key(obj)
            session_data = request.session.get(session_key, dict())
            session_data['checked'] = data.pop('checked', False)
            request.session[session_key] = session_data

    # Overridden methods
    def alter_detail_data_to_serialize(self, request, bundle):
        # object > resource
        bundle.data['checked'] = \
            self.get_object_checked_status(bundle.obj, request)
        return bundle

    def alter_list_data_to_serialize(self, request, to_be_serialized):
        # objects > resource
        for bundle in to_be_serialized['objects']:
            bundle.data['checked'] = \
                self.get_object_checked_status(bundle.obj, request)
        return to_be_serialized

    def obj_update(self, bundle, request=None, **kwargs):
        # resource > object
        save_object_checked_status(bundle.obj, bundle.data, request)
        return super(ItemResource, self)\
            .obj_update(bundle, request, **kwargs)

def get_item_session_key(obj): return 'item-%s' % obj.id

You can do something like this (not tested):

def alter_list_data_to_serialize(self, request, data):

    for index, row in enumerate(data['objects']):

        foo = Foo.objects.filter(baz=row.data['foo']).values()
        bar = Bar.objects.all().values()

        data['objects'][index].data['virtual_field'] = bar

    return data
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top