前几天啃了一本《Flask Web开发》,写了个图书馆系统,当做学校课程的作业了。

项目地址在这里

虽然是个很小的项目,但是我喜欢不断改进,不断做细致,以此来提升自己。
即使被一些同学嘲讽“不就是个作业吗,大一就做了”

趁着还没忘,记一些东西。 — 首先是项目结构。

BookLibrary
|- app/
|  |- static/          css,JavaScript等网页需要的资源
|  |   |- css/
|  |   |- ...
|  |- templates/       放置html模版
|  |- __init__.py
|  |- models.py        模型
|  |- views.py         视图
|  |- forms.py         表单
|  |- ...
|- config.py           配置文件
|- run.py              启动文件
|- venv/               python虚拟环境
|- ...

其中 venv ,也就是 python 虚拟环境,不是项目必须,但是极力推荐使用虚拟环境,这样可以不污染全局 python 解释器。
并且,venv 这个文件夹放在哪里无所谓,并不一定要放在项目文件夹里,我放在这里纯粹是为了好管理。

这就是相对简单的项目结构了。等我把Flask蓝图的概念搞清楚,再决定要不要重构项目。

然后,就是项目中遇到的一些细节问题。


使用 Flask-SQLAlchemy 时,遇到一些 ORM 难以解决的查询

  • Email case-insensitive 查询
if User.query.filter(db.func.lower(User.email) == db.func.lower(filed.data)).first():
    raise ValidationError(u'该 Email 已经被注册了')

这里借助了db.func这样的函数,将输入值和数据库里的值都转为小写,再来判断。
当然,db是初始化的时候,通过db = SQLAlchemy(app)这样的方法创建的。
其实有更好的写法。

if User.query.filter(User.email.ilike(filed.data)).first():
    raise ValidationError(u'该 Email 已经被注册了')
  • 如何连接两个表,用统计数据排序
popular_books = Book.query.outerjoin(Log).group_by(Book.id)\
                .order_by(db.func.count(Log.id).desc()).limit(5)

一些 jinja2 技巧

<li {%- if request.path == url_for('books') %} class="active" {%- endif -%} >
  <a href="{{ url_for('books') }}">书籍清单</a>
</li>

由于 jinja2 的语法和我写博客的 Liquid 语法太相近,害得我转义转了半天。

通过比较当前请求路径和所对应的链接是否对应,设置其是否是 active 状态。
之前为了达到同样的效果,用 JavaScript 在页面渲染完后手动去设置 class , 自然是没有 jinja2 这样的语法直接而又优雅。

链接跳转技巧

html模版:

<a href="{{url_for('book_borrow',book_id=book.id,next=request.path) }}">借阅</a>

路由函数:

@app.route('/logs_info/borrow/')
@login_required
def book_borrow():
    # ...
    return redirect(request.args.get('next') or url_for('book_detail', book_id=book_id))

同样,是通过 request.path 把当前的路径传给 next 参数,路由回重定向到 next
也就是之前设置的 request.path
这样就相当于,发起一个链接请求,又回到的原地。