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.
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 usingbox=<number>,<number>,...,<number>tastypie explodes that intobox__exact=...and tries to findboxfield in AdvertResource and fails as expected.If your advert has a field called
locationyou could addwithinboxas 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_getandobj_get_listin AdvertResource.Finally, when extending
build_filtersyou 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:and convert the list of values into
Polygonwithinapply_filtersbefore it is handed off to the actual filter method.