实现保存礼物
#web/gitf.py
from flask import current_app
from app.models.base import db
from app.models.sql_gift import Gift
from . import web
from flask_login import login_required, current_user
······
@web.route('/gifts/book/<isbn>')
@login_required
def save_to_gifts(isbn):
gift = Gift()
gift.isbn = isbn
#这里的current_user指代实例化的User对象,通过之前的git_id方法,在flask_login底层实例话的对象
gift.uid = current_user.id
current_user.beans += current_app.config['BEANS_UPLOAD_ONE_BOOK']
db.session.add(gift)
db.session.commit()
这里的每次添加鱼豆的个数卸载配置文件内,自行添加
#setting.py
BEANS_UPLOAD_ONE_BOOK = 0.5
上面的判断显然是不严谨的,需要添加判断条件,来确认是否将书籍添加到礼物列表中,在sql_user.py
下进行判断
from sqlalchemy import Column, Integer, Float, String, Boolean
from werkzeug.security import generate_password_hash, check_password_hash
from app.libs.helper import is_isbn_or_key
from app.models.base import Base, db
from app import login_manager
from flask_login import UserMixin
from app.models.sql_gift import Gift
from app.models.sql_wish import Wish
from app.spider.yushu_book import YuShuBook
class User(Base, UserMixin):
id = Column(Integer, primary_key=True)
_password = Column('password', String(128), nullable=False)
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))
@property
def password(self):
return self._password
@password.setter
def password(self, raw):
self._password = generate_password_hash(raw)
def check_password(self, raw):
return check_password_hash(self._password, raw)
# 新增验证gitf方法,先判断isbn输入是否正确,再判断api中是否存在这个isbn书籍
def can_save_to_list(self, isbn):
if is_isbn_or_key(isbn) != '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()
#Wisth和sql_gift.py模块一样,把类名改一下即可
wishing = Wish.query.filter_by(uid=self.id, isbn=isbn, launched=False).first()
if not gifting and not wishing:
return True
else:
return False
@login_manager.user_loader
def get_user(uid):
return User.query.get(int(uid))
事务与回滚
事务处理可以用来维护数据库的完整性,保证成批的 SQL 语句要么全部执行,要么全部不执行
这里操作了两个模型,即两个数据表,所以涉及到数据库的事务处理,但是sqlalchemy
模块本身就支持事务,因为只有在执行了commit
的时候才会一起提交到数据库,否则就不提交,所以这里使用异常处理,如果捕获异常,则释放,否则后面的sql语句也不会被提交,即数据回滚
@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.commit()
except Exception as e:
db.session.rollback()
raise e
else:
flash('这本书已经添加至你的赠送清单或者存在你的心愿清单')
上下文管理器
上面把添加数据库的代码做了事务处理,那么不难想到其他的操作数据库应该也需要相同的处理,这样每一个数据库操作的时候都会添加同样的代码,这里就可以使用上下文管理器来简化操作
#modles/base.py
#这里把导入的SQLAlchemy重新命名为_SQLAlchemy,想不出子类的名字的时候可以这么使用=
from flask_sqlalchemy import SQLAlchemy as _SQLAlchemy
#继承_SQLAlchemy,并且创建上下文管理器
class SQLAlchemy(_SQLAlchemy):
#python提供了简化创建上下文管理器的装饰器,否则需要定义类方法
@contextmanager
def auto_commit(self):
try:
#这里的yield就是返回出去执行的处理数据库的方法执行完之后执行下面的代码
yield
self.session.commit()
except Exception as e:
self.session.rollback()
raise e
#这里需要把db放到子类下面,否则找不到SQLAlchemy()
db = SQLAlchemy()
然后就可以替换刚刚的代码
#web/gitf.py
@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)
else:
flash('这本书已经添加至你的赠送清单或者存在你的心愿清单')
同样的,之前注册的代码
#web/auth.py
@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)
变量的陷阱
这里再测试赠送礼物功能之前,还有几个小问题,第一个是创建时间再base.py
中定义了create_time
,但是还没有给这个赋值,这里线给他赋值
from datetime import datetime
·····
class Base(db.Model):
__abstract__ = True
create_time = Column('create_time', Integer)
status = Column(SmallInteger, default=1)
def __init__(self):
self.create_time = int(datetime.now().timestamp())
这里有一个问题,就是既然status
可以设置默认值,为什么不把create_time
也设置成默认值。
这里这个问题的原因就是,create_time
被设置成了类变量,类变量再app
启动的时候就已经被赋值成功了,所以如果设置成类变量,所有的create_time
都会变成同一个时间,然而,self
指向实例,create_time
就是指对象被实例化的时间,所以这里要使用self
来设置创建时间
启动app
测试提交书籍,还是失败,原因是gift.py
里的save_to_gift
没有返回,再flask中视图函数必须要有返回,否贼就会报错
合理使用Ajax
这里如果使用return redirect(url_for('web.book_detail', isbn=isbn))
把他重定向回到详情页面,会刷新一次,用户体验不是特别好,像这种提交数据,但不希望刷新页面的场景,就可以使用Ajax
来异步提交,这里的Ajax
,,之后补上,先用redirect
, orz
添加心愿清单
这块视图函数几乎和save_to_gift
一模一样,直接给出代码
#web/wish.py
from flask import current_app, flash, redirect, url_for
from flask_login import login_required, current_user
from app.models.base import db
from app.models.sql_wish import Wish
from . import web
@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))
处理数据
首先再BookViewModle.py
中添加两个新的数据,使书籍详情页面显示完整
class BookViewModle:
def __init__(self,book):
self.title = book['title']
self.publisher= book['publisher']
self.pages= book['pages']
self.price = book['price']
self.author = '、'.join(book['author'])
self.summary = book['summary']
self.isbn = book['isbn']
self.image= book['image']
#新数据
self.pubdate = book['pubdate']
self.binding = book['binding']
然后在处理赠送书籍列表,前面说过了,在原始数据和试图数据之前需要有个处理数据的viewmodle
,所以在view_modles
下创建新的trade.py
作为书籍详情页的viewmodle
#view_modles/trade.py
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
)
然后在视图函数中编写试图
#web/book.py
@web.route('/book/<isbn>/detail')
def book_detail(isbn):
has_in_gifts = False
has_in_wishes = False
# 取书籍详情数据
yushu_book = YuShuBook()
yushu_book.search_by_isbn(isbn)
book = BookViewModle(yushu_book.first)
if current_user.is_authenticated:
if Gift.query.filter_by(uid=current_user.id, isbn=isbn, launched=False).first():
has_in_gifts = True
if Wish.query.filter_by(uid=current_user.id, isbn=isbn, launched=False).first():
has_in_wishes = True
trade_gifts = Gift.query.filter_by(isbn=isbn, launched=False).all()
trade_wishes = Wish.query.filter_by(isbn=isbn, launched=False).all()
trade_gifts_modle = TradeInfo(trade_gifts)
trade_wishes_modle = TradeInfo(trade_wishes)
return render_template('book_detail.html', book=book, wishes=trade_wishes_modle, gifts=trade_gifts_modle, has_in_gifts=has_in_gifts, has_in_wishes=has_in_wishes)
要理解这个函数里的一些参数,需要结合模板来看
{% if not has_in_gifts and not has_in_wishes %}
<div class="col-md-1">
<a class="btn btn-outline"
href="#modal">
赠送此书
</a>
</div>
<div style="margin-left:30px;" class="col-md-1">
<a class="btn btn-outline"
href="{{ url_for('web.save_to_wish', isbn=book.isbn) }}">
加入到心愿清单
</a>
</div>
{% elif has_in_wishes %}
<div class="col-md-3">
<span class="bg-info">已添加至心愿清单</span>
</div>
{% else %}
<div class="col-md-3">
<span class="bg-info">已添加至赠送清单</span>
</div>
{% endif %}
这里时用于区分三种状态,用has_in_wishes
和has_in_gifts
来区分。
重写基类的filter_by
因为再查询的时候我们要确定这条记录有没有被删除,前面说过了,删除使用标志位status
来表示的,但是每条查询都传入标志位, 显然是个废话(因为查询肯定是要查询没有被删除的)所以采用重写filter_by
的方法来解决。
首先,filter_by
继承了flask_sqlalchemy
,flask_sqlalchemy
继承了sqlalchemy
的BaseQuery
所以定义的Query
只需要继承BaseQuery
即可
class Query(BaseQuery):
def filter_by(self, **kwargs):
if 'status' not in kwargs.keys():
kwargs['status'] = 1
#调用父类的filter_by方法,把新的kwargs传入进行查询并返回
return super(Query, self).filter_by(**kwargs)
然后在db
中覆盖这个方法即可
db = SQLAlchemy(query_class=Query)