Flask-restless endpoints with user resolution and filtration - python

How would correctly should look API that returns only objects belonging to the user who asks for them?
api/version/items/<items_id>
or
api/version/user/<user_id>/items/<items_id>
In the first case, the server queried the database with a user id, which it obtains from its authentication.
I don't know how to create both cases in Flask-restless. I think a preprocessor will be useful, where I could get user_id from authorization (JWT token), but I can't find a way to use it as search parameters for DB.
from flask_jwt import JWT, jwt_required, current_user
...
manager.create_api(Item,
methods=['GET'],
collection_name='items',
url_prefix='/api',
preprocessors=dict(GET_SINGLE=[api_auth],GET_MANY=[api_auth]))
#jwt_required()
def api_auth(*args, **kwargs):
user_id = current_user.id
# some code with user id addition.
pass

Preprocessor would be the place where you build a query object. I think the endpoint for items should look simply like:
api/version/items
but whithin the preprocessor you would build a query object that would be passed with the request:
GET api/version/items?q={"filters":[{"name":"userid","op":"eq","val":10}]}

You should use an endpoint that refers directly to the resources that you are trying to query. For example:
api/version/items
You should define a get_single and get_many preprocessor separately. The instance_id for the single (integer) and the result argument (dictionary) for the multiple pre-processor should be used to define what is returned to the user.
From the Flask-restless docs:
Those preprocessors and postprocessors that accept dictionaries as
parameters can (and should) modify their arguments in-place. That
means the changes made to, for example, the result dictionary will be
seen by the Flask-Restless view functions and ultimately returned to
the client.
Therefore in your pre-processors, you could do something like the following to retrieve the items (defined in a relationship to the user) in your database:
#jwt_required()
def api_auth_get_many(instance_id=None, *args, **kwargs):
user_id = current_user.id
if instance_id in User.query.get(user_id).items:
pass
else:
instance_id = None # Do not return the value if not permitted
#jwt_required()
def api_auth_get_many(result=None, *args, **kwargs):
user_id = current_user.id
result = User.query.get(user_id).items # Return all items allowed for user

Related

django-rest-framework non orm-based filtering

I am using DjangoRestApi and while it works like a charm with queryset (orm-based) views, I am struggling to make views that use different back-end to behave same way orm-based views are. Notably I want to add filters and have them cast and validated automatically.
Pseudo code below:
class NewsFilter(django_filters.FilterSet):
category = django_filters.NumberFilter(name='category')
limit = django_filters.NumberFilter(name='limit')
page = django_filters.NumberFilter(name='page')
class NewsView(generics.APIView):
filter_class = NewsFilter
def get(self, request):
filters = self.filter_class(??) # not sure, what to put here
payload = logic.get_business_news(**filters.data) # same
return Response(payload, status=status.HTTP_200_OK)
Any hint how to tackle problem will be appreciated.
Ultimate goal is to:
user types something into url or sends via POST, django-rest intercepts relevant values, extracts them, casts them into correct type and return as a dictionary
filters are displayed as they would if serializer was ORM based
The function signature to any single filter is like
class MyFilter(django_filters.Filter):
def filter(self,queryset,value):
[...]
The function signature to a FilterSet is:
def __init__(self, data=None, queryset=None, prefix=None, strict=None):
So, it looks like you pass in request.GET as data param and then pass in your queryset.

Django custom decorator user_passes_test() can it obtain url parameters?

Can Django's user_passes_test() access view parameters?
For example I have view that receives an id to retrieve specific record:
def property(request, id):
property = Property.objects.get(id=int(id))
The record has a field named user_id that contains the id for user that originally created record. I want users to be able to view only their own records otherwise be redirected.
I'd like to use a custom decorator which seems simple and clean.
For custom decorator is there some variation of something like this that will work?
#user_passes_test(request.user.id = Property.objects.get(id=int(id)).id, login_url='/index/')
def property(request, id):
property = Property.objects.get(id=int(id))
I have tried creating separate test_func named user_is_property_owner to contain logic to compare current user to property record user_id
#user_passes_test(user_is_property_owner(id), login_url='/index/')
def property(request, id):
property = Property.objects.get(id=int(id))
def user_is_property_owner(property_id):
is_owner = False
try:
Property.objects.filter(id=property_id, user_id=user_id).exists()
is_owner = True
except Property.DoesNotExist:
pass
But having trouble getting current user id and the property id from the request into the user_is_property_owner decorator function.
EDIT to add solution I was using. I did test inside each view test was required. It is simple. i thought using a decorator might be prettier and slightly more simple.
def property(request, id):
# get object
property = Property.objects.get(id=int(id))
# test if request user is not user id on property record
if request.user.id != property.user_id:
# user is not same as property user id so redirect to index
return redirect('index')
# rest of the code if request user is user_id on property record
# eg it is ok to let user into view
Typically, (using class based views), I'll handle this in the get_queryset method so it would be something like
class PropertyDetail(DetailView):
def get_queryset(self):
return self.request.user.property_set.all()
and that will give a 404 if the property isn't for the current user. You might prefer to use a project like django-guardian if you end up with more permission relationships than just Property.
If you take a look at UserPassesTestMixin you'll see that it processes the test_func before calling dispatch so you'll have to call self.get_object(request) yourself if you decide to go that route.

get request parameters in Tastypie

I am building a REST API for my application that uses a NoSQL db (Neo4j) using Tastypie.
So I overrode some main methods of the class tastypie.resources.Resource to do so, and currently struggling to implement def obj_get_list(self, request=None, **kwargs): which is supposed to return a list of objects.
Actually, I want to pass a parameter to this method through the url (something like http://127.0.0.1:8000/api/airport/?query='aQuery' ) and then perform a query based on this parameter.
The problem is that the request is None so I can't get its parameter !
When printing the kwargs variable, I see this :
{'bundle': <Bundle for obj: '<testNeo4Django.testapp.api.Airport object at 0x9d829ac>' and with data: '{}'>}
Thanks for your help
Currently positional argument request is not passed toobj_get_list.
So you should:
def obj_get_list(self, bundle, **kwargs):
param = bundle.request.GET['param']
#fetch objects based on param
return objects

Get model object from tastypie uri?

How do you get the model object of a tastypie modelresource from it's uri?
for example:
if you were given the uri as a string in python, how do you get the model object of that string?
Tastypie's Resource class (which is the guy ModelResource is subclassing ) provides a method get_via_uri(uri, request). Be aware that his calls through to apply_authorization_limits(request, object_list) so if you don't receive the desired result make sure to edit your request in such a way that it passes your authorisation.
A bad alternative would be using a regex to extract the id from your url and then use it to filter through the list of all objects. That was my dirty hack until I got get_via_uri working and I do NOT recommend using this. ;)
id_regex = re.compile("/(\d+)/$")
object_id = id_regex.findall(your_url)[0]
your_object = filter(lambda x: x.id == int(object_id),YourResource().get_object_list(request))[0]
You can use get_via_uri, but as #Zakum mentions, that will apply authorization, which you probably don't want. So digging into the source for that method we see that we can resolve the URI like this:
from django.core.urlresolvers import resolve, get_script_prefix
def get_pk_from_uri(uri):
prefix = get_script_prefix()
chomped_uri = uri
if prefix and chomped_uri.startswith(prefix):
chomped_uri = chomped_uri[len(prefix)-1:]
try:
view, args, kwargs = resolve(chomped_uri)
except Resolver404:
raise NotFound("The URL provided '%s' was not a link to a valid resource." % uri)
return kwargs['pk']
If your Django application is located at the root of the webserver (i.e. get_script_prefix() == '/') then you can simplify this down to:
view, args, kwargs = resolve(uri)
pk = kwargs['pk']
Are you looking for the flowchart? It really depends on when you want the object.
Within the dehydration cycle you simple can access it via bundle, e.g.
class MyResource(Resource):
# fields etc.
def dehydrate(self, bundle):
# Include the request IP in the bundle if the object has an attribute value
if bundle.obj.user:
bundle.data['request_ip'] = bundle.request.META.get('REMOTE_ADDR')
return bundle
If you want to manually retrieve an object by an api url, given a pattern you could simply traverse the slug or primary key (or whatever it is) via the default orm scheme?

Django admin filter list with custom model manager extra methods

I have an Address model that contains two float fields, lat and lng. I have written a custom model manager with a nearby(lat, lng, distance) method that uses raw SQL to return only Addresses which lie within a certain radius. (GeoDjango appeared to be overkill).
Example call:
Address.objects.nearby(53.3, 13.4, 10) (returns QuerySet)
Now I want to dynamically filter Address objects in the Django admin using this method (ideally letting the user pick a location on a Google map and a max distance using a slider). I have no idea how to achieve that. Can anyone point me in the right direction?
CLARIFICATION
You don't need to write any JavaScript for me, I just want to know how to make Django admin evaluate extra Query parameters in the URL, such that I can do queries like /admin/appname/address/?lat=53&long=13&dist=10. I can then probably figure out how to stuff a Google map and the required JavaScript magic into the template myself.
UPDATE
I've tried to overwrite queryset in the ModelAdmin like so:
def queryset(self, request):
try:
lat = float(request.REQUEST['lat'])
lng = float(request.REQUEST['lng'])
dist = int(request.REQUEST['dist'])
matches = Address.objects.nearby(lat=lat, lng=lng, dist=dist)
return matches
except:
return super(ReportAdmin, self).queryset(request)
However, the admin does not like it and returns with ?e=1, without filtering the results.
I've added this to the ModelAdmin of the object class that has the address as a FK:
def lookup_allowed(self, lookup, *args, **kwargs):
if lookup == 'address__dst':
return True
return super(ReportAdmin, self).lookup_allowed(lookup, args, **kwargs)
I've added this to the model
def _filter_or_exclude(self, negate, *args, **kwargs):
try:
value = kwargs.pop('address__dst')
matches = self.nearby(*map(float, value.split(',')))
pks = [m.pk for m in matches]
kwargs.update({ 'pk__in': pks })
except:
pass
return super(ReportQuerySet, self)._filter_or_exclude(negate, *args, **kwargs)
allowing me to filter like ?address_dst=lat,lng,dst.
Is there a nicer solution?
If you need this functionality on admin list display page you can write a custom filter.
See the solution in: Custom Filter in Django Admin on Django 1.3 or below.

Categories