#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中視圖函式必須要有返回,否賊就會報錯
這裡如果使用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
來區分。
因為再查詢的時候我們要確定這條記錄有沒有被刪除,前面說過了,刪除使用標誌位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)