Unit testing Flask function (with logged in user) - python

I'm new to Python and Flask but I'm gradually getting to grips with it. I've got so far into building an app and I'm now thinking I should start some unit testing. I really can't get my head around it though. I've read various docs, posts and examples but I can't seem to transfer this to my own code. I'm hoping if someone can show me how to write a test for one of my functions then things will fall into place. It's very tempting at this stage to ignore it and press on building my app.
#app.route('/user/<nickname>/create_bike', methods = ['GET', 'POST'] )
#login_required
def create_bike(nickname):
user = User.query.filter_by(nickname = nickname).first()
bikes = user.bikes.all()
bikecount = user.bikes.count()
form = CreateBike()
if form.validate_on_submit():
if bikecount < user.bike_allowance:
# let user create a bike
newbike = Bikes(bikename = form.bikename.data,
user_id = g.user.id, shared = SHARED )
db.session.add(newbike)
db.session.commit()
flash('Your new bike has been created')
return redirect(url_for('create_bike', nickname = nickname))
else:
flash("You have too many bikes")
return render_template('create_bike.html',
user = user,
form = form
)
UPDATE - Here's my working test
def test_create_bike(self):
u = User(nickname = 'john', email = 'john#example.com', account_type = "tester")
db.session.add(u)
db.session.commit()
# login user
with self.app as c:
with c.session_transaction() as sess:
sess['user_id'] = int(u.get_id())
# http://pythonhosted.org/Flask-Login/#fresh-logins
sess['_fresh'] = True
rv = c.post('/user/john/create_bike', data = dict(
bikename = 'your new bike',
user_id = sess['user_id'],
shared = 1
), follow_redirects = True)
assert 'Your new bike has been created' in rv.data

Your test is likely failing because that view requires a logged in user, and since you arent passing in any session data, you are being redirected to the login page (the data argument to .post is form data, available in request.form in your view). Before your assertion you can see what the response is to help you along the way:
print rv.status_code
print rv.location #None if not a redirect
There's some documentation around sessions in tests here, and if you're using Flask-Login like it looks you are, this answer shows you how to set the session up so you get a logged in user

You're on the right track.
Checking if the html page contains some piece of data that should be there is one the most common scenarios in testing web apps. You just need to repeat for all the other pages that you create. You can also add tests to see that creating a user follows all the validation rules that you've setup or that the username field is unique etc.

Related

python-flask db.session.commit() is not working

The problem is when user tries 'forgot password' option. It creates new reset_key for verification, but the new key is not getting updated into DB.
#app.route('/login/forgot/', methods=['GET', 'POST'])
def forgot():
form = ResetLoginForm(request.form)
#There's no session yet. User just pointing to /login/forgot url.
if request.method == 'POST' and form.validate():
user = User.query.filter_by(email=form.email.data).first()
if not user:
flash('The username or email incorrect')
return render_template('forgot.html', form=form)
reset_key = generate_key() ## this creates a new key, but how update this key into db?
#tried something like
user.reset_key = reset_key
db.session.add(user)
db.session.commit()
#this is not working. Is it due to session is not started or something?
Thanks for any help or hint.
This is because User.query.filter_by(email=form.email.data).first() will return a sqlalchemy.orm.query.Query object. As its doc says:
Query is the source of all SELECT statements generated by the ORM,
both those formulated by end-user query operations as well as by high
level internal operations such as related collection loading. It
features a generative interface whereby successive calls return a new
Query object, a copy of the former with additional criteria and
options associated with it.
So you just get a copied object, so your change will not work;
You can use like this:
user = db.session.query(User).filter_by(email==form.email.data).first()
and then you can change user attrs
user = db.session.query(User).first() solved problem.

Is it bad practice to have multiple routes per flask view function?

So basically I have multiple render_template returns based on if statements, and they return different variables that my jinja2 template responds to. I believe I could break up these routes into their own functions (likewise I could break up my templates too into more than one template (for example an edit.html template instead of an {% if editform %} in my template)), but I like the idea of having a single view function and template for any given page.
Before I spend more time creating the rest of my view functions, I want to make sure that what I'm doing isn't going to bite me later.
Code below, thanks!
#app.route('/admin/users/', defaults={'id': None}, methods = ['GET'])
#app.route('/admin/users/<id>', methods = ['GET'])
#app.route('/admin/users/edit/<id>', methods = ['GET', 'POST'])
#login_required
def users(id):
if not current_user.role == ROLE_ADMIN:
flash('You do not have access to view this page.')
return redirect(url_for('index'))
if id:
user = User.query.filter_by(id = id).first()
if 'edit' in request.path:
editform = UserForm()
if editform.validate_on_submit():
user.username = editform.username.data
user.email = editform.email.data
user.role = editform.role.data
db.session.add(user)
db.session.commit()
return redirect('/admin/users/' + str(user.id))
editform.username.data = user.username
editform.email.data = user.email
editform.role.data = user.role
return render_template("users.html",
title = "Edit User",
user = user,
editform = editform)
return render_template("users.html",
title = "User",
user = user)
users = User.query.all()
return render_template("users.html",
title = 'Users',
users = users)
The answer to this question is probably a little bit down to personal taste, but personally I'd avoid doing things like this. My main reason would just be to keep things simple - you've got three nested if statements inside that function, that are basically doing the exact same job as flasks routing logic would do if you were to split up the views.
Admittedly it's not a massive problem at present, but if you were to add more logic in to any of the paths then it could lead to a surprisingly complex function. This could lead to subtle bugs, or just plain difficulty understanding exactly what's going on from a glance.
If you split things up however, it should be obvious at a glance exactly what each route is doing. Doesn't even involve more code, since you can cut out on all the if statements:
#app.route('/admin/users/', methods = ['GET'])
def get_all_users():
users = User.query.all()
return render_template("users.html",
title = 'Users',
users = users)
#app.route('/admin/users/<id>', methods = ['GET'])
def get_single_user(id):
user = User.query.filter_by(id = id).first()
return render_template("users.html",
title = "User",
user = user)
#app.route('/admin/users/edit/<id>', methods = ['GET', 'POST'])
def edit_user(id):
editform = UserForm()
if editform.validate_on_submit():
user.username = editform.username.data
user.email = editform.email.data
user.role = editform.role.data
db.session.add(user)
db.session.commit()
return redirect('/admin/users/' + str(user.id))
editform.username.data = user.username
editform.email.data = user.email
editform.role.data = user.role
return render_template("users.html",
title = "Edit User",
user = user,
editform = editform)
EDIT: To clarify - I'm not saying it's wrong to have more than one url per flask route. In this case I think it's a bad idea to have a single function doing three fairly different things. There are other use cases for multiple routes per view that I do make use of. For example, in my own code I frequently have views like this:
#migrations_tasks.route('/external_applications', methods=['GET'])
#migrations_tasks.route('/external_applications/<cursor>', methods=['POST'])
def migrate_external_applications(cursor=None, subdomain=None):
... do stuff
Where the alternative route accepts a cursor into a database query for pagination. The flow of the code is basically the same, it just has different URLs for the first page and subsequent pages.
There is no hard and fast rule, but I would suggest splitting into multiple functions is more modular going with the Separation of concerns principle.
I would even go one step further and use the MethodView, where there is a class per route with get,post, post etc overridden from base.
When your function is small it looks ok, but when it grows some modularity helps !

404 error on unique page creation with google app engine

I asked a similar question here: create permenant unique links based on a user ID but couldn't quite get an answer. I am trying to give every user on my site a unique profile page. I have it mostly set up but I keep getting a 404 error. Now I am not sure if it is a problem with my handler or just the whole way I am doing it.
Here is my app code:
app = webapp2.WSGIApplication([('/', MainPage),
(r'/profile/(.+)', ProfilePage)])
and here is my ProfilePage handler class:
class ProfilePage(webapp2.RequestHandler):
def get(self, profile_id):
profileowner = User.get_by_id(profile_id)
if profileowner:
#Get all posts for that user and render....
#theid is their federated_id
theid = profileowner.theid
personalposts = db.GqlQuery("select * from Post where theid =:1 order by created desc limit 30", theid)
#I collect this so that I can have their username in the top of the page
global visits
logout = users.create_logout_url(self.request.uri)
currentuser = users.get_current_user()
self.render('profile.html', user = currentuser, visits = visits, logout=logout, personalposts=personalposts)
else:
self.redirect("/")
For an ID of 1201, which I found in the datastore viewer for a use, I have been testing it by typing in www.url.com/profile/1201 and that is when I get the 404 error.
Update:
It now is redirecting me to the main page with Amber's suggested change.
Now when I change this line:
profileowner = User.get_by_id(profile_id)
to this:
profileowner = User.get_by_id(17001)
it goes through correctly so I am guessing that that line is not correctly getting the profile_id from the URL
r'/profile/<profile_id>'
is not a valid regular expression. You probably want something like this instead:
r'/profile/(.+)'

How to implement user_loader callback in Flask-Login

I'm attempting to use Flask and the Flask-Login extension to implement user authentication in a Flask app. The goal is to pull user account information from a database and then log in a user, but I'm getting stuck; however, I've narrowed it down to a particular part of Flask-Login behavior.
According to the Flask-Login documentation, I need to create a user_loader "callback" function. The actual purpose and implementation of this function has had me confused for a few days now:
You will need to provide a user_loader callback. This callback is used
to reload the user object from the user ID stored in the session. It
should take the Unicode ID of a user, and return the corresponding
user object. For example:
#login_manager.user_loader
def load_user(userid):
return User.get(userid)
Now, say I want the user to enter a name and password into a form, check against a database, and log in the user. The database stuff works fine and is no problem for me.
This 'callback' function wants to be passed a user ID #, and return the User object (the contents of which I'm loading from a database). But I don't really get what it's supposed to be checking/doing, since the user IDs are all pulled from the same place anyway. I can 'sort-of' get the callback to work, but it seems messy/hackish and it hits the database with every single resource that the browser requests. I really don't want to check my database in order to download favicon.ico with every page refresh, but flask-login seems like it's forcing this.
If I don't check the database again, then I have no way to return a User object from this function. The User object/class gets created in the flask route for logging in, and is thus out of scope of the callback.
What I can't figure out is how to pass a User object into this callback function, without having to hit the database every single time. Or, otherwise figure out how to go about doing this in a more effective way. I must be missing something fundamental, but I've been staring at it for a few days now, throwing all kinds of functions and methods at it, and nothing is working out.
Here are relevant snippets from my test code. The User class:
class UserClass(UserMixin):
def __init__(self, name, id, active=True):
self.name = name
self.id = id
self.active = active
def is_active(self):
return self.active
The function I made to return the user object to Flask-Login's user_loader callback function:
def check_db(userid):
# query database (again), just so we can pass an object to the callback
db_check = users_collection.find_one({ 'userid' : userid })
UserObject = UserClass(db_check['username'], userid, active=True)
if userObject.id == userid:
return UserObject
else:
return None
The 'callback', which I don't totally understand (must return the User object, which gets created after pulling from database):
#login_manager.user_loader
def load_user(id):
return check_db(id)
The login route:
#app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST" and "username" in request.form:
username = request.form["username"]
# check MongoDB for the existence of the entered username
db_result = users_collection.find_one({ 'username' : username })
result_id = int(db_result['userid'])
# create User object/instance
User = UserClass(db_result['username'], result_id, active=True)
# if username entered matches database, log user in
if username == db_result['username']:
# log user in,
login_user(User)
return url_for("index"))
else:
flash("Invalid username.")
else:
flash(u"Invalid login.")
return render_template("login.html")
My code 'kinda' works, I can log in and out, but as I said, it must hit the database for absolutely everything, because I have to provide a User object to the callback function in a different namespace/scope from where the rest of the login action takes place. I'm pretty sure I'm doing it all wrong, but I can't figure out how.
The example code provided by flask-login does it this way, but this only works because it's pulling the User objects from a global hard-coded dictionary, not as in a real-world scenario like a database, where the DB must be checked and User objects created after the user enters their login credentials. And I can't seem to find any other example code that illustrates using a database with flask-login.
What am missing here?
You will need to load the user object from the DB upon every request. The strongest reason for that requirement is that Flask-Login will check the authentication token every time to ensure its continuing validity. The calculation of this token may require parameters stored on the user object.
For example, suppose a user has two concurrent sessions. In one of them, the user changes their password. In subsequent requests, the user must be logged out of the second session and forced to login anew for your application to be secure. Think of the case where the second session is stolen because your user forgot to log out of a computer - you want a password change to immediately fix the situation. You might also want to give your admins the ability to kick a user out.
For such forced logout to happen, the authentication token stored in a cookie must 1) be based in part on the password or something else that changes each time a new password is set; 2) be checked before running any view, against the latest known attributes of the user object - which are stored in the DB.
I do share your concerns Edmond: hitting database each time when one needs to know user's role or name is insane. Best way would be to store your User object in session or even application-wide cache which gets updated from the DB each couple of minutes. I personally use Redis for that (that way website can be run by multiple threads/processes while using single cache entry point). I just make sure Redis is configured with password and non-default port, and any confidential data (like user hashes etc) are stored there in an encrypted form. Cache can be populated by a separate script running on specified interval, or separate thread can be spawned in Flask. Note: Flask-Session can be also configured to use (the same) redis instance to store session data, in that case instance with 'bytes' datatype will be needed, for a regular cache you might often go with instance type which automatically translates bytes into strings (decode_responses=True).
Here is my code, another User as data mapping object provide query_pwd_md5 method.
User login:
#app.route('/users/login', methods=['POST'])
def login():
# check post.
uname = request.form.get('user_name')
request_pwd = request.form.get('password_md5')
user = User()
user.id = uname
try:
user.check_pwd(request_pwd, BacktestUser.query_pwd_md5(
uname, DBSessionMaker.get_session()
))
if user.is_authenticated:
login_user(user)
LOGGER.info('User login, username: {}'.format(user.id))
return utils.serialize({'userName': uname}, msg='login success.')
LOGGER.info('User login failed, username: {}'.format(user.id))
return abort(401)
except (MultipleResultsFound, TypeError):
return abort(401)
User class:
class User(UserMixin):
"""Flask-login user class.
"""
def __init__(self):
self.id = None
self._is_authenticated = False
self._is_active = True
self._is_anoymous = False
#property
def is_authenticated(self):
return self._is_authenticated
#is_authenticated.setter
def is_authenticated(self, val):
self._is_authenticated = val
#property
def is_active(self):
return self._is_active
#is_active.setter
def is_active(self, val):
self._is_active = val
#property
def is_anoymous(self):
return self._is_anoymous
#is_anoymous.setter
def is_anoymous(self, val):
self._is_anoymous = val
def check_pwd(self, request_pwd, pwd):
"""Check user request pwd and update authenticate status.
Args:
request_pwd: (str)
pwd: (unicode)
"""
if request_pwd:
self.is_authenticated = request_pwd == str(pwd)
else:
self.is_authenticated = False

Google App Engine with Python: Unable to update the data entity

I have read, read, and read again the documentation and do many search on the Web but I don't understand yet why my app doesn't work correctly.
When a user connects, he have to complete a form. The code for this works. When the user logs out, and then, logs in, the form fields are filled with his information. So, the data is correctly saved. But, when the user changes the form fields values and submits the form, data are not updated.
My model:
class Members(db.Model):
account = db.UserProperty()
hashtags = db.StringProperty()
Here the class to submit the form:
class Submit(webapp.RequestHandler):
def post(self):
user = users.get_current_user()
if user:
url = users.create_logout_url('/')
url_linktext = 'Logout'
member = db.GqlQuery("SELECT * FROM Members WHERE account = :1", user)
if member.count(1) > 0:
m = Members(account=user)
m.hashtags = ','.join([
self.request.get('hashtag1'),
self.request.get('hashtag2'),
self.request.get('hashtag3')])
m.put()
else:
member = Members()
member.account = user
member.hashtags = ','.join([
self.request.get('hashtag1'),
self.request.get('hashtag2'),
self.request.get('hashtag3')])
member.put()
self.redirect('/')
else:
self.redirect('/')
The problem is you are adding a new record instead of updating the existing record. For your code, the simplest fix would be to change this:
if member.count(1) > 0:
m = Members(account=user)
to:
if member.count(1) > 0:
m = member[0]
The reason your code is updating the existing record is because you have not assigned a key. To learn more about keys, you can read about them in Kinds and Identifiers.

Categories