「Flask」鱼书项目实战七

栏目: Python · 发布时间: 6年前

内容简介:flask鱼书项目这里的每次添加鱼豆的个数卸载配置文件内,自行添加上面的判断显然是不严谨的,需要添加判断条件,来确认是否将书籍添加到礼物列表中,在

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)

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Code Reading

Code Reading

Diomidis Spinellis / Addison-Wesley Professional / 2003-06-06 / USD 64.99

This book is a unique and essential reference that focuses upon the reading and comprehension of existing software code. While code reading is an important task faced by the vast majority of students,......一起来看看 《Code Reading》 这本书的介绍吧!

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试