4,103

Flask项目搭建——实现登录、注册(邮箱验证)

在学习当中,即便是模仿书本一字一句的编写代码(因为我还是小白,哈哈),虽然才接触flask不久,但是该框架的轻便,具有丰富的三方库,深深的吸引的喜欢,接下来我就要把自己完成的一个模拟登陆以及邮箱注册验证的实例总结一下(直接上源码)。

一、项目结构:

|--mytest
  |--app/
    |--__init__.py--初始化
    |--models.py--模型文件
  	|--auth/
  	   |--__init__.py--初始化
  	   |--forms.py--auth表单文件
  	   |--views.py--auth视图文件
  	|--main/
  	   |--__init__.py--main初始文件
  	   |--views.py--视图文件
  	|--static/--样式文件
  	|--templates/--模板文件
  |--venv--虚拟环境
  |--config.py--配置文件
  |--emailTest.py--发送邮件文件
  |--etc.py--默认文件
  |--manage.py--启动文件
  |--requirements.txt--三方库集合

1、配置文件:

# -*- coding:utf-8 -*-

class Config(object):
    '''
        1 加密秘钥
        2 自动提交数据库
    '''
    SECRET_KEY = "Ay98Cct2oNSlnHDdTl8"
    SQLALCHEMY_COMMIT_ON_TEARDOWN = True
    # SQLALCHEMY_TRACK_MODIFICATIONS = True
    @staticmethod
    def init_app(app):
        pass

class LastConfig(Config):
    '''
        调试
        连接数据库
    '''
    DEBUG = True
    #SQLALCHEMY_ECHO = True
    #连接数据库、其中username为你的登录的用户名,password则为登录密码
    SQLALCHEMY_DATABASE_URI = "mysql+pymysql://username:[email protected]:3306/test_three"

config = {'default':LastConfig, 'test':TestConfig}

2、启动项

# -*- coding:utf-8 -*-

from app import create_app, db
from flask_script import Manager, Shell
from etc import default
#创建app
app = create_app(default)
#实例化Manager对象
manager = Manager(app)

def make_shell_context():
    return dict(app=app, db=db)
#调试模式
manager.add_command("shell", Shell(make_context=make_shell_context))
#启动程序
if __name__ == "__main__":
    manager.run()

3、三方库集合requirements.txt

alembic==0.8.8
blinker==1.4
click==6.6
dominate==2.2.1
Flask==0.11.1
Flask-Bootstrap==3.3.7.0
Flask-Mail==0.9.1
Flask-Migrate==2.0.0
Flask-Moment==0.5.1
Flask-Script==2.0.5
Flask-SQLAlchemy==2.1
Flask-WTF==0.12
itsdangerous==0.24
Jinja2==2.8
Mako==1.0.4
MarkupSafe==0.23
python-editor==1.0.1
SQLAlchemy==1.0.15
visitor==0.1.3
Werkzeug==0.11.11
WTForms==2.1
Flask-Login==0.3.1 

一键安装插件库:

pip install -r requirements.txt

4、发送邮箱配置文件

# -*- coding:utf-8 -*-
from flask import Flask,render_template
from flask_mail import Mail, Message
app = Flask(__name__)
'''
	1 邮箱服务
        2 邮箱端口
        3 发送邮箱
        4 邮箱授权码
	'''
app.config.update(
	MAIL_SERVER='smtp.qq.com',
	MAIL_PORT='465',
	MAIL_USE_SSL=True,
	MAIL_USERNAME='QQ号',
	MAIL_PASSWORD='授权码'#(可在邮箱设置中获取)
	)
mail = Mail(app)

def send_email(to,subject,template,user,token):
	'''
		1 实例化Message对象
		2 设置发送邮件的内容
		3 发送邮件
	'''
	msg = Message(subject, sender='发送文件的QQ邮箱地址', recipients=[to])
	msg.html = render_template(template + '.txt', user=user,token=token)
	mail.send(msg)

5、虚拟环境:前面已将介绍了,在cmd中打开创建虚拟环境的根目录,通过执行命令virtualenv venv(虚拟环境名,自定义)来创建:

# 创建虚拟环境
virtualenv venv
或
python -m venv venv

6、app:即整个应用的核心,我是手动创建的文件夹,并且命名为app.
6.1、初始文件:其中包括工厂函数(create_app(config)),其中的参数就是配置文件里的配置名、数据库实例化数据库、邮箱、时间格式、bootstrap等。

from flask import Flask
from flask_bootstrap import Bootstrap
from flask_mail import Mail
from flask_moment import Moment
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
from config import config
#实例化各应用类
bootstrap = Bootstrap()
mail = Mail()
moment = Moment()
db = SQLAlchemy()

login_manager = LoginManager()
login_manager.session_protection = 'strong'
login_manager.login_view = 'auth.login'


def create_app(config_name):
    app = Flask(__name__)
    app.config.from_object(config[config_name])
    config[config_name].init_app(app)

    bootstrap.init_app(app)
    mail.init_app(app)
    moment.init_app(app)
    db.init_app(app)
    login_manager.init_app(app)
    #分别导入蓝图,并注册
    from .main import main as main_blueprint
    app.register_blueprint(main_blueprint)

    from .auth import auth as auth_blueprint
    app.register_blueprint(auth_blueprint, url_prefix='/auth')

    return app

6.2、数据库模型(models):

from werkzeug.security import generate_password_hash, check_password_hash
from flask_login import UserMixin
###从flask_login导入UserMixin类
###USerMixin类包含的以上四种方法的默认实现。
from . import db, login_manager
###从程序的工厂函数引入login_manager实例
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
##导入生成令牌函数
from flask import current_app


class Role(db.Model):
    '''

    1 添加角色表、角色id、角色名

    '''
    __tablename__ = 'roles'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True)
    users = db.relationship('User',backref='role',lazy='dynamic')

    def __repr__(self):
        return '' % self.name


class User(UserMixin, db.Model):
    '''

        1 User继承UserMixin和db.Model类的功能属性
        2 建表users、id、email、username、role_id、password_hash(密码加密字段),confirmed(账户确认)
        3 密码属性、加密密码
        4 加载用户的回调函数接收以Unicode字符串形式表示的用户标示符
        5 如果能找到用户,这个函数必须返回用户对象,否则返回None。

    '''
    __tablename__ = 'users'
    
    id = db.Column(db.Integer, primary_key=True)
    
    email = db.Column(db.String(64), unique=True, index=True)
   
    username = db.Column(db.String(64),unique=True, index=True)

    role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))

    password_hash = db.Column(db.String(128))

    confirmed = db.Column(db.Boolean,default=False)


    @property
    def password(self):
        raise AttributeError('password is not a readable attribute')

    @password.setter
    def password(self,password):
        self.password_hash = generate_password_hash(password)

    def verify_password(self, password):
        return check_password_hash(self.password_hash, password)

    def __repr__(self):
        return '' % self.username

    #expiration为令牌失效时长,定义生成令牌函数
    
    def generate_confirmation_token(self,expiration=3600):
        '''
            加密确认码
        '''
        s = Serializer(current_app.config['SECRET_KEY'],expiration)
        return s.dumps({'confirm':self.id})

    def confirm(self,token):
        '''
            解密确认码
        '''
        s = Serializer(current_app.config['SECRET_KEY'])
        try:
            data=s.loads(token)
        except:
            return False
        if data.get('confirm') != self.id:
            return False
        self.confirmed = True
        db.session.add(self)
        return True 


@login_manager.user_loader

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

6.3、用户文件(auth):(包含初始文件(创建蓝图)、forms文件(创建表单)、views文件(视图函数)).
6.3.1、用户初始化(创建蓝图):

from flask import Blueprint
auth = Blueprint('auth',__name__)
from . import views

6.3.2、forms文件(创建表单):

from flask_wtf import Form
from wtforms import StringField, PasswordField, BooleanField, SubmitField
from wtforms.validators import Required, Length, Email,Regexp,EqualTo
from wtforms import ValidationError
from ..models import User


#表单login
class LoginForm(Form):
	'''
		1 登录表单。StringField构造函数中的可选参数validators指定一个有验证函数组成的列表,在接受用户提交的数据之前验证数据。
		2 电子邮件字段用到了WTForms提供的Length()和Email()验证函数。
		3 PasswordField类表示属性为type="password"的< input>元素。
		4 BooleanField类表示复选框。
	'''
	email = StringField('Email', validators=[Required(), Length(1, 64), Email()])
    
    
	password = PasswordField('密码', validators=[Required()]) 
    
	remember_me = BooleanField('记住密码')

	submit = SubmitField('登录')

#表单注册Registration
class RegistrationForm(Form):
	'''
		1 注册表单
		2 填写表单时的格式限制(输入邮箱、输入用户名、输入密码、确认密码、注册按钮)
		3 验证账号是否被注册过
		4 验证用户名是否被注册过
	'''
	
	
	email = StringField('Email',validators=[Required(),Length(1,64),Email()])
	
	username = StringField('用户名',validators=[Required(),Length(1,64),Regexp('^[A-Za-z][A-Za-z0-9_.]*$',\
		0,'Usernames must have only letter,numbers,dots or underscores')])
	
	password = PasswordField('密码',validators=[Required(),EqualTo('password2',message='两次密码必须一致.')])
	
	password2 = PasswordField('确认密码',validators=[Required()])
	
	submit = SubmitField('注册')
	
	def validate_email(self,field):

		if User.query.filter_by(email=field.data).first():

			raise ValidationError('Email 已注册过.')

	
	def validata_username(self,field):

		if User.query.filter_by(username=field.data).first():

			raise ValidationError('用户名已存在.')

6.3.3、views视图函数:

from flask import render_template, redirect, request, url_for, flash
from flask_login import login_user, logout_user, login_required 
from . import auth 
from ..models import User 
from .forms import LoginForm
from .forms import RegistrationForm
from .. import db
from emailTest import send_email
from flask.ext.login import current_user

@auth.route('/login', methods=['GET', 'POST'])
###当请求为GET时,直接渲染模板,当请求是POST提交时,验证表格数据,然后尝试登入用户。
def login():
    flash('账户或者密码不正确.')
    '''
        1 实例化表单
        2 表格中填入了数据,执行下面操作
        3 视图函数使用表单中填写的email加载用户
        4 如果user不是空的,而且验证表格中的密码正确,执行下面的语句,调用Flask_Login中的login_user()函数,在用户会话中把用户标记为登录
        5 否则直接执行flash消息和跳转到新表格中。
        6 login_user函数的参数是要登录的用户,以及可选的‘记住我’布尔值。
        7 用户访问未授权的url时会显示登录表单,Flask-Login会把原地址保存在查询字符串的next参数中,这个参数可从request.args字典中读取。如果查询字符串中没有next参数,则重定向到首页。
    '''
    form = LoginForm()
    if form.validate_on_submit():

        user = User.query.filter_by(email=form.email.data).first() 

        if user is not None and user.verify_password(form.password.data):

            login_user(user, form.remember_me.data)

            return redirect(request.args.get('next') or url_for('main.index'))

        flash('账户或者密码不正确.')

    return render_template('auth/login.html', form=form)


@auth.route('/logout') 
###退出路由
@login_required 
###用户要求已经登录
def logout():
    '''
       1 登出用户,这个视图函数调用logout_user()函数,删除并重设用户会话。 

    '''
    logout_user()

    flash('你已经退出登录!')

    return redirect(url_for('main.index'))

###用户注册
@auth.route('/register',methods=['GET','POST'])
def register():
    '''
        1 实例化注册表单
        2 判断是否提交表单,并对数据库进行操作,如果是则发送账户确认邮件给注册邮箱,且重定向到首页
    '''
    form = RegistrationForm()

    if form.validate_on_submit():

        user = User(email=form.email.data,username=form.username.data,password=form.password.data)

        db.session.add(user)

        db.session.commit()

        token = user.generate_confirmation_token()

        send_email(user.email,'确认你的账户','auth/email/confirm',user,token)

        flash('账号验证已发送邮箱!')

        return redirect(url_for('main.index'))

        # return redirect(url_for('auth.login'))
        ##重定向到登录页面
    return render_template('auth/register.html',form=form)

#确认账户    
@auth.route('/confirm/')
@login_required
def confirm(token):
    '''
        1 判断数据库的confirmed字段是否为True,并以flash消息提示账户是否已确认,并重定向到首页
    '''
    if current_user.confirmed:

        return redirect(url_for('main.index'))

    if current_user.confirm(token):

        flash('你已经确认了你的账户,谢谢')

    else:
        flash('这个确认链接不可用,或已超时')

    return redirect(url_for('main.index'))

@auth.before_app_request
def before_request():
    '''
        如果返回响应或重定向,会直接发送至客户端,不会调用请求视图函数
    '''
    if current_user.is_authenticated \
            and not current_user.confirmed \
            and request.endpoint[:5] != 'auth.'\
            and request.endpoint != 'static':

        return redirect(url_for('auth.unconfirmed'))

#尚未确认账户
@auth.route('/unconfirmed')
def unconfirmed():
    '''
        判断数据库的confirmed字段是否为True,如果为False(0),就跳转到提示确认账户界面
    '''
    if current_user.is_anonymous or current_user.confirmed:

        return redirect(url_for('main.index'))

    return render_template('auth/unconfirmed.html')

#重新发送账户确认邮件
@auth.route('/confirm')
@login_required
def resend_confirmation():

    token = current_user.generate_confirmation_token()

    send_email(current_user.email, '确认你的账户',
               'auth/email/confirm', current_user, token)

    flash('新确认账户邮件已发送到邮箱,注意查收.')

    return redirect(url_for('main.index'))

6.4、main文件:(初始文件也是蓝图创建)、views视图函数.
6.4.1、初始函数:

from flask import Blueprint

main = Blueprint('main', __name__)

from .views import *

6.4.2、视图函数:

from . import main
from .. import auth
from flask import render_template, request
from app import db
from sqlalchemy import or_
from flask import redirect, url_for


@main.route('/')
def index():
    # return render_template('index.html')
     return redirect(url_for('auth.login'))

@main.route('/index')
def index1():
    return render_template('index.html')

7.静态样式文件:(主要包括样式(css、js等))
8.静态模板文件:(主要是HTML模板)

最后,根据蓝图,视图函数,把各个模块以及模板串在一起,这样吃能实现点击注册是,将表单添加的资料写入数据库,并同时发送邮箱验证(初始是否验证字段在数据库的默认值为0(Flase),当用户点击发送到自己的邮箱的验证链接,并更改数据的字段为1(True),还有就是秘密加密,flask有一个模块对于加密数据很方便,就是itsdangerous包里的TimedJSONWebSignatureSerializer可以对字段进行加密解密。对于账户、用户名是否已被用过,就是把表单输入的内容跟数据库中字段最匹配,看是否已存在。

Flask项目搭建——实现登录、注册(邮箱验证)》有1个想法

发表评论

邮箱地址不会被公开。 必填项已用*标注