Different fields for SHOW and INDEX actions #18

Open
bmihelac opened this Issue Oct 25, 2010 · 40 comments

Comments

Projects
None yet
@bmihelac

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

This comment has been minimized.

Show comment
Hide comment
@wlt008

wlt008 Nov 14, 2010

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

wlt008 commented Nov 14, 2010

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

@onyxfish

This comment has been minimized.

Show comment
Hide comment
@onyxfish

onyxfish Dec 14, 2010

Contributor

+1 (via duplicate at #44)

Contributor

onyxfish commented Dec 14, 2010

+1 (via duplicate at #44)

@bmihelac

This comment has been minimized.

Show comment
Hide comment
@bmihelac

bmihelac Dec 14, 2010

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?

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?

@toastdriven

This comment has been minimized.

Show comment
Hide comment
@toastdriven

toastdriven Dec 16, 2010

Contributor

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.

Contributor

toastdriven commented Dec 16, 2010

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.

@onyxfish

This comment has been minimized.

Show comment
Hide comment
@onyxfish

onyxfish Jan 8, 2011

Contributor

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.

Contributor

onyxfish commented Jan 8, 2011

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.

@gstf

This comment has been minimized.

Show comment
Hide comment

gstf commented May 6, 2011

+1

@AndrewIngram

This comment has been minimized.

Show comment
Hide comment
@AndrewIngram

AndrewIngram May 6, 2011

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

@aldarund

This comment has been minimized.

Show comment
Hide comment

+1

@vhbit

This comment has been minimized.

Show comment
Hide comment

vhbit commented Jul 12, 2011

+1

@berggren

This comment has been minimized.

Show comment
Hide comment

+1

@petergx

This comment has been minimized.

Show comment
Hide comment

petergx commented Aug 22, 2011

+1

@stuartkeith

This comment has been minimized.

Show comment
Hide comment

+1

@adilatilgan

This comment has been minimized.

Show comment
Hide comment

+1

@dolph

This comment has been minimized.

Show comment
Hide comment
@dolph

dolph Oct 2, 2011

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

dolph commented Oct 2, 2011

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

@daviddul

This comment has been minimized.

Show comment
Hide comment
@daviddul

daviddul Oct 25, 2011

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?

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?

@ashwoods

This comment has been minimized.

Show comment
Hide comment
@ashwoods

ashwoods Nov 10, 2011

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

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

This comment has been minimized.

Show comment
Hide comment

daveyss commented Nov 13, 2011

+1

@ghost

This comment has been minimized.

Show comment
Hide comment

ghost commented Jan 5, 2012

+1

@pavel-v-chernykh

This comment has been minimized.

Show comment
Hide comment
@dericcrago

This comment has been minimized.

Show comment
Hide comment
@dericcrago

dericcrago Feb 9, 2012

Contributor

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
Contributor

dericcrago commented Feb 9, 2012

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

This comment has been minimized.

Show comment
Hide comment
@dericcrago

dericcrago Feb 10, 2012

Contributor

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
Contributor

dericcrago commented Feb 10, 2012

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

This comment has been minimized.

Show comment
Hide comment
@pix0r

pix0r Mar 26, 2012

+1, using fix from @dericcrago for now.

pix0r commented Mar 26, 2012

+1, using fix from @dericcrago for now.

@swiharta

This comment has been minimized.

Show comment
Hide comment

+1

@aaloy

This comment has been minimized.

Show comment
Hide comment

aaloy commented May 16, 2012

+1

@toastdriven

This comment has been minimized.

Show comment
Hide comment
@toastdriven

toastdriven Jun 14, 2012

Contributor

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

Contributor

toastdriven commented Jun 14, 2012

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

@funkybob

This comment has been minimized.

Show comment
Hide comment
@funkybob

funkybob Jun 19, 2012

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.

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.

@dhatch

This comment has been minimized.

Show comment
Hide comment
@dhatch

dhatch Jul 17, 2012

Contributor

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.

Contributor

dhatch commented Jul 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.

@funkybob

This comment has been minimized.

Show comment
Hide comment
@funkybob

funkybob Jul 17, 2012

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

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

@dhatch

This comment has been minimized.

Show comment
Hide comment
@dhatch

dhatch Jul 17, 2012

Contributor

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

Contributor

dhatch commented Jul 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

This comment has been minimized.

Show comment
Hide comment
@numan

numan Jul 19, 2012

Contributor

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

Contributor

numan commented Jul 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

@glibersat

This comment has been minimized.

Show comment
Hide comment
@glibersat

glibersat Sep 6, 2012

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

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

@djedi

This comment has been minimized.

Show comment
Hide comment
@djedi

djedi Dec 3, 2012

Going with hack provided by @dericcrago

djedi commented Dec 3, 2012

Going with hack provided by @dericcrago

@giovannic

This comment has been minimized.

Show comment
Hide comment

+1

@snanda85

This comment has been minimized.

Show comment
Hide comment
@snanda85

snanda85 Mar 13, 2013

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)

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)
@vchakoshy

This comment has been minimized.

Show comment
Hide comment

+1

@dnozay

This comment has been minimized.

Show comment
Hide comment
@dnozay

dnozay Apr 30, 2013

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

dnozay commented Apr 30, 2013

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

This comment has been minimized.

Show comment
Hide comment

surfeurX commented Aug 1, 2013

+1

@nkeilar

This comment has been minimized.

Show comment
Hide comment
@nkeilar

nkeilar Sep 29, 2013

+1 for @dnozay workaround

nkeilar commented Sep 29, 2013

+1 for @dnozay workaround

@mindcruzer

This comment has been minimized.

Show comment
Hide comment
@mindcruzer

mindcruzer Nov 24, 2013

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

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

@SeanHayes

This comment has been minimized.

Show comment
Hide comment
@SeanHayes

SeanHayes Oct 12, 2015

Member

Possibly related: #1265

Member

SeanHayes commented Oct 12, 2015

Possibly related: #1265

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