程序员flask高级编程-鱼书FLASK入门

慕课网Flask高级编程实战-8.用户登录与注册

2018-06-13  本文已影响33人  9c0ddf06559c

专题章节目录

慕课网Flask高级编程实战-知识点思维导图

慕课网Flask高级编程实战-1.项目准备 和 Flask入门

慕课网Flask高级编程实战-2.搜索书籍路由编写

慕课网Flask高级编程实战-3.蓝图、模型与CodeFirst

慕课网Flask高级编程实战-4.flask核心机制

慕课网Flask高级编程实战-5.书籍详情页面的构建

慕课网Flask高级编程实战-6.书籍详情页面的构建

慕课网Flask高级编程实战-7.静态文件、模板、消息闪现与Jinja2

慕课网Flask高级编程实战-8.用户登录与注册

慕课网Flask高级编程实战-9.书籍交易模型(数据库事务、重写Flask中的对象)

慕课网Flask高级编程实战-10.鱼书业务处理

慕课网Flask高级编程实战-11.Python与Flask的结合应用

慕课网Flask构建可扩展的RESTful API-1. 起步与红图

慕课网Flask构建可扩展的RESTful API-2. REST基本特征

慕课网Flask构建可扩展的RESTful API-3. 自定义异常对象

慕课网Flask构建可扩展的RESTful API-5. Token与HTTPBasic验证 —— 用令牌来管理用户

慕课网Flask构建可扩展的RESTful API-6. 模型对象的序列化

慕课网Flask构建可扩展的RESTful API-7. 权限控制

8.1 viewmodel意义的体现与filter函数的巧妙应用

在搜索书籍页面里,需要将每一条结果的作者,出版社,价格在一行展示,并以”/“分割。由于这三个属性还有可能为空,所以在html模板里处理不太方便。我们选择将这些数据处理的工作放在viewmodel中。

简单粗暴一点的方法是写一段 if-else 代码,将这三个属性以及可能为空的情况全都穷举出来,但是python给我们提供了更优雅的解决方式,就是使用filter过滤器+lambda表达式

class BookViewModel:

def __init__(self, book):
self.title = book['title'],
self.publisher = book['publisher'],
self.pages = book['pages'],
self.author = '、'.join(book['author']),
self.price = book['price'],
self.summary = book['summary'],
self.image = book['image']

# @property注解可以让我们把一个方法当做一个属性来使用
@property
def intro(self):
intros = filter(lambda x: True if x else False,
[self.author[0], self.publisher[0], self.price[0]])

return ' / '.join(intros)

8.2 书籍详情页面

1.业务逻辑梳理

2.编写思路

3.实现代码

web/book.py

@web.route("/book/<isbn>/detail")
def book_detail(isbn):
yushu_book = YuShuBook()
yushu_book.search_by_isbn(isbn)
book = BookViewModel(yushu_book.first)
return render_template("book_detail.html", book=book, wishes=[], gifts=[])

spider/yushu_book.py

@property
def first(self):
return self.books[0] if self.total >= 1 else None

view_models/book.py


class BookViewModel:

def __init__(self, data):
self.title = data['title']
self.author = '、'.join(data['author'])
self.binding = data['binding']
self.publisher = data['publisher']
self.image = data['image']
self.price = '¥' + data['price'] if data['price'] else data['price']
self.isbn = data['isbn']
self.pubdate = data['pubdate']
self.summary = data['summary']
self.pages = data['pages']

@property
def intro(self):
intros = filter(lambda x: True if x else False,
[self.author, self.publisher, self.price])
return ' / '.join(intros)

8.3 模型关系

分析业务逻辑,用户赠送书籍,需要将用户赠送书籍的数据保存到数据库中。为此我们需要建立业务模型,并通过codeFirst的原则,反向生成数据库表

1.模型与模型关系

2.编写建立模型代码

models/user.py


from sqlalchemy import Column
from sqlalchemy import Integer, Float
from sqlalchemy import String, Boolean

from app.models.base import db


class User(db.Model):
id = Column(Integer, primary_key=True)
nickname = Column(String(24), nullable=False)
phone_number = Column(String(18), unique=True)
email = Column(String(50), unique=True, nullable=False)
confirmed = Column(Boolean, default=False)
beans = Column(Float, default=0)
send_counter = Column(Integer, default=0)
receive_counter = Column(Integer, default=0)
wx_open_id = Column(String(50))
wx_name = Column(String(32))

models/gift.py

from app.models.base import db
from sqlalchemy import Column, String, Integer, ForeignKey, Boolean
from sqlalchemy.orm import relationships

__author__ = "gaowenfeng"


class Gift(db.Model):
id = Column(Integer, primary_key=True, autoincrement=True)
# relationships表示管理关系
user = relationships('User')
# ForeignKey定义外键约束
uid = Column(Integer, ForeignKey('user.id'))
# 书籍我们记录isbn编号,因为书籍信息是从网络获取的
isbn = Column(String(15),nullable=True)
# 是否已经赠送出去
launched = Column(Boolean, default=False)

3.自定义基类模型

base.py

from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import SmallInteger, Column

__author__ = "gaowenfeng"

db = SQLAlchemy()


class Base(db.Model):
__abstract__ = True
status = Column(SmallInteger, default=1)

8.4 用户注册

web/auth.py

@web.route('/register', methods=['GET', 'POST'])
def register():
form = RegisterForm(request.form)
if request.method == 'POST' and form.validate():
user = User()
user.set_attrs(form.data)

db.session.add(user)
db.session.commit()

return render_template('auth/register.html', form={'data': {}})

models/user.py

class User(Base):
id = Column(Integer, primary_key=True)
nickname = Column(String(24), nullable=False)
_password = Column('password', String(128))
phone_number = Column(String(18), unique=True)
email = Column(String(50), unique=True, nullable=False)
confirmed = Column(Boolean, default=False)
beans = Column(Float, default=0)
send_counter = Column(Integer, default=0)
receive_counter = Column(Integer, default=0)
wx_open_id = Column(String(50))
wx_name = Column(String(32))

@property
def password(self):
return self._password

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

models/base.py

db = SQLAlchemy(query_class=Query)


class Base(db.Model):
__abstract__ = True
status = Column(SmallInteger, default=1)

def set_attrs(self, attrs_dict):
for key, value in attrs_dict.items():
if hasattr(self, key) and key != 'id':
setattr(self, key, value)

forms/auth.py

class RegisterForm(Form):
email = StringField(validators=[
DataRequired(), Length(8, 64, message='电子邮箱不符合规范')])

nickname = StringField('昵称', validators=[
DataRequired(), Length(2, 10, message='昵称至少需要两个字符,最多10个字符')])

password = PasswordField('密码', validators=[
DataRequired(), Length(6, 20)])
def validate_email(self, field):
# User.query.filter_by(email=field.data).first() 等同于 select * from user where name = field.data limit 1
if User.query.filter_by(email=field.data).first():
raise ValidationError('电子邮件已被注册')

def validate_nickname(self, field):
if User.query.filter_by(nickname=field.data).first():
raise ValidationError('昵称已存在')

8.5 用户登录

web/auth.py

...
...
@web.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm(request.form)
if request.method == 'POST' and form.validate():
user = User.query.filter_by(email=form.email).first()
if user and user.check_password(user.password):
# 使用flask-login 的 login_user间接写入cookie
# 默认是暂时的cookie,关闭浏览器后cookie消失,如果想改成长期的需要传入关键字参数remember
login_user(user, remember=True)
else:
flash("账号不存在或者密码错误")
return render_template('auth/login.html', form=form)
...
...

login_user方法并不是把user内的搜索属性全都写入cookie,login_user需要我们为user类定义几个方法,如get_id用来获取id。如果我们把他定义的方法全都编写出来,太多了,我们可以集成他提供给我们的UserMixin类,如果和他的默认配置不同,复写他的方法即可

image.png

models/user.py

...
...
from flask_login import UserMixin

from app.models.base import Base


class User(UserMixin, Base):
...
...

flask-login模块也需要在app中进行注册,注册方法同SQLALChemy
app/__init__.py

login_manager = LoginManager()


def create_app():
app = Flask(__name__)
app.config.from_object("app.secure")
app.config.from_object("app.settings")
# 注册蓝图
register_blueprint(app)

# 注册SQLAlchemy
db.init_app(app)

# 创建所有表
with app.app_context():
db.create_all()

# 注册LoginManager
login_manager.init_app(app)
return app


def register_blueprint(app):
from app.web import web
app.register_blueprint(web)

8.6 访问权限控制与重定向攻击

1.在需要限制登录才能访问的试图函数上,加入@login_required装饰器

@web.route('/my/gifts')
@login_required
def my_gifts():
return "my gifts"

2.在User模型里,编写get_user 方法用来根据id查询用户,并加入@login_manager.user_loader 装饰器(login_manager是从app/__init__.py中导入)

@login_manager.user_loader
def get_user(self, uid):
# 如果是根据主键查询,不要filter_by,使用get方法即可
return User.query.get(int(uid))

3.在app/__init__.py中,配置未登录时调整到的页面和提示消息

login_manager.login_view = 'web.login'
login_manager.login_message = '请先登录或注册'

4.登录成功以后,重定向到next页面;如果没有next页面,则跳转到首页;为了防止重定向攻击,应该判断next是否"/"开头

@web.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm(request.form)
if request.method == 'POST' and form.validate():
user = User.query.filter_by(email=form.email.data).first()
if user and user.check_password(form.password.data):
login_user(user, remember=True)
# request.form 获取表单信息;
# request.args获取url路径?后面的信息
next = request.args.get('next')
# next.startswith('/')防止重定向攻击
if not next or not next.startswith('/'):
return redirect(url_for('web.index'))
return redirect(next)
else:
flash("账号不存在或者密码错误")
return render_template('auth/login.html', form=form)
上一篇下一篇

猜你喜欢

热点阅读