Flask routing multiple optional parameters using url_for() - flask

I know there are similar questions on this but they don't seem to fit my case outlined below:
I use one optional parameter in many of my routes like this.
#bp.route('/page1', methods=["GET", "POST"])
#bp.route('/page1/<foo>', methods=["GET", "POST"])
def page1(foo=None):
if foo:
# do foo stuff
This is called with url_for('page1') or url_for('page1', foo=3) and it works just fine.
I would like to optionally use additional parameters like this:
url_for('page2', foo=4) or url_for('page2', bar= 5) or url_for('page2', foo=4, bar=5)
I tried this.
#bp.route('/page2', methods=["GET", "POST"])
#bp.route('/page2/<foo>/<bar>', methods=["GET", "POST"])
def page1(foo=None, bar=None):
if foo:
# do foo stuff
if bar:
#do bar stuff
I was hoping that url_for() would be smart enough to build the request in the correct order if a parameter was missing since they are known and named - but it does not. Perhaps empty slot like page2//5 is just not allowed.
Further, url_for('page2', foo=None, bar= 5) seems to be illegal ( pycharm will not run it)
and url_for('page2', foo='', bar= 5) will cause a runtime error.
The only thing that does work is url_for('page2', foo='ignorestring', bar= 5)
So, I can make this work but have to populate my unused variable(s) with something (instead of None or empty string) which makes for ugly code!
Any ideas how to do this cleanly without resorting to less secure querystrings?

Related

flask route with default argument at beginning of path

In flask, I can specify a default argument to a route thus:
#app.route("/user", defaults={'uid': 3})
#user.route('/user/<uid>')
def user(uid):
# If /user or /user/ is requested, I'll see uid=3.
# If /user/4 is requested, I'll see uid=4.
But I have a reverse path:
#app.route("/user", defaults={'thing': None})
#user.route('/<thing>/user/')
def user(thing):
# If /user or /user/ is requested, I want to see thing == None.
# If /dog/user/ is requested, I'll see thing == 'dog'.
Basically, if thing isn't provided, I'll do something a bit complex to compute it. But it's fixed for a session, so I pass it around.
I know that this is asking quite a lot from the route parser. (In particular, no thing had better conflict with another path, and I could easily cause quite a lot of inefficient backtracking if I'm not careful. But things are designed never to conflict.)
The question is whether there's a way to do this?
What I've done is to create two functions. But I have a handful of functions like this, so this is feeling heavy.
#app.route("/user")
def user():
return redirect(url_for('main.user', thing=ComputeThing()))
#user.route('/<thing>/user/')
def user(thing):
# If /user or /user/ is requested, I want to see thing == None.
# If /dog/user/ is requested, I'll see thing == 'dog'.
I must be missing something, can you expand on what you mean by a reverse path, because just using defaults seems to achieve what you want in your last example.
from flask import Flask
from random import choice
app = Flask(__name__)
choices = ['fox', 'cat', 'eel', 'cow']
def compute_thing():
return choice(choices)
#app.route("/user", defaults={'thing': None})
#app.route("/<thing>/user/")
def user(thing):
if thing is None:
thing = compute_thing()
return f"`thing` set as {thing}"
if __name__ == '__main__':
tc = app.test_client()
ru = tc.get('/user')
print(ru.data) # `thing` set as cow'
rd = tc.get('/dog/user/')
print(rd.data) # `thing` set as dog'

Test Django Mock - check that function/method is called

I want to check that do_a calls do_b. I'm doing like this:
The code:
def do_a(...):
...
do_b(...)
...
The test:
def test_do_a(self):
...
with patch('...do_b', new_callable=do_nothing()) as mock_do_b:
do_a(...)
mock_do_b.assert_called_once_with(...)
And do_nothing:
def do_nothing():
pass
This is working fine but I had to use do_nothing() which I find hacky. Is there a way to make the same test without the extra useless function do_nothing()?
You could use patch as a decorator
#patch('...do_b')
def test_do_a(self, mock_do_b):
do_a(...)
mock_do_b.assert_called_once_with(...)

Should unrecoverable exceptions from HTTP parameter problems be caught?

Is it necessary to catch errors stemming from HTTP inputs? Is it ever a good idea to let it fail naturally (allow the exception to bubble up)?
I have a Django view for a server side interface for an AJAX call that looks something like this:
def some_view(request):
try:
some_int = int(request.POST.get('some_int')) # May raise ValueError or TypeError
except (ValueError, TypeError):
return HttpResponseBadRequest('some_int must be an int')
# ... Code that assumes some_int is an int
return HttpResponse('The normal response')
Is it ever acceptable in production code to have something like this?
def some_view(request):
some_int = int(request.POST.get('some_int')) # Ignore ValueError or TypeError raised
# ... Code that assumes some_int is an int
return HttpResponse('normal_response')
As I accept more parameters, I find that it is frustrating to maintain so many try/except blocks which are mostly the same and I end up with a ton of boiler plate code.
Of course I tried to refactor this into a separate function but since Django requires an HttpResponse to be returned, not raised as an exception, I can't plug it into a view without a try/except block. Also, conversions to int aren't the only thing I check... there are a lot of business logic sanity checks performed depending on the input as well. For example, I would validate that the JSON passed is of a specific format (i.e. array of objects of int array, etc.).
My views end up being 70+ lines of code just for sanity checks and a few lines of code that actually generate the response. Somehow I feel like there should be a more elegant way but I haven't found one so I'm considering forgoing all checks and just letting Django take care of it. Is this a bad idea?
I'm aware of the following potential problems if I don't catch the exceptions:
The same HTTP 500 is returned for all errors
If logging is enabled in production, it would probably log an error every time an invalid input occurs
Are there other problems I should be aware of? It just feels wrong not to catch exceptions from user inputs even though there's not much I can do about it in terms of recovery logic.
I think the best way to handle this is by writing your own middleware that catches the exceptions and turns them into your desired response.
That might look something like this:
# views.py
def some_view(request):
some_int = int(request.POST.get('some_int'))
# ... Code that assumes some_int is an int
return HttpResponse('normal_response')
# middleware.py
class MyValidationMiddleware(object):
def process_exception(self, request, e):
if isinstance(e, ValueError):
return HttpResponseBadRequest('Input did not validate')
else:
return None # let it bubble up
(Since middleware is site-wide, you might want to explicitly define your own input-validation exceptions to distinguish them from other sorts of exceptions that might occur.)
Alternatively, you could do the same sort of thing with per-view decorators.

How can I avoid repeating this common code in Django views?

I have many similar functions in my Django views.py that start off like this:
#login_required
def processMyObject(request, myObjectID, myObjectSlug=None):
logger.info("In processMyObject(myObjectID=%s, myObjectSlug=%s)" % (myObjectID, myObjectSlug))
try:
myObject = MyObject.objects.get(id=myObjectID)
except:
logger.error("Failed to get MyObject Object by ID")
raise "Failed to get MyObject Object by ID"
if myObjectSlug != None and myObject.slug != myObjectSlug:
logger.error("myObjectSlug '%s' doesn't match myObject #%s's slug" % (myObjectSlug, myObject.id))
raise "myObjectSlug '%s' doesn't match myObject #%s's slug" % (myObjectSlug, myObject.id)
Each of these functions has the same argument signature and contains the same chunk of code at the top, but then goes on to do implement some unique functionality. However this is common code in each one of them. It seems like a horrible violation of DRY for me to have typed the same code so many multiple times.
How can I use inheritance or some other technique to elegantly factor out this code so that it only appears once but is used in each of these view functions?
You can write decorator that recieves the MyObjectId and slug from view as parameter. logs the info line and raises an error if object is missing.
Just check information about function decorators and read django code for examples. For example, look up the code for the decorator you are already using (login_required) and look up the user_passes_test decorator in django.contrib.auth.decorators. That is probably the best real example for your case.
And then use the decorator in front of each view that needs it - just like you are using #login_required

Django's admin 'change' view not accepting kwargs={'object_id':'blah}

I'm a little thrown by this. I have the following code, which works perfectly well:
urlresolvers.reverse('admin:cards_card_change', args=([92]))
To further my understanding, I wanted to try rewriting the line as:
urlresolvers.reverse('admin:cards_card_change', kwargs={'object_id':92})
as seemingly suggested by the documentation on reversing admin views (I'm using Django 1.4).
However, this doesn't seem to match anything at all. Why not? I tried looking in the django source code for answers, but couldn't find the view used for change, so links to the relevant module there would be really helpful as well!
The urlpattern of the change view is in admin/options.py:
url(r'^(.+)/$',
wrap(self.change_view),
name='%s_%s_change' % info),
You could find that it dispatches a request to the change_view method of the ModelAdmin instance. The change_view method also resides in admin/options.py:
def change_view(self, request, object_id, form_url='', extra_context=None):
...
It does accept a parameter object_id.
The reason of the missing match of reverse is that the urlpattern above does not accept named parameter, if you change it to something like
url(r'^(?P<object_id>.+)/$',
wrap(self.change_view),
name='%s_%s_change' % info),
The urlresolvers.reverse('admin:cards_card_change', kwargs={'object_id':92}) should work.
I've no idea whether it was intended to avoid some edge cases or it's just a bug and there's already a ticket fixing this. I'll check it later.
I think this is a python syntax issue.
The thing is that **kwargs cannot be taken as *args.
The function you're trying to call has a first argument (object_id) which is positional and cannot be used as a named argument. (Someone tell me if I'm wrong)
For you, object_id is a positional argument, and thus will be ignored if used in kwargs
If you want your view function to accept object_id also in kwargs, you have to change it with for example
my_id = kwargs.get('object_id', args[0])