基于flask的microBlog开发笔记(四)

4.用户登录

1.配置

对于登录系统,使用到扩展:Flask-Login。配置情况如下(app/init.py)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#-*- coding:utf-8 -*-


from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
from flask.ext.login import LoginManager


# 初始化flask应用
app = Flask(__name__)
app.config.from_object('config')

# 初始化数据库
db = SQLAlchemy(app)

# 初始化flask-Login
lm = LoginManager()
lm.setup_app(app)

from app import views, models

2.重构用户模型

Flask-Login 扩展需要在我们的 User 类中实现一些特定的方法,但是类如何去实现这些方法却没有什么要求,让我们为 Flask-Login 实现的 User 类(app/models.py)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
nickname = db.Column(db.String(15), index=True, unique=True)
email = db.Column(db.String(128), index=True, unique=True)
role = db.Column(db.SmallInteger, default=ROLE_USER)
posts = db.relationship('Post', backref='author', lazy='dynamic')

def is_authenticated(self):
return True

def is_active(self):
return True

def is_anonymous(self):
return False

def get_id(self):
return unicode(self.id)

@classmethod
def login_check(cls, user_name):
user = cls.query.filter(db.or_(
User.nickname == user_name, User.email == user_name)).first()

if not user:
return None

return user

def __repr__(self):
return '<User %r>' % (self.nickname)

is_authenticated 方法有一个具有迷惑性的名称。一般而言,这个方法应该只返回 True,除非表示用户的对象因为某些原因不允许被认证;is_active 方法应该返回 True,除非是用户是无效的,比如因为他们的账号被禁止;is_anonymous 方法应该返回 True,除非是伪造的用户不允许登录系统;get_id 方法应该返回一个用户唯一的标识符,以 unicode 格式返回我们使用数据库生成的唯一的id。

3.user_loader 回调

我们已经准备好用 Flask-Login 扩展来开始实现登录系统。
首先,我们必须编写一个函数用于从数据库加载用户,这个函数将会被 Flask-Login 使用(app/views.py)

1
2
3
@lm.user_loader
def load_user(user_id):
return User.query.get(int(user_id))

Flask-Login 中的用户的id永远是 unicode 字符串,因此在我们把id 发送给 Flask-SQLAlchemy 之前,需要把id转成整型是必须的,否则会报错。

4.登陆视图函数(app/views.py)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#-*- coding:utf-8 -*-

import datetime

from flask import render_template, flash, redirect, session, url_for, request, g
from flask.ext.login import login_user, logout_user, current_user, login_required

from models import User, Post, ROLE_USER, ROLE_ADMIN
from app import app, db, lm


@lm.user_loader
def load_user(user_id):
return User.query.get(int(user_id))

... # 这里省略的是我们的index函数


@app.route('/login', methods=['GET', 'POST'])
def login():
# 验证用户是否被验证
if current_user.is_authenticated():
return redirect('index')
# 注册验证
form = LoginForm()
if form.validate_on_submit():
user = User.login_check(request.form.get('user_name'))
if user:
login_user(user)
user.last_seen = datetime.datetime.now()

try:
db.session.add(user)
db.session.commit()
except:
flash("The Database error!")
return redirect('/login')

flash('Your name: ' + request.form.get('user_name'))
flash('remember me? ' + str(request.form.get('remember_me')))
return redirect(url_for("users", user_id=current_user.id))
else:
flash('Login failed, Your name is not exist!')
return redirect('/login')

return render_template(
"login.html",
title="Sign In",
form=form)

整个流程就是,验证用户,验证用户是否已经注册,如果注册则从数据库中加载用户并转到用户页面。如果要让这些都起作用的话,Flask-Login 需要知道哪个视图允许用户登录。我们在应用程序模块初始化中配置(app/init.py)

1
2
lm = LoginManager()
lm.setup_app(app)

5.首页视图

前面我们的 index 视图函数使用了伪造的对象,因为那时候我们并没有用户或者 blog。现在我们有用户了,让我们使用它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@app.route('/')
@app.route('/index')
def index():
user = 'Man'
posts = [
{
'author': {'nickname': 'John'},

'body': 'Beautiful day in Portland!'
},

{
'author': {'nickname': 'Susan'},

'body': 'The Avengers movie was so cool!'
}
]
return render_template(
"index.html",
title="Home",
user=user,
posts=posts)

6.登录

我们已经实现了登录,现在增加登陆的功能,即对登录视图函数进行修改(app/views.py)

1
2
3
4
5
@app.route('/logout')
@login_required
def logout():
logout_user()
return redirect(url_for('index'))

其中login_required是为了验证用户必须是登陆的前提,才会有登出。

7.注册

  • 再注册前,我们需要修改app/forms.py文件以绑定数据库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# -*- coding:utf-8 -*-

from flask_wtf import Form
from wtforms import TextField, BooleanField, SubmitField, TextAreaField
from wtforms.validators import Required, Email, Length


class LoginForm(Form):
user_name = TextField('user name', validators=[
Required(), Length(max=15)])
remember_me = BooleanField('remember me', default=False)
submit = SubmitField('Log in')

class SignUpForm(Form):
user_name = TextField('user name', validators=[
Required(), Length(max=15)])
user_email = TextField('user email', validators=[
Email(), Required(), Length(max=128)])
submit = SubmitField('Sign up')

在这里添加了类SignUpForm,用户的用户名和邮件的注册提交

  • 接着,实现用户注册视图(app/views.py)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    from forms import LoginForm, SignUpForm


    @app.route('/sign-up', methods=['GET', 'POST'])
    def sign_up():
    form = SignUpForm()
    user = User()
    if form.validate_on_submit():
    user_name = request.form.get('user_name')
    user_email = request.form.get('user_email')

    register_check = User.query.filter(db.or_(
    User.nickname == user_name, User.email == user_email)).first()
    if register_check:
    flash("error: The user's name or email already exists!")
    return redirect('/sign-up')

    if len(user_name) and len(user_email):
    user.nickname = user_name
    user.email = user_email
    user.role = ROLE_USER
    try:
    db.session.add(user)
    db.session.commit()
    except:
    flash("The Database error!")
    return redirect('/sign-up')

    flash("Sign up successful!")
    return redirect('/index')

    return render_template(
    "sign_up.html",
    form=form)

在提交注册信息的时候验证数据库中是否已经注册该用户信息,如果没有注册则在数据库中提交该信息,并显示注册成功,转到首页。

  • 在修改主页index.html

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    {% extends "base.html" %}

    {% block content %}

    {% if not current_user.is_authenticated() %}
    <h1>Hi, Guys!</h1>
    {% else %}
    <h1>Welcome back, {{ current_user.nickname }}!</h1>
    {% endif %}

    {% for post in posts %}
    <p>{{ post.author.nickname }} says: <b>{{ post.body }}</b></p>
    </div>
    {% endfor %}
    {% endblock %}
  • 修改登录模版(login.html)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{% extends "base.html" %}

{% block content %}
<h1>Sign Up</h1>
<form action="/login" method="post" name="login">
{{ form.hidden_tag() }}
<p>Please enter your name: {{ form.user_name }}</p>
{% for error in form.errors.user_name %}
<p style="color:red;">[-] {{ error }}</p>
{% endfor %}

<p>记住我? {{ form.remember_me }}</p>
<p>{{ form.submit }}</p>

</form>
{% endblock %}

当现在如果运行程序的话,肯定会说用户名不存在,因为还需要建立一个注册模版(sign_up.html)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{% extends "base.html" %}

{% block content %}

<form action="/sign-up", method="POST" name="sign_up">
{{ form.hidden_tag() }}
<p>Nick name: {{ form.user_name }}</p>
{% for error in form.errors.user_name %}
<p style="color:red;">[-] {{ error }}</p>
{% endfor %}

<p>E-mail: {{ form.user_email }}</p>
{% for error in form.errors.user_email %}
<p style="color:red;">[-] {{ error }}</p>
{% endfor %}

<p>{{ form.submit }}</p>
</form>

{% endblock %}

但是还没有在模版中添加登出和注册的链接。将要把这个链接放在基础层中的导航栏里(app/templates/base.html)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<html>
<head>
{% if title %}
<title>{{title}} - microblog</title>
{% else %}
<title>microblog</title>
{% endif %}
</head>
<body>
<div>Microblog:
<a href="{{ url_for('index') }}">Home</a>
{% if not current_user.is_authenticated() %}
| <a href="{{ url_for('login') }}">Log in</a>
or <a href="{{ url_for('sign_up') }}">Sign up</a>
{% else %}
| <a href="{{ url_for('logout') }}">Logout</a>
{% endif %}
</div>
<hr />
{% with messages = get_flashed_messages() %}
{% if messages %}
<ul>
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</body>

{% block js %}{% endblock %}
</html>