Review
In the previous series, we created a database and learned to populate it with users and emails, but we haven't been able to implant into our programs yet. Before two chapters, we've seen how to create a Web form and leave a full login form.
In this article, we will build and implement our own user login system based on the Web Forms and databases we have learned. At the end of the tutorial we will implement new user registration, login and exit functions.
To keep up with this chapter, you need the last part of the previous chapter, the microblog program we left behind. Please make sure your program is installed and running correctly.
In the previous section, we started to configure the flask extensions we're going to use. To login to the system, we will use two extensions, Flask-login and Flask-openid. The configuration looks like this (fileapp\__init__.py):
Import OS from
flaskext.login import Loginmanager to
Flaskext.openid import OpenID from
config import Basedir
lm = Loginmanager ()
Lm.setup_app (APP)
oid = OpenID (app, Os.path.join (Basedir, ' tmp '))
Flask-openid extensions to allow temporary files to be stored, a temporary folder path is required. To this end, we have provided its location.
Revisit our user model
Flask-login extensions need to implement some of the methods in our user class. In addition to these methods, classes are not required to implement other methods.
The following is our user class (fileapp/models.py):
Class User (db. Model):
ID = db. Column (db. Integer, Primary_key = True)
nickname = db. Column (db. String (+), unique = True)
email = db. Column (db. String (), unique = True) role
= db. Column (db. Smallinteger, default = Role_user)
posts = db.relationship (' Post ', backref = ' author ', lazy = ' dynamic ')
def Is_au Thenticated (self): return
true
def is_active (self): return
true
def is_anonymous (self):
return False
def get_id (self): return
Unicode (self.id)
def __repr__ (self): return
' <user%r > '% (self.name)
The Is_authenticated method is a misleading method of name, and usually this method should return true unless the object represents a user who, for some reason, is not authenticated.
The Is_active method should return true for the user unless the user is not active, for example, they have been banned.
The Is_anonymous method should return true for those users who are not allowed to log on.
Finally, the Get_id method returns a unique Unicode identifier for the user. We use the database layer to generate a unique ID.
User Load Callback
Now we implement the login system by using Flask-login and Flask-openid extensions
First, we need to write a method to load a user from the database. This method will be used by Flask-login (fileapp/views.py):
@lm. User_loader
def load_user (ID): return
User.query.get (int (ID))
Remember that the user ID in Flask-login is always a Unicode type, so when we pass the ID to Flask-sqlalchemy, it is necessary to convert it to an integer type.
Login View function
Next we want to update the login view function (fileapp/views.py):
From flask import render_template, Flash, redirect, sessions, Url_for, request, G from
flaskext.login import Login_user , Logout_user, Current_User, login_required from
app Import app, DB, LM, OID to
forms import LoginForm
from Models import User, Role_user, Role_admin
@app. Route ('/login ', methods = [' Get ', ' POST '])
@oid. Loginhandler
def login ():
if G.user isn't None and g.user.is_authenticated (): Return
Redirect (url_for (' index '))
form = LoginForm ()
if Form.validate_on_submit ():
session[' remember_me '] = Form.remember_me.data
Return Oid.try_login (form.openid.data, ask_for = [' nickname ', ' email ']) return
render_template (' login.html ',
title = ' Sign in ',
form = form,
providers = app.config[' Openid_providers ']
Note that we have imported some new modules, some of which will be used later.
The changes with the previous version are very small. We added a new adorner to the view function: Oid.loginhandler. It tells Flask-openid that this is our login view function.
At the beginning of the method body, we detect whether the user is already authenticated, and if so, redirect to the index page. The idea here is that if a user is already logged in, then we won't let it log in two times.
Global variable g is a variable that is set by flask and used to store and share data during a request lifecycle. So I guess you already think that we're going to put the logged-in user in the G variable.
The Url_for () method we use when calling redirect () is flask defined to get the URL from the given view method. If you want to redirect to the index page, you h probably use redirect ('/index '), but we have good reason to let flask construct the URL for you.
When we get the returned data from the login form, the next code to run is also new. Here we do two things. First we save the Boolean value of the Remember_me to the flask session, and don't confuse the Flask-sqlalchemy db.session. We already know that the Flask G object is used to save and share data in the life cycle of a request. The flask session along this line provides more and more complex services. Once the data is saved to the session, it will remain in the same client's request and later in the request and not perish. The data will remain in session until it is explicitly removed. In order to do this, flask each client to establish their own session.
The following oid.try_login is performed through Flask-openid to perform user authentication. This method has two parameters, and the Web Form provides the OpenID and OpenID provider we want for the list data item. Since we have defined the user class that contains nickname and email, we are looking for nickname and email items.
The authentication based on OpenID is asynchronous. If the authentication succeeds, Flask-openid will invoke a method that is registered by the Oid.after_login adorner. If the authentication fails then the user is redirected to the login page.
Flask-openid Login Callback
This is the After_login method we have achieved (app/views.py)
@oid. After_login
def after_login (resp):
if Resp.email is None or Resp.email = "":
Flash (' Invalid login. Please try again. ')
Redirect (Url_for (' login '))
user = User.query.filter_by (email = resp.email). The
if user is None:
Nickname = Resp.nickname
if nickname is None or nickname = = "":
nickname = Resp.email.split (' @ ') [0]
user = Us ER (nickname = nickname, email = resp.email, role = Role_user)
db.session.add (USER)
db.session.commit ()
Remember_me = False
if ' Remember_me ' in session:
Remember_me = session[' Remember_me ']
session.pop (' Remember_me ', None)
login_user (user, remember = remember_me) return
redirect (Request.args.get (' Next ') or Url_for (' index '))
The RESP parameter passed to the After_login method contains some information returned by the OpenID provider.
The first if declaration is only for validation. We asked for a valid email, so we couldn't get him to log in without an email.
Next, we will look up the database according to email. If the email is not found we think it is a new user, so we will add a new user to the database, as we learned from the previous chapters. Note that we did not process nickname because some OpenID provider did not contain this information.
By doing this we will get the value of Remember_me from the flask session, and if it exists, it is the Boolean value that we previously saved to the session in the login view method.
Then we call Flask-login's Login_user method to register this valid login.
Finally, we redirect to the next page on the last line, or we redirect to the index page if the next page is not available in the request.
The concept of jumping to the next page is simple. For example we need you to log in to navigate to a page, but you are not logged in now. In Flask-login you can use the login_required adorner to qualify users who are not logged in. If a user wants to connect to a qualified URL, he will be automatically redirected to the login page. Flask-login will save the original URL as the next page, and once the login is complete we will jump to this page.
To do this job flask-login need to know the user is currently on that page. We can configure it in the initialization component of the app (app/__init__.py):
LM = Loginmanager ()
Lm.setup_app (APP)
lm.login_view = ' Login '
Global variable G.user
If you are focused, then you should remember that in the Login view method We check G.user to determine if a user is logged in. To achieve this we will use the Before_request event provided by flask. Any method decorated by a before_request adorner will be executed in advance of the view method each time the request is received. So here's where to set our G.user variable (app/views.py):
@app. Before_request
def before_request ():
g.user = Current_User
That's all it takes, the CURRENT_USER global variable is set by Flask-login, so we just have to copy it to a more accessible g-variable and OK. In this way, all requests can access the logged-in user, or even the internal template.
Index view
In the previous chapters we left our index view with fake code because there were no user and blog posts in our system at that time. Now we have the user, so let's finish it:
@app. Route ('/')
@app. Route ('/index ')
@login_required
def index ():
user = G.user
posts = [
{
' author ': {' nickname ': ' John '},
' body ': ' beautiful ', ' portland! '
},
{
' author ': {' Nickname ': ' Susan '},
' body ': ' The Avengers movie is so cool! '
}
Return render_template (' index.html ',
title = ' Home ',
user = user,
posts = posts)
There are only two changes in this method. First, we added a login_required adorner. This indicates that the page is accessible only to logged-in users.
Another change was to pass the G.user to the template, replacing the dummy object.
Now it's time to run our apps.
When we connect to http://localhost:5000 you will see the landing page. Remember that if you log on through OpenID then you must use the OpenID URL provided by your provider. You can create a correct URL for you by any of the OpenID provider in the URL below.
As part of the login process, you will be redirected to the OpenID provider's website, where you will authenticate and authorize you to share some of the information that is applied to us (we only need email and nickname, and rest assured that there will be no password or other personal information being exposed).
Once the login is complete you will be taken to the index page as a logged-in user.
Try the Remember_me check box. With this option, you are still logged on when you reopen the browser when it is closed.
Logout Login
We have implemented the login, now is the time to implement logout login.
The method for Unregistering logins is often simple (file app/views.py):
@app. Route ('/logout ')
def logout ():
logout_user () return
redirect (url_for (' index '))
But we don't have a link to log off in the template yet. We will add this link (file app/templates/base.html) to the top navigation bar in base.html:
How simple it is, we just need to check if there is a valid user in the G.user, and if so, add the logout link. In our template we have once again used the Url_for method.
Last Words
we now have a fully functional user login system. In the next chapter, we will create the user's profile page and display the user's avatar.
In the meantime, here is the updated application code, including all the changes in this article:
Download Microblog-0.5.zip.