前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >慕课网Flask高级编程实战-9.书籍交易模型(数据库事务、重写Flask中的对象)

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

作者头像
Meet相识
发布2018-09-12 16:55:36
8100
发布2018-09-12 16:55:36
举报
文章被收录于专栏:技术专栏技术专栏

9.1 鱼豆

我们的鱼书有一个经济系统,在上传一本书的时候,将获取0.5个鱼豆。赠送一个本书的时候,再获取1个鱼豆。索要一本书的时候,消耗一个鱼豆,其中赠送和索要书籍是用户之间鱼豆互相加减,上传的时候是系统赠送。

基于上面的规则,我们来编写赠送鱼书的视图函数。

1.判断当前书籍是否可以加入赠送清单

1.如果isbn编号不符合规则,不允许添加 2.如果isbn编号对应的书籍不存在,不允许添加 3.同一个用户,不能同时赠送同一本书籍 4.一个用户对于一本书不能既是赠书者,又是索要者 5.3和4合并成一条,就是一本书必须即不在心愿清单又不在赠书列表里才可以添加

并不是web编程就简单,算法就难,他们都有自己难和简单的地方,对于web编程来说,他不需要算法,数学的支撑。但是需要很强的逻辑思维能力,因为业务一直在变化,需要非常好的抽象能力来适应变化。

models/user.py

代码语言:javascript
复制
def can_save_to_list(self, isbn):
"""
判断可以将书籍加入心愿清单
1.如果isbn编号不符合规则,不允许添加
2.如果isbn编号对应的书籍不存在,不允许添加
3.同一个用户,不能同时赠送同一本书籍
4.一个用户对于一本书不能既是赠书者,又是索要者
5.3和4合并成一条,就是一本书必须即不在心愿清单又不在赠书列表里才可以添加
:param isbn:
:return:
"""
if not is_isbn_or_key(isbn):
return False

yushu_book = YuShuBook()
yushu_book.search_by_isbn(isbn)
if not yushu_book.first:
return False

gifting = Gift.query.filter_by(uid=self.id, isbn=isbn, launched=False).first()
wishing = Wish.query.filter_by(uid=self.id, isbn=isbn, launched=False).first()
return not wishing and not gifting

之所以要把这个逻辑判断方法加在models里而不是在form里,是因为编程是活的,要视情况而定,这个can_save_to_list加载models在使用起来更加灵活,复用性更强。

2.添加赠送清单,增加鱼豆

添加赠送清单,增加鱼豆对应了两个数据库操作,如果其中一个在执行过程中失败了,那么另一个也不能提交,这用到了数据库的事务。 给用户添加鱼豆需要获取当前用户,我们可以从flask_login的current_user获取当前用户

代码语言:javascript
复制
@web.route('/gifts/book/<isbn>')
@login_required
def save_to_gifts(isbn):
if current_user.can_save_to_list(isbn):
try:
gift = Gift()
gift.isbn = isbn
gift.uid = current_user.id

current_user.beans += current_app.config['BEANS_UPLOAD_ONE_BOOK']

db.session.add(gift)
db.session.add(current_user)
db.session.commit()
except Exception as e:
db.session.rollback()
raise e
else:
flash("这本书以添加进您的赠送清单或已经存在于您的心愿清单,请不要重复添加")
return redirect(url_for('web.book_detail', isbn=isbn))

3.添加心愿清单

web/wishs.py

代码语言:javascript
复制
@web.route('/wish/book/<isbn>')
@login_required
def save_to_wish(isbn):
if current_user.can_save_to_list(isbn):
with db.auto_commit():
wish = Wish()
wish.isbn = isbn
wish.uid = current_user.id

db.session.add(wish)
else:
flash("这本书以添加进您的赠送清单或已经存在于您的心愿清单,请不要重复添加")
return redirect(url_for('web.book_detail', isbn=isbn))

4.巧用ajax

上面我们在添加赠送书籍完成之后,由重定向回了书籍详情页面。由于我们之前就是在数据详情页面,做了一次操作以后又重定向回去了,这样的操作时非常浪费服务器资源的。我们可以用ajax异步请求来改善这个问题。

另一个消耗服务器性能的点在于书籍详情页面的模板渲染工作,所以另一种优化方案,就是将页面作为一个静态页面缓存起来,下一次重定向只需要将缓存的页面读取出来返回即可


9.2 contextmanager

1.contextmanager简单讲解

contextmanager可以简化上下文管理器,不需要我们编写__enter__和__exit__函数。他给了我们一个机会,让我们把之前一个不是上下文管理器的类变成一个上下文管理器,而不需要我们去修改这个类的源代码

其中的yield的作用,是中断当前函数执行流程,先去执行yield出去的部分的代码执行流程

下面的代码的作用,在书籍前后自动加上《》

代码语言:javascript
复制
@contextmanager
def book_mark():
print('《', end='')
yield
print('》', end='')


with book_mark():
print('钢铁',end='')

2.结合继承,contextmanager,yield,rollback来简化try-except的数据库事务代码

1.我们可以通过contextmanager实现一个上下文管理器,将try-except的代码放在contextmanager里,将具体的业务逻辑代码yield出去 2.SQLAlchemy并没有这个上下文管理器,但是我们可以做一个子类,来扩展他的功能 3.编写子类的时候,命名是非常不好起的,我们可以改变父类的名字,给子类命名为原父类的名字

models/base.py

代码语言:javascript
复制
from flask_sqlalchemy import SQLAlchemy as _SQLAlcmemy

class SQLAlchemy(_SQLAlcmemy):
@contextmanager
def auto_commit(self):
try:
yield
self.session.commit()
except Exception as e:
self.session.rollback()
raise e

使用auto_commit的save_to_gifts视图函数

代码语言:javascript
复制
@web.route('/gifts/book/<isbn>')
@login_required
def save_to_gifts(isbn):
if current_user.can_save_to_list(isbn):
with db.auto_commit():
gift = Gift()
gift.isbn = isbn
gift.uid = current_user.id

current_user.beans += current_app.config['BEANS_UPLOAD_ONE_BOOK']

db.session.add(gift)
db.session.add(current_user)
else:
flash("这本书以添加进您的赠送清单或已经存在于您的心愿清单,请不要重复添加")
return "aaa"

使用auto_commit的register视图函数

代码语言:javascript
复制
@web.route('/register', methods=['GET', 'POST'])
def register():
form = RegisterForm(request.form)
if request.method == 'POST' and form.validate():
with db.auto_commit():
user = User()
user.set_attrs(form.data)

db.session.add(user)

return redirect(url_for('web.login'))

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

干货:

1.遇到比较复杂的问题,应该把他单独的分离出来,在一个单独的文件里来编写一些非常简单的源码,因为业务越简单,越能够让我们去关注知识和原理本身的相关问题。 2.高级编程不是在于学习更高级的语法(学会更好),更关键的在于能够用自己所学的知识,写出更好的代码来 3.对知识的综合运用能力很重要,将单个的知识点组合在一起写出一段很好的代码来


9.3 书籍交易视图模型

书籍详情页,除了需要显示书籍详情信息外。还应该显示其他信息,这些信息分为三类 1.默认情况下,显示想要赠送这本书的人的列表,包括名字和上传时间。 2.如果当前用户是此书的赠送者,应该显示索要这本书的人的列表。 3.如果当前用户是此书的索要者,应该显示想要赠送这本书的人的列表。

综上所述,我们一共需要两个列表,这本书的索要人列表和这本书的赠书人的列表,根据不同情况进行展示。

代码语言:javascript
复制
# 赠书人列表和索要人列表
trade_gifts = Gift.query.filter_by(isbn=isbn).all()
trade_wishs = Wish.query.filter_by(isbn=isbn).all()

我们在view_model中处理这两个列表的原始数据,加工成我们姓名,上传时间的列表。由于gifts,wishs两个的加工逻辑一样,只是数据库表不一样,所以可以写一个统一的类trade来处理

代码语言:javascript
复制
class TradeInfo:

def __init__(self, goods):
self.total = 0
self.trades = []
self.__parse(goods)

def __parse(self, goods):
self.total = len(goods)
self.trades = [self.__map_to_trade(single) for single in goods]

def __map_to_trade(self, single):
if single.create_datetime:
time = single.create_datetime.strftime('%Y-%m-%d')
else:
time = '未知'
return dict(
user_name=single.user.nickname,
time=time,
id=single.id
)

create_time 本是int类型,要进行strftime格式化操作需要转化成string类型,这个操作每个模型都要用到,所以编写在base.py里

代码语言:javascript
复制
@property
def create_datetime(self):
if self.create_time:
return str(self.create_time)
else:
return None

接下来完善书籍详情视图函数。区分上面说的三种情况。使用current_user的is_authenticated可以判断用户是否登录。然后分别以当前用户id为查询条件去wish表和gift表里查询,如果能查询到,则将对应的has_in_gifts/has_in_wishs设置为True

代码语言:javascript
复制
@web.route("/book/<isbn>/detail")
def book_detail(isbn):
has_in_gifts = False
has_in_wishs = False

# 取出每本书的详情
yushu_book = YuShuBook()
yushu_book.search_by_isbn(isbn)
book = BookViewModel(yushu_book.first)

# 三种情况的判断
if current_user.is_authenticated:
if Gift.query.filter_by(uid=current_user.id).first():
has_in_gifts = True
if Wish.query.filter_by(uid=current_user.id).first():
has_in_wishs = True

# 赠书人列表和索要人列表
trade_gifts = Gift.query.filter_by(isbn=isbn).all()
trade_wishs = Wish.query.filter_by(isbn=isbn).all()
return render_template("book_detail.html", book=book,
wishes=trade_wishs, gifts=trade_gifts,
has_in_wishs=has_in_wishs, has_in_gifts=has_in_gifts)

9.4 重写filter_by

由于我们的删除操作都是逻辑删除,所以在查询的时候应该默认查询status=1的记录(即未删除的记录),但是如果在每一个filter_by里都这么写,就太麻烦了,我们的思路是重写默认的filter_by函数,加上status=1的限制条件。

那么我们就需要先了解原来SQLAlchemy的继承关系 Flask的SQLAlchemy中有一个BaseQuery,BaseQuery继承了orm.Query(原SQLAlchemy的类),这里面有filter_by函数;也就是说BaseQuery通过继承orm.Query拥有了filter_by的能力

flask_sqlalchemy

代码语言:javascript
复制
...
...
class SQLAlchemy(object):
Query = None

def __init__(self, app=None, use_native_unicode=True, session_options=None,
metadata=None, query_class=BaseQuery, model_class=Model):
...
...

class BaseQuery(orm.Query):
...
...

orm.Query

代码语言:javascript
复制
def filter_by(self, **kwargs):
# for循环拼接关键字参数查询条件
clauses = [_entity_descriptor(self._joinpoint_zero(), key) == value
for key, value in kwargs.items()]
return self.filter(sql.and_(*clauses))

所以如果我们要重写filter_by,需要自己编写子类,继承BaseQuery,重写filter_by函数,将status=1加入到kwargs

代码语言:javascript
复制
class Query(BaseQuery):

def filter_by(self, **kwargs):
if 'status' not in kwargs:
kwargs['status'] = 1
return super(Query, self).filter_by(**kwargs)

最后,Flask的SQLAlchemy给了我们一种方法,让我们应用自己的Query类,即在实例化的时候传入关键字参数query_class

代码语言:javascript
复制
db = SQLAlchemy(query_class=Query)
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018.06.14 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 9.1 鱼豆
    • 1.判断当前书籍是否可以加入赠送清单
      • 2.添加赠送清单,增加鱼豆
        • 3.添加心愿清单
          • 4.巧用ajax
          • 9.2 contextmanager
            • 1.contextmanager简单讲解
              • 2.结合继承,contextmanager,yield,rollback来简化try-except的数据库事务代码
              • 9.3 书籍交易视图模型
              • 9.4 重写filter_by
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档