Python TastyPie - Custom manager methods as filters?

django, geodjango, python, tastypie

Solution

There are a couple of issues with your code above.

First, yes you can expose any custom manager to anything, regardless if you are using it tastypie or not. AdvertManager you defined above can be accessed through Advert.objects only if you have replaced it default manager with your own version.

Second, the way you want to expose tastypie filtering on AdvertResource is orthogonal on how filtering actually works.

All filters are applied as practically ORM filters in the form `<field_name>__<filter_name>=<value_or_values>`. Since in your example you are using `box=<number>,<number>,...,<number>` tastypie explodes that into `box__exact=...` and tries to find `box` field in AdvertResource and fails as expected.

If your advert has a field called `location` you could add `withinbox` as a filter for that field and filter by: `location__withinbox=<values>`.

If you want to keep your original approach, you would have to parse the box filter yourself from request.GET dictionary and then pass them to your own overridden version of `obj_get` and `obj_get_list` in AdvertResource.

Finally, when extending `build_filters` you are only making a mapping between Tastypie filter and an ORM filter. In your example you are returning objects as a filter; instead simply define it as:

{ 'withinbox' : 'point__within' }

and convert the list of values into `Polygon` within `apply_filters` before it is handed off to the actual filter method.

Problem

I have a GeoDjango project that has a manager model like this; ``` class AdvertManager(models.GeoManager): def within_box(self, x0, y0, x1, y1): geometry = Polygon.from_bbox((x0, y0, x1, y1,)) return self.filter(point__within=geometry) ``` I'm trying to get my resource model (AdvertResource) to expose the within_box function through a GET parameter, something like; ``` http://127.0.0.1:8000/api/v1/advert/?format=json&box=51.623349,-3.25362,51.514195,-3.4754133 ``` I started to write a `build_filters` method on the resource model like this; ``` def build_filters(self, filters=None): if not filters: filters = {} orm_filters = super(AdvertResource, self).build_filters(filters) if 'box' in filters: points = [float(p.strip()) for p in filters['box'].split(',')] orm_filters = {'box': Advert.objects.within_box(*points).all()} return orm_filters ``` But this throws an error "Cannot resolve keyword 'box' into field...". Is it possible to expose methods from a custom manager to the api urls? EDIT - I have now solved this with the following solution. ``` class AdvertResource(ModelResource): longitude = fields.FloatField(attribute='longitude', default=0.0) latitude = fields.FloatField(attribute='latitude', default=0.0) author = fields.ForeignKey(UserResource, 'author') def build_filters(self, filters=None): """ Build additional filters """ if not filters: filters = {} orm_filters = super(AdvertResource, self).build_filters(filters) if 'point__within_box' in filters: points = filters['point__within_box'] points = [float(p.strip()) for p in points.split(',')] orm_filters['within_box'] = points return orm_filters def apply_filters(self, request, applicable_filters): """ Apply the filters """ if 'within_box' in applicable_filters: area = applicable_filters.pop('within_box') poly = Polygon.from_bbox(area) applicable_filters['point__within'] = poly return super(AdvertResource, self).apply_filters(request, applicable_filters) ``` This now means that the request `http://127.0.0.1:8000/api/v1/advert/?format=json&point__within_box=51.623349,-3.25362,51.514195,-3.4754133` now filters all results within the bounding box.

Original source