Different fields for SHOW and INDEX actions #18

bmihelac opened this Issue Oct 25, 2010 · 40 comments

Hi all, is it possible to display different fields for resource depending of action.

The reason I want it is to avoid burdening of index action with all has_many fields that resource includes.

For example in Author index view I would like to return only his name, but for show action I would like to include all books resources.



I am deeply hoping for this feature: "detail field" and "list field" can be separated


+1 (via duplicate at #44)


It seems to me that there are two things which could make easier to get subset of data in index actions:

  1. Make full_dehydrate use different fields for index and get methods. Maybe something like:

    index_exclude_fields = ['some_m2m_field']

This would allow us to save some db query.

  1. Pass additional 'method' parameter to dehydrate. This would allow customization of bundle like to skip some fields which are not needed in index and we want to spare some bandwidth.

I didnt evaluate 1. would have impact on caching.
What do you think?


Also duped in #48, but each issue has worthwhile aspects.

I wanted to push off this feature if I could, but I'm way outgunned. It'll need to be there for better file support anyhow, so it has to get addressed. Will target 1.0 on this.


After examining my use-case for this a little more, I'd like to suggest it be implemented as something more flexible than just show/list views. What I really need to be able to do is take a querystring flag like this:


and have the output reflect the user's selected level-of-detail. I looked for a way to hack this in, but as the fields are included/excluded when the ModelResource class is created, I could not come up with any way to alter what was available later in the response cycle. Moreover, the request object is not available at the steps in the process where I would want to make such a determination.




I'll mention this thought here first as it seems relevant to this issue, but it might need to be spun off into its own.

If a mechanism for showing different fields is developed, it would be useful if it could be integrated with authentication too. Being able to expose more/less fields depends on the user's permissions would be very powerful. If this is already possible, I haven't been able to find any mention of it.







I'd expect to be able to control the fields displayed (using either fields or excludes) on a per method (POST, PUT, GET) in addition to list/detail.


Here's a workaround, see the example below: you don't exclude anything from the original model's resource model (UserResource in the example) so it'd be full in it's index view. You have to go into the dehydrate method of the modelresource that includes your sub model (the BlogPostResource includes the author in it) and just delete the elements of the bundle.


class BlogPostResource(ModelResource):
     author = fields.ForeignKey(UserResource, 'author', full=True)
     class Meta:

def dehydrate(self, bundle):
         del bundle.data['author'].data['field_you_dont_wanna_show_here']
         del bundle.data['author'].data['field_you_dont_wanna_show_here']
         return bundle

So when you want to list the users, you still get all the fields but when you list blogposts, you can for example just get the author's first name and last name.

What do you think?


Example of a very, very dirty workaround: Doesnt show project fields in list_view, but it does in detail_view, without having to access the resource itself, getting the url itself without hardcoding it should be posible but haven't had time to check it out:

class CompanyResource(ModelResource):
       Tastypie resource for Company
       projects = fields.ToManyField('api.resources.ProjectResource',
       class Meta:
           queryset = Company.objects.all()
           resource_name = 'companies'

       def dehydrate(self, bundle):
           if bundle.request.path == "/api/v1/companies/":
               del bundle.data['projects']
           return bundle





I'll also hop on board the it'd be nice to have train, but what @ashwoods pointed out is enough to get me by. I've modified it slightly to be a bit more dynamic.

    def dehydrate(self, bundle):
        if self.get_resource_uri(bundle) == bundle.request.path:
            print "Detail"

        if self.get_resource_uri(bundle) != bundle.request.path:
            print "Not Detail - Could be list or reverse relationship."

        return bundle

So I gave this some more thought and came up with something that comes pretty close to letting me do what I think @bmihelac was looking for.

Using the example @ashwoods provided, suppose we only wanted to display the projects field if it was a detail response:

class CompanyResource(ModelResource):
    Tastypie resource for Company

    class Meta:
        queryset = Company.objects.all()
        resource_name = 'companies'
        additional_detail_fields = {'projects': fields.ToManyField('api.resources.ProjectResource', 'projects',full=True)}

    def dehydrate(self, bundle):
        # detect if detail
        if self.get_resource_uri(bundle) == bundle.request.path:
            # detail detected, include additional fields
            bundle = self.detail_dehydrate(bundle)

        return bundle

    # detail_dehydrate is basically full_dehydrate
    # except we'll loop over the additional_detail_fields
    # and we won't want to do the dehydrate(bundle) at the end
    def detail_dehydrate(self, bundle):
        Given a bundle with an object instance, extract the information from it
        to populate the resource.
        # Dehydrate each field.
        # loop over additional_detail_fields instead
        #for field_name, field_object in self.fields.items():
        for field_name, field_object in self._meta.additional_detail_fields.items():
            # A touch leaky but it makes URI resolution work.
            if getattr(field_object, 'dehydrated_type', None) == 'related':
                field_object.api_name = self._meta.api_name
                field_object.resource_name = self._meta.resource_name

            bundle.data[field_name] = field_object.dehydrate(bundle)

            # Check for an optional method to do further dehydration.
            method = getattr(self, "dehydrate_%s" % field_name, None)

            if method:
                bundle.data[field_name] = method(bundle)

        # dehydrating the bundle will create an infinite loop
        #bundle = self.dehydrate(bundle)
        return bundle

+1, using fix from @dericcrago for now.




Partial implementation in #526, not sure I'm sold on all of it & it lacks tests/docs.


Just saw this ticket... and also like the 'shape' approach mentioned by onyxfish above...

Was thinking my solution in #526 was a little limited, in case people wanted different 'shapes' in other cases...

to the suggestions to remove fields after dehydrating ... my whole reason is to avoid computing the values in the first place.

However, the idea for detail_dehydrate hook to allow conditionally adding more details, I like.


Looks like two possible implementations both including tests and docs are available. I wrote one in #569 and #538 also performs similar functionality (#538 allowing a bit more flexibility since use_in may be a callable). My implementation adds meta attributes to control this functionality (which is consistent with the current fields attribute) while #538 adds an attribute to fields. Both seem valid, just a design decision as to which way to go. Adding to the meta seems consistent to me and is easier to use given that some fields can be automatically generated and modifying their initialization parameters may not be possible. Another alternative would be to combine both pull requests and allow for the use_in parameter to be automatically set based on the meta attribute, however this seems to add more complexity to the API than is necessary. Thanks to @issackelly for pointing out related pull requests to me.


[chiming in as I was the original cause behind #538, it being a cleanup of my #526]
Makes a lot of sense... the Meta approach would, indeed, gel with the excludes list for ModelResource, etc...

As I said in another ticket, a "simple" solution such as this would be, IMHO, sufficient for a 1.0 release... with a more complex solution like "client selectable 'shapes' " being perhaps desirable for a later release...


@funkybob Agreed, certainly the more complex solution would be helpful from a client side, but it would be nice to have this functionality included asap so it can begin being used before 1.0 is released.


Actually using the PR in a production application, I really like the flexibility of the callback provided by #538. I have a couple of use cases where I have to hide a resource at runtime based on permission.

This wouldn't be possible for me using #569


Hi, any news? That PR makes life so easier, thanks!


Going with hack provided by @dericcrago


A simple workaround that I am using is to override the get_detail & get_listmethods to edit such fields.

This saves the overhead of actually fetching the data for the field and then deleting it from the bundle, but i am not sure if this method is threadsafe, as it looks like that the Resource objects are not created on every api call.

It'll be great if somebody can comment on this.

Here is the code:

class ArticleResource(BaseModelResource):
    owner = fields.ToOneField(UserResource, 'owner', full=True)

    class Meta:
        resource_name = "articles"

    def get_list(self, request, **kwargs):
        self.fields.pop("comments", None)
        return super(ArticleResource, self).get_list(request, **kwargs)

    def get_detail(self, request, **kwargs):
        self.fields["comments"] = fields.ToManyField(CommentResource, 'comments', full=True)
        return super(ArticleResource, self).get_detail(request, **kwargs)



another workaround is to have different resources for detail and list views:

from tastypie.resources import ModelResource
from django.contrib.auth.models import User

# detail will show everything except password
class UserResourceDetail(ModelResource):
    class Meta:
        queryset = User.objects.all()
    excludes = ('password',)
    resource_name = 'user'

# list will only show username & date_joined (and exclude password)
class UserResource(UserResourceDetail):
    class Meta(UserResourceDetail.Meta):
        fields = ('username', 'date_joined')
    get_detail = UserResourceDetail().get_detail

# ... register & use UserResource



+1 for @dnozay workaround


+1 @dnozay, awesome

Note that if you want get_resource_uri to work correctly with the user detail view, you'll need to add the following after UserResource is defined.

UserResourceDetail.get_resource_uri = UserResource().get_resource_uri

Otherwise resource_uri will be empty in all detail responses.

django-tastypie member

Possibly related: #1265

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment