「Flask實戰」魚書項目實戰七

「Flask實戰」魚書項目實戰七

資源介紹參數
資源類別: Python
如遇問題: 聯繫客服/留言反饋
釋放雙眼,帶上耳機,聽聽看~!

flask魚書項目實戰


實現儲存禮物

#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_wisheshas_in_gifts來區分。

重寫基類的filter_by

因為再查詢的時候我們要確定這條記錄有沒有被刪除,前面說過了,刪除使用標誌位status來表示的,但是每條查詢都傳入標誌位, 顯然是個廢話(因為查詢肯定是要查詢沒有被刪除的)所以採用重寫filter_by的方法來解決。
首先,filter_by繼承了flask_sqlalchemyflask_sqlalchemy繼承了sqlalchemyBaseQuery所以定義的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)
聲明:本文為原創作品,版權歸作者所有。未經許可,不得轉載或用於任何商業用途。如若本站內容侵犯了原著者的合法權益,可聯繫我們進行處理。

給TA打賞
共{{data.count}}人
人已打賞
0 條回復 A文章作者 M管理員
    暫無討論,說說你的看法吧
個人中心
購物車
優惠劵
今日簽到
有新私信 私信列表
搜尋