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:password@localhost: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可以对字段进行加密解密。对于账户、用户名是否已被用过,就是把表单输入的内容跟数据库中字段最匹配,看是否已存在。

你可能感兴趣的:(学习笔记)