Skip to content
This repository

Different fields for SHOW and INDEX actions #18

Open
bmihelac opened this Issue October 24, 2010 · 39 comments
Bojan Mihelac

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.

thanks,
Bojan

wlt008

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

Christopher Groskopf

+1 (via duplicate at #44)

Bojan Mihelac

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:

    Meta:
    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?

Daniel Lindsley
Owner

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.

Christopher Groskopf

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:

&shape=[full|simple|none]

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.

Gustaf Lindqvist
gstf commented May 06, 2011

+1

Andrew Ingram

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.

Dmitry Molotkov

+1

Valerii Hiora
vhbit commented July 12, 2011

+1

Johan Berggren

+1

petergx

+1

Dolph Mathews

+1

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.

David Dul

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.

example:

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?

Ashley Camba

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',
                                  'projects',full=True)
       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
daveyss

+1

Deleted user

The content you are editing has changed. Reload the page and try again.

+1

Sending Request…

Attach images by dragging & dropping or selecting them. Octocat-spinner-32 Uploading your images… Unfortunately, we don't support that file type. Try again with a PNG, GIF, or JPG. Yowza, that's a big file. Try again with an image file smaller than 10MB. This browser doesn't support image attachments. We recommend updating to the latest Internet Explorer, Google Chrome, or Firefox. Something went really wrong, and we can't process that image. Try again.

Deric Crago

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
Deric Crago

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
Mike Matz
pix0r commented March 26, 2012

+1, using fix from @dericcrago for now.

swiharta

+1

Antoni Aloy
aaloy commented May 16, 2012

+1

Daniel Lindsley
Owner

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

Curtis Maloney

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.

David Hatch
dhatch commented July 17, 2012

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.

Curtis Maloney

[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...

David Hatch
dhatch commented July 17, 2012

@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.

Numan S
Collaborator
numan commented July 19, 2012

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

Guillaume Libersat

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

Dustin Davis

Going with hack provided by @dericcrago

Sandeep Nanda

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)
vahid chakoshy

+1

Damien Nozay

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
amine

+1

Nathan Keilar

+1 for @dnozay workaround

Sean Stewart

+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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.