Question

Code that worked in api.py With Python 2.6, Django 1.4.1, Tastypie 0.9.11 (Running on 2.6.32-48-generic-pae #110-Ubuntu SMP)

class DriveResource(CommonModelResource):
    driver = fields.ForeignKey(DriverResource, 'driver')
    bus = fields.ForeignKey(BusResource, 'bus')
    route = fields.ForeignKey(RouteResource, 'route')

    class Meta(CommonModelResource.Meta):
        queryset = Drive.objects.all()
        allowed_methods = ['get', 'put']
        excludes = ('bus_location',)
        filtering = {
            'updated': ('gt', 'gte', 'lt', 'lte', 'exact'),
            'id': ('exact',),
        }

    def get_object_list(self, request):
        whole_list = super(DriveResource, self).get_object_list(request)
        todays_day_name = datetime.now().strftime('%w')
        filtered_list = whole_list.filter(driver__user=request.user).filter(day=todays_day_name)
        return filtered_list

    def dehydrate_driver(self, bundle):
        return bundle.obj.driver.person_id

    def dehydrate_bus(self, bundle):
        return bundle.obj.bus.number

    def dehydrate_route(self, bundle):
        return bundle.obj.route.id

    def hydrate_bus_location(self, bundle):
        # Guard against the method being called twice
        if not hasattr(bundle, 'geopoint_processed'):
            lon = bundle.data['bus_location'][0] / 1e6
            lat = bundle.data['bus_location'][1] / 1e6
            bundle.data['bus_location'] = Point(lon, lat)
            bundle.geopoint_processed = True
        return bundle


class DriveFullResource(CommonModelResource):
    driver = fields.ForeignKey(DriverResource, 'driver', full=True, blank=True, null=True)
    bus = fields.ForeignKey(BusResource, 'bus', full=True, blank=True, null=True)
    route = fields.ForeignKey(RouteResource, 'route', full=True)

    class Meta(CommonModelResource.Meta):
        queryset = Drive.objects.all()
        resource_name = 'drive'
        allowed_methods = ['get', 'put']
        excludes = ('bus_location',)

    def obj_get_list(self, request=None, **kwargs):
        whole_list = super(DriveFullResource, self).obj_get_list(request, **kwargs)
        todays_day_name = datetime.now().strftime('%w')
        filtered_list = whole_list.filter(driver__user=request.user).filter(day=todays_day_name)
        return filtered_list

    def hydrate_bus_location(self, bundle):
        # Guard against the method being called twice
        if not hasattr(bundle, 'geopoint_processed'):
            lon = bundle.data['bus_location'][0] / 1e6
            lat = bundle.data['bus_location'][1] / 1e6
            bundle.data['bus_location'] = Point(lon, lat)
            bundle.geopoint_processed = True
        return bundle

    def put_detail(self, request, **kwargs):
        deserialized = self.deserialize(request, request.raw_post_data, format=request.META.get('CONTENT_TYPE', 'application/json'))
        deserialized = self.alter_deserialized_detail_data(request, deserialized)
        bundle = self.build_bundle(data=dict_strip_unicode_keys(deserialized), request=request)

        try:
            drive = Drive.objects.get(pk=kwargs['pk'])
            bundle = self.hydrate_bus_location(bundle)
            drive.bus_location = bundle.data['bus_location']
            drive.save()
            return http.HttpNoContent()
        except:
            return http.HttpBadRequest()

Does not work with Python 2.6, Django 1.5, Tastypie 0.11 (Running on Centos 5)

Calling server with : https://api.server.com/api/full/drive/?format=json&limit=50&user=12345

I tried changing to:

def obj_get_list(self, bundle, **kwargs):
    request = bundle.request
    whole_list = super(DriveFullResource, self).get_object_list(request)
    todays_day_name = datetime.now().strftime('%w')
    filtered_list = whole_list.filter(driver__user=request.user).filter(day=todays_day_name)
    return filtered_list

and

def obj_get_list(self, bundle, **kwargs):
    request = bundle.request
    whole_list = super(DriveFullResource, self).obj_get_list(bundle, **kwargs)
    todays_day_name = datetime.now().strftime('%w')
    filtered_list = whole_list.filter(driver__user=request.user).filter(day=todays_day_name)
    return filtered_list

Still get error messages of the type below:

{"error_message": "obj_get_list() got multiple values for keyword
argument 'bundle'", "traceback": "Traceback (most recent call
last):\n\n  File
\"/usr/lib/python2.6/site-packages/tastypie/resources.py\", line 195,
in wrapper\n\n  File
\"/usr/lib/python2.6/site-packages/tastypie/resources.py\", line 426,
in dispatch_list\n\n  File
\"/usr/lib/python2.6/site-packages/tastypie/resources.py\", line 458,
in dispatch\n\n  File
\"/usr/lib/python2.6/site-packages/tastypie/resources.py\", line 1266,
in get_list\n\n  File
\"/home/django_projects/project/src/routes/api.py\", line 110, in
obj_get_list\n    request = bundle.request\n\nTypeError:
obj_get_list() got multiple values for keyword argument 'bundle'\n"}

{"error_message": "obj_get_list() got multiple values for keyword
argument 'bundle'", "traceback": "Traceback (most recent call
last):\n\n  File
\"/usr/lib/python2.6/site-packages/tastypie/resources.py\", line 195,
in wrapper\n\n  File
\"/usr/lib/python2.6/site-packages/tastypie/resources.py\", line 426,
in dispatch_list\n\n  File
\"/usr/lib/python2.6/site-packages/tastypie/resources.py\", line 458,
in dispatch\n\n  File
\"/usr/lib/python2.6/site-packages/tastypie/resources.py\", line 1266,
in get_list\n\n  File
\"/home/django_projects/project/src/routes/api.py\", line 114, in
obj_get_list\n    return filtered_list\n\nTypeError: obj_get_list()
got multiple values for keyword argument 'bundle'\n"}
Was it helpful?

Solution

I tested the method on Tastypie 0.11 and seems to work ok. But in my opinion you are taking wrong and error prone approach. I prepared solution that will eliminate them.

Problem definition
You didn't defined what is your code supposed to do. Reviewing code I assume you want to have resource that returns object for currently logged user and filtered by day name. In other words you want to limit access to resource - And this is.. I think you know, authorization.

Why not obj_get_list?
You see that Tastypie obj_get_list is quite Django ORM specific and not documented well. I don't really know how to use it. I wouldn't dare to deal with it.

Solution
Let's define Authorization class for your resource:

from datetime import datetime

from tastypie.authorization import Authorization


class DriverAuthorization(Authorization):
    def read_list(self, object_list, bundle):
        todays_day_name = datetime.now().strftime('%w')
        return object_list.filter(driver__user=bundle.request.user,
                                  day=todays_day_name)

    def read_detail(self, object_list, bundle):
        todays_day_name = datetime.now().strftime('%w')
        return bundle.obj.day == todays_day_name and bundle.obj.driver.user == bundle.request.user

    def create_list(self, object_list, bundle):
        return []

    def create_detail(self, object_list, bundle):
        return False

    def update_list(self, object_list, bundle):
        return []

    def update_detail(self, object_list, bundle):
        object_before_update = object_list.get(pk=bundle.obj.pk)
        return object_before_update.driver.user == bundle.request.user

    def delete_list(self, object_list, bundle):
        return []

    def delete_detail(self, object_list, bundle):
        return False

Now attach authorization to resource:

class DriveResource(CommonModelResource):
    driver = fields.ForeignKey(DriverResource, 'driver')
    bus = fields.ForeignKey(BusResource, 'bus')
    route = fields.ForeignKey(RouteResource, 'route')

    class Meta(CommonModelResource.Meta):
        queryset = Drive.objects.all()
        allowed_methods = ['get', 'put']
        excludes = ('bus_location',)
        authorization = DriverAuthorization()
        filtering = {
            'updated': ('gt', 'gte', 'lt', 'lte', 'exact'),
            'id': ('exact',),
        }

    def dehydrate_driver(self, bundle):
        return bundle.obj.driver.person_id

    def dehydrate_bus(self, bundle):
        return bundle.obj.bus.number

    def dehydrate_route(self, bundle):
        return bundle.obj.route.id

    def hydrate_bus_location(self, bundle):
        # Guard against the method being called twice
        if not hasattr(bundle, 'geopoint_processed'):
            lon = bundle.data['bus_location'][0] / 1e6
            lat = bundle.data['bus_location'][1] / 1e6
            bundle.data['bus_location'] = Point(lon, lat)
            bundle.geopoint_processed = True
        return bundle

Benefits
1. Code now is much more clearer to read
2. You have control on what user can do or cannot do in one place.
3. No obj_get_list means no more problems.

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