Andre Gregori
Andre Gregori

Reputation: 1200

Tastypie Authorization and ToManyField

I'm trying to implement a resource containing a ToManyField, but I'd like the objects returned by the ToManyField to be limited by the requesting user.

I've tried the following, to no avail.

from django.db import models
from my_project.apps.my_app.models import MyUser
from tastypie.authorization import Authorization
from tastypie.resources import ModelResource
from tastypie import fields

###
# MODELS
###
class TieFighter(models.Model):
    pilot = models.ForeignKey(MyUser)
    fighter_class = models.CharField(max_length=200, blank=True, null=True, default='Interceptor')
    squadron = models.ForeignKey(Squadron, related_name='tie_fighters')

class Squadron(models.Model):
    name = models.CharField(max_length=200, blank=True, null=True)

###
# AUTHORIZATION
###
class TieFighterAuthorization(Authorization):
    def read_list(self, object_list, bundle):
        return object_list.filter(pilot=bundle.request.user)

    def read_detail(self, object_list, bundle):
        return bundle.obj.pilot == bundle.request.user

###
# RESOURCES
###
class TieFighterResource(ModelResource):
    class Meta:
        authorization = TieFighterAuthorization()
        queryset = TieFighter.objects.all()
        allowed_methods = ['get']
        resource_name = 'tie_fighters'

class SquadronResource(ModelResource):
    class Meta:
        authorization = Authorization()
        queryset = Squadron.objects.all()
        allowed_methods = ['get']
        resource_name = 'squadrons'

    tie_fighters = fields.ToManyField(TieFighterResource, null=True, full=True, attribute='tie_fighters')

Notice that the pilot relation, which I'd like to filter by, only exists in TieFighter. Thus I created a special Authorization subclass for TieFighterResource, which enforces that the resource only returns tie fighters whose pilots match the requesting user. This works when I call the TieFighterResource endpoint directly. But when I call SquadronResource, the constraint is gone; all tie fighters within that squadron are listed, regardless of who the pilot/requesting user is.

Please note: I want to filter out TieFighters on each Squadron, but still return all Squadrons. That is to say, I don't want my filtering out of TieFighters to prevent a user from seeing Squadrons with an empty tie_fighter relation.

Suggestions?

Upvotes: 0

Views: 109

Answers (2)

Andre Gregori
Andre Gregori

Reputation: 1200

Here's how I resolved the issue. You can filter on relational fields in TastyPie by passing in a callable as the named argument attribute. See also: https://stackoverflow.com/a/20035610/1387495.

from tastypie.bundle import Bundle

'''
Enforce the TieFighterResource's ACL.
'''
def enforce_acl(bundle):
    res = TieFighterResource()
    new_bundle = Bundle(request=bundle.request)
    objs = res.obj_get_list(new_bundle)
    return objs

class SquadronResource(ModelResource):    
    ...        
    tie_fighters = fields.ToManyField(TieFighterResource, null=True, full=True, attribute=enforce_acl)

Ideally, this would be built into TastyPie; I think it's a fair assumption that the authorization logic of the resource you passed into your ToManyField would constrain the field's result set without any additional configuration. I will create a pull request when I have time.

Upvotes: 0

Tomasz Jakub Rup
Tomasz Jakub Rup

Reputation: 10680

In read_list filter by tie_fighters__pilot.

In read_detail fetch all fie_fighters and filter by pilot.

class SquadronAuthorization(Authorization):
    def read_list(self, object_list, bundle):
        return object_list.filter(tie_fighters__pilot=bundle.request.user)

    def read_detail(self, object_list, bundle):
        return bundle.obj.tie_fighters.all().filter(pilot=bundle.request.user).count() <> 0

Upvotes: 1

Related Questions