一. 首页:最近的礼物(复杂SQL的编写方案)
逻辑导图:
我们将这些逻辑在app/models/gift.py中实现
from app.models.base import Base
from sqlalchemy import Column, Integer, String, Boolean, ForeignKey, SmallInteger, desc
from sqlalchemy.orm import relationship
from flask import current_app
class Gift(Base):
id = Column(Integer, primary_key=True)
launched = Column(Boolean, default=False)
user = relationship('User')
uid = Column(Integer, ForeignKey('user.id'))
isbn = Column(String(15), nullable=False)
def recent(self): # 逻辑实现, 只筛选没有赠送出去的
recent_gift = Gift.query.filter_by(launched=False).group_by(Gift.isbn).order_by(desc(Gift.create_time)).limit(
current_app.config['RECENT_BOOK_COUNT']).distinct().all()
return rencent_gift
只显示30个: limit(current_app.config['RECENT_BOOK_COUNT'])
按时间排序: order_by(desc(Gift.create_time))
去重: group_by(Gift.isbn) + distinct()
Gift如果实例化了, 还存在recent方法的话,非常不合理, 所以需要变为类方法:
class Gift(Base):
id = Column(Integer, primary_key=True)
launched = Column(Boolean, default=False)
user = relationship('User')
uid = Column(Integer, ForeignKey('user.id'))
isbn = Column(String(15), nullable=False)
@classmethod
def recent(cls): # 逻辑实现, 只筛选没有赠送出去的
recent_gift = Gift.query.filter_by(launched=False).group_by(Gift.isbn).order_by(Gift.create_time).limit(
current_app.config['RECENT_BOOK_COUNT']).distinct().all()
return rencent_gift
在app/web/main.py中编写对应的视图函数index:
from .blueprint import web
from app.models.gift import Gift
from app.view_models.book import BookViewModel
from flask import render_template
__author__ = 'cannon'
@web.route('/')
def index():
"""
首页视图函数
"""
recent_gifts = Gift.recent()
books = [BookViewModel(gift.book) for gift in recent_gifts]
return render_template('index.html', recent=books)
gift.book即单本书的dict信息, 我们在Gift模型中完善book方法:
class Gift(Base):
id = Column(Integer, primary_key=True)
launched = Column(Boolean, default=False)
user = relationship('User')
uid = Column(Integer, ForeignKey('user.id'))
isbn = Column(String(15), nullable=False)
@classmethod
def recent(cls):
recent_gift = Gift.query.filter_by(launched=False).group_by(Gift.isbn).order_by(Gift.create_time).limit(
current_app.config['RECENT_BOOK_COUNT']).distinct().all()
return recent_gift
@property
def book(self): # 通过isbn获取书籍的信息
yushu_book = YuShuBook()
yushu_book.search_by_isbn(self.isbn)
return yushu_book.first # 从api的搜索结果中获取第一本就行
二. 我的礼物(赠送清单)页面
有两种思路:
选第二种, 第一种循环遍历数据库是 无法接受的。
1. 循环遍历我的礼物,并取出每个isbn编号组成一个列表
在app/models/gift.py的class Gift(Base)中增加get_user_gifts类方法:
@classmethod
def get_user_gifts(cls, uid):
gifts = Gift.query.filter_by(uid=uid, launched=False).order_by(desc(Gift.create_time)).all()
return gifts
在对应的app/web/gift.py视图函数my_gifts中编写对应逻辑的代码:
@web.route('/my/gifts')
@login_required
def my_gifts():
uid = current_user.id
gifts_of_mine = Gift.get_user_gifts(uid)
isbn_list = [gift.isbn for gift in gifts_of_mine] # 取出每个isbn编号组成一个列表
pass
2. db.session.query与func.count统计联合使用
在Gift模型中,增加get_wish_counts方法,统计isbn_list中每个isbn对应的心愿数量:
filter中 in_
的用法, 以及Wish_status==1
不能漏(重写的是filter_by不是filter)
@classmethod
def get_wish_counts(cls, isbn_list):
# 根据传入的一组isbn,到Gift表中检索出相应的礼物, 并且计算出某个礼物的Wish心愿数量
db.session.query(Wish).filter(Wish.launched == False, Wish.isbn.in_(isbn_list), Wish.status == 1).group_by(Wish.isbn).all() #filter接收条件表达式
pass
这样db.session.query查到的是Wish, 并不是数量, 我们需要使用sqlalchemy的func
from sqlalchemy import func
···
@classmethod
def get_wish_counts(cls, isbn_list):
# 根据传入的一组isbn,到Gift表中检索出相应的礼物, 并且计算出某个礼物的Wish心愿数量
# query(func.count(Wish.id), Wish.isbn) 这样查询出来count_list列表的每个元素都是是 wish的数量,对应isbn 组成的元组
count_list = db.session.query(func.count(Wish.id), Wish.isbn).filter(Wish.launched == False,
Wish.isbn.in_(isbn_list),
Wish.status == 1).group_by(
Wish.isbn).all() # filter接收条件表达式
pass
3.不要在函数中返回元组, 应该返回字典
query(func.count(Wish.id), Wish.isbn)联合查询,返回的是以元组为元素的列表。这样不方便以后使用, 我们改为使用字典。
@classmethod
def get_wish_counts(cls, isbn_list):
count_list = db.session.query(func.count(Wish.id), Wish.isbn).filter(Wish.launched == False,
Wish.isbn.in_(isbn_list),
Wish.status == 1).group_by(
Wish.isbn).all()
count_list = [{'count': w[0], 'isbn': w[1]} for w in count_list] # 改成字典
return count_list
4. 编写对应的view_model
先完善app/web/gift.py的my_gifts视图函数:
@web.route('/my/gifts')
@login_required
def my_gifts():
uid = current_user.id
gifts_of_mine = Gift.get_user_gifts(uid)
isbn_list = [gift.isbn for gift in gifts_of_mine]
wish_count_list = Gift.get_wish_counts(isbn_list)
pass
我们每本书要显示 书籍信息外, 还有心愿数目, 根据这些, 编写对应view_model/gift.py:
from .book import BookViewModel
class MyGifts:
def __init__(self, gifts_of_mine, wish_count_list):
self.gifts = []
self.__gifts_of_mine = gifts_of_mine
self.__wish_count_list = wish_count_list
self.gifts = self.__parse()
# 通过结果返回回来, 而不是直接在方法中修改实例变量
# 这样一旦这个类变得很复杂, 就能在__init__中知道实例变量都在哪里被修改了。
def __parse(self):
temp_gifts = []
for gift in self.__gifts_of_mine:
my_gift = self.__matching(gift)
temp_gifts.append(my_gift)
return temp_gifts
def __matching(self, gift):
count = 0
for wish_count in self.__wish_count_list:
if gift.isbn == wish_count['isbn']:
count = wish_count['count']
my_gift = {
'wishes_count': count,
'book': BookViewModel(gift.book), # gift.book没有转化为BookViewModel,需转化
'id': gift.id
}
return my_gift
对应编写my_gifts视图函数:
@web.route('/my/gifts')
@login_required
def my_gifts():
uid = current_user.id
gifts_of_mine = Gift.get_user_gifts(uid)
isbn_list = [gift.isbn for gift in gifts_of_mine]
wish_count_list = Gift.get_wish_counts(isbn_list)
view_model = MyGifts(gifts_of_mine, wish_count_list)
return render_template('my_gifts.html', gifts=view_model.gifts)
三. 用户注销
使用flask_login的logout_user实现:
app/web/auth.py的logout视图函数:
from flask_login import login_user, logout_user
···
@web.route('/logout')
def logout():
logout_user()
return redirect(url_for('web.index'))
四. 我的心愿页面
与赠送清单的代码正好相反。
照着app/models/gift.py修改wish.py:
from app.models.base import Base, db
from .gift import Gift
from sqlalchemy import Column, Integer, String, Boolean, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy import desc, func
from app.spider.yushu_book import YuShuBook
from flask import current_app
class Wish(Base):
id = Column(Integer, primary_key=True)
launched = Column(Boolean, default=False) # 是否送出去了
user = relationship('User')
uid = Column(Integer, ForeignKey('user.id'))
isbn = Column(String(15), nullable=False)
@classmethod
def get_user_Wishes(cls, uid):
gifts = Wish.query.filter_by(uid=uid, launched=False).order_by(desc(Wish.create_time)).all()
return gifts
@classmethod
def get_gift_counts(cls, isbn_list):
# 根据传入的一组isbn,到Wish表中检索出相应的心愿, 并且计算出某个心愿的Gift心愿数量
count_list = db.session.query(func.count(Gift.id), Gift.isbn).filter(Gift.launched == False,
Gift.isbn.in_(isbn_list),
Gift.status == 1).group_by(
Gift.isbn).all() # filter接收条件表达式
count_list = [{'count': w[0], 'isbn': w[1]} for w in count_list]
return count_list
@classmethod
def recent(cls):
recent_gift = Wish.query.filter_by(launched=False).group_by(Wish.isbn).order_by(desc(Wish.create_time)).limit(
current_app.config['RECENT_BOOK_COUNT']).distinct().all()
return recent_gift
@property
def book(self):
yushu_book = YuShuBook()
yushu_book.search_by_isbn(self.isbn)
return yushu_book.first
修改对应的视图函数 app/web/wish.py的my_wish:(参照gift.py的my_gift视图函数)
@web.route('/my/wish')
def my_wish():
uid = current_user.id
wished_of_mine = Wish.get_user_gifts(uid)
isbn_list = [wish.isbn for wish in wished_of_mine]
Gift_count_list = Wish.get_gift_counts(isbn_list)
view_model = MyWishes(wished_of_mine, Gift_count_list)
return render_template('my_wish.html', wishes=view_model.wishes)
复制view_models/gift.py 编写wish.py, 将MyGifts改名为MyWishes即可。
运行后报错:
File "app/models/wish.py", line 9, in <module>
from app.models.gift import Gift
ImportError:cannont import name 'Gift'
错误原因是 gift.py导入wish.py,wish.py 导入 gift.py,造成循环导入。
再谈循环导入的解决方案
解决办法: 出现循环引用了, 一般修改成 在需要的地方导入而不是在开头导入。
gift.py中把:from .wish import Wish放入get_wish_counts方法中
wish.py中把:from .gift import Gift放入get_gift_counts方法中
运行通过。