flask入门项目

项目功能

这个小程序能够实现用户(仅一个,且在程序中已经规定了用户名和密码)的登陆和注销,能够显示数据库中 的条目。php

涉及到的知识点

涉及到了配置文件、数据库的初始化、链接和断开,表单的提交读取等等。css

须要注意的

要仔细想一想页面之间的跳转,整个程序的构架。
程序流程
1. 运行程序后,打开 http://127.0.0.1:5000/ 页面,出现的是一下界面
html

显示的是show_entries.html模块的内容。python

show_entries.html模块web

{% extends "layout.html" %} <!--模板继承 ,继承layout.html的内容--> {% block body %} {% if session.logged_in %} <!--初次登陆,session.logged_in为假,如下内容不显示 --> <form action="{{ url_for('add_entry') }}" method=post class=add-entry> <dl> <dt>Title: <dd><input type=text size=30 name=title> <dt>Text: <dd><textarea name=text rows=5 cols=40></textarea> <dd><input type=submit value=Share> </dl> </form> {% endif %} <ul class=entries> {% for entry in entries %} <li><h2>{{ entry.title }}</h2>{{ entry.text|safe }} {% else %} <!-- 如下显示--> <li><em>Unbelievable. No entries here so far</em> {% endfor %} </ul> {% endblock %}

关于模板继承
模板继承容许你建立一个基础的骨架模板, 这个模板包含您网站的通用元素,而且定义子模>板能够重载的 blocks 。使用 {% block %} 标签订义了四个子模板能够重载的块。 block 标签所作的的全部事情就是告诉模板引擎: 一个子模板可能会重写父模板的这个部分。
{% extends %} 是关键,它会告诉模板引擎这个模板继承自另外一个模板的, 模板引擎分析这个模板时首先会定位其父父模板。extends 标签必须是模板的首个标签。 想要渲染父模板中的模板须要使用 {{ super() }}。
更多:sql

  1. 点开log in后,页面跳转到’/login’


画面上显示的是login.html模板的内容,填好表单后按下’Login’,提交表单,执行login()函数。数据库

login.html模板django

{% extends "layout.html" %} <!--模板继承--> {% block body %} <!--重载--> <h2>Login</h2> {% if error %}<p class=error><strong>Error:</strong> {{ error }}{% endif %} <!-- 即error不为None时,就显示出错误缘由。--> <form action="{{ url_for('login') }}" method=post> <!--action后面是form表单提交的地址 --> <dl> <dt>Username: <dd><input type="text" name="username"> <dt>Password: <dd><input type="password" name="password"> <dd><input type="submit" value="Login"> </dl> </form> {% endblock %}

login()函数编程

@app.route('/login',methods=['GET','POST'])
def login():
    error = None
    if request.method == 'POST':
        if request.form['username'] != app.config['USERNAME']:
            error = 'Invalid username'
        elif request.form['password'] != app.config['PASSWORD']:
            error = 'Invalid password'
        else:
            session['logged_in'] = True
            flash('You were logged in')
            return redirect(url_for('show_entries'))
    return render_template('login.html', error=error)  # 用login.html模板通知,提示从新登陆

3.登陆成功后,会出现如下界面。

画面上显示的是show_entries.html的内容。flask

{% extends "layout.html" %} <!--模板继承 ,继承layout.html的内容--> {% block body %} {% if session.logged_in %} <!--session.logged_in为True,显示如下内容--> <form action="{{ url_for('add_entry') }}" method=post class=add-entry> <dl> <dt>Title: <dd><input type=text size=30 name=title> <dt>Text: <dd><textarea name=text rows=5 cols=40></textarea> <dd><input type=submit value=Share> </dl> </form> {% endif %} <ul class=entries> {% for entry in entries %} <li><h2>{{ entry.title }}</h2>{{ entry.text|safe }} {% else %} <li><em>Unbelievable. No entries here so far</em> {% endfor %} </ul> {% endblock %}
  1. 填写好内容按下Share之后,执行add_entry()函数。
@app.route('/add',methods=['POST']) # 只回应POST请求
def add_entry():
    if not session.get('logged_in'):
        abort(401)
    g.db.execute('insert into entries (title, text) values (?, ?)',[request.form['title'], request.form['text']])
    # 使用问号标记来构建 SQL 语句。不然,当使用格式化字符串构建 SQL 语句时, 应用容易遭受 SQL 注入。
    g.db.commit()
    flash('New entry was successfully posted')   # 用flash()向下一个请求闪现一条信息
    return redirect(url_for('show_entries'))   # 重定向,跳转回show_entries页


7. 点击log out,执行logout()函数
logout()函数

@app.route('/logout')
def logout():
    session.pop('logged_in', None)   # 不须要检查用户是否已经登入,由于若是不存在则什么都不作
    flash('You were logged out')
    return redirect(url_for('show_entries'))

执行后,就回到了show_entries.html设置的页面。

编程过程当中遇到的问题汇总

1.没法利用初始化建立数据库。

原始代码

from __future__ import with_statement   #__future__导入必须先于其余的导入
import sqlite3
from flask import Flask
from contextlib import closing

DATABASE = 'G:/python/flask/flaskr/tmp/flaskr.db'

app = Flask(__name__)
app.config.from_object(__name__)

def connect_db():
    return sqlite3.connect(app.config['DATABASE'])

def init_db():
    with closing(connect_db()) as db:
        with open('schema.sql') as f:
            db.cursor().executescript(f.read())
        db.commit()
init_db()

程序运行后,会出现如下报错:
这里写图片描述

缘由
DATABASE的路径有问题,在调用init_db()函数前,flaskr目录下并无tmp这个文件夹,程序也可建立数据库,却没办法建立一个文件夹。因此指令没法执行。
解决方法
修改路径,将’/tmp’去掉或者在flaskr文件夹中创建一个tmp文件夹,运行后便可生成flaskr.db文件。

2. 400 Bad Request报错

问题描述:
这里写图片描述
登陆页面提交时,没法提交成功,出现该报错。

解决
400 Bad Request,该状态码表示请求报文中存在语法错误。
因此就检查了一下login()函数,发现果真有一处拼写错误,将“username”拼成了“usename”,修改完以后,运行程序,就能够登陆了。

3. 关于配置的用户名和密码

问题描述:
在登陆界面时,因为这个小程序是已经规定好了只有一个用户可使用,也在程序中规定了用户名和密码。可是我一直没找到用户名和密码。。。最后才想起来好像在开头的时候配置过用户名和密码,这才登陆成功。

# 一直在里找密码。。。彻底忘记以前配置过USERNAME和PASSWORD了
    if request.method == 'POST':
        if request.form['username'] != app.config['USERNAME']:
            error = 'Invalid username'  
        #若是从表单中读取到的username和以前配置的USERNAME的值不同,error就等于'Invalid username

        elif request.form['password'] != app.config['PASSWORD']:
            error = 'Invalid password'
        else:
            session['logged_in'] = True
            flash('You were logged in')
            return redirect(url_for('show_entries'))

以前配置的USERNAME和PASSWORD

#配置文件
DATABASE = 'G:/python/flask/flaskr/flaskr.db'
DEBUG = True
SECRET_KEY = 'development key'
USERNAME = 'admin'
PASSWORD = 'default'

4. 应用全局变量

只在一个请求内,从一个函数到另外一个函数共享数据,全局变量并不够好。由于这在线程环境下行不通。Flask提供了一个特殊的对象来确保只在活动中的请求有效,而且每一个请求都返回不一样的值。

flask.g
在这上存储你任何你想要存储的。例如一个数据库链接或者当前登入的用户。

从 Flask 0.10 起,对象 g 存储在应用上下文(?)中而再也不是请求上下文中(?),这意味着即便在应用上下文中它也是可访问的而不是只能在请求上下文中。在结合 伪造资源和上下文 模式使用来测试时这尤其有用。

另外,在 0.10 中你可使用 get() 方法来获取一个属性或者若是这个属性没设置的话将获得 None (或者第二个参数)。 这两种用法如今是没有区别的:

user = getattr(flask.g, 'user', None)
user = flask.get.get('user', None)

如今也能在 g 对象上使用 in 运算符来肯定它是否有某个属性,而且它将使用 yield 关键字来生成这样一个可迭代的包含全部keys的生成器。

5. 上下文(context)

Flask从客户端接收请求时,要让视图函数能访问一些对象,这样才能处理请求。请求对象就是一个很好的例子,它封装了客户端发送HTTP请求。

要想让视图函数可以访问请求对象,能够将其做为参数传进视图函数。不过这会致使程序中的每一个视图函数都要增长一个参数。除了访问请求对象,若是视图函数在请求时还要访问其余对象,状况会变得更糟糕。

为了不大量无关紧要的参数把视图函数弄得一团糟,Flask 使用上下文临时把某些对象变为全局可访问。有了上下文,就能够写出下面的视图函数:

from flask import request

@app.route('/')
def index():
    user_agent = request.headers.get('User-Agent')
    return '<p>Your browser is %s</p>' % user_agent

这个视图函数中咱们把request看成全局变量使用。事实上,request不多是全局变量。试想,在多线程服务器中,多个线程同时处理不一样客户端发送的不用请求时,每一个线程看到的request对象必然不一样。Flask使用上下文让特定的变量在一个线程中全局可访问,与此同时却不会干扰其余进程。
F
lask有两种上下文:应用上下文( application context )和请求上下文(request context)。Flask在分发请求以前激活(或推送)应用和请求上下文,请求处理完成后再将其删除。应用上下文被推送后,就能够在线程中使用current_appg变量。相似地,请求上下文被推送后,就可使用requestsession变量。

关于Flask中的before_request、after_request,teardown_request

before_request :在请求收到以前绑定一个函数作一些事情。
after_request:每个请求以后绑定一个函数,若是请求没有异常。
teardown_request:每一次请求后绑定一个函数,即便遇到了异常。

6. execute()

将字符串内容看成命令来执行,即执行原生的SQL语句。

cur = g.db.execute('select title,text from entries order by id desc')
  • desc–降序(descend)
  • asc – 升序(ascend)

程序代码

flask2.py

#导入全部模块
from __future__ import with_statement   #__future__导入必须先于其余的导入
import sqlite3
from flask import Flask,request,session,g,redirect,url_for,abort,render_template,flash
from contextlib import closing

#配置文件
DATABASE = 'G:/python/flask/flaskr/flaskr.db'
DEBUG = True
SECRET_KEY = 'development key'
USERNAME = 'admin'
PASSWORD = 'default'

#建立应用
app = Flask(__name__)
app.config.from_object(__name__)

def connect_db():
    return sqlite3.connect(app.config['DATABASE'])  #使用配置

#初始化数据库
def init_db():
    with closing(connect_db()) as db:
        with open('schema.sql') as f:
            db.cursor().executescript(f.read())
        db.commit()

#在请求收到以前链接数据库
@app.before_request
def before_request():
    g.db = connect_db()


@app.teardown_request
def teardown_request(exception):
    g.db.close()

@app.route('/')
def show_entries():
    cur = g.db.execute('select title,text from entries order by id desc')
    entries = [dict(title=row[0],text=row[1]) for row in cur.fetchall()]
    return render_template('show_entries.html',entries=entries)

#添加新条目,实际的表单是显示在show_entries页面
@app.route('/add',methods=['POST']) # 只回应POST请求
def add_entry():
    if not session.get('logged_in'):
        abort(401)
    g.db.execute('insert into entries (title, text) values (?, ?)',[request.form['title'], request.form['text']])
    # 使用问号标记来构建 SQL 语句。不然,当使用格式化字符串构建 SQL 语句时, 应用容易遭受 SQL 注入。
    g.db.commit()
    flash('New entry was successfully posted')   # 用flash()向下一个请求闪现一条信息
    return redirect(url_for('show_entries'))   # 重定向,跳转回show_entries页

# 登陆
@app.route('/login',methods=['GET','POST'])
def login():
    error = None
    if request.method == 'POST':
        if request.form['username'] != app.config['USERNAME']:
            error = 'Invalid username'
        elif request.form['password'] != app.config['PASSWORD']:
            error = 'Invalid password'
        else:
            session['logged_in'] = True
            flash('You were logged in')
            return redirect(url_for('show_entries'))
    return render_template('login.html', error=error)  # 用login.html模板通知,提示从新登陆

#注销
@app.route('/logout')
def logout():
    session.pop('logged_in', None)   # 不须要检查用户是否已经登入,由于若是不存在则什么都不作
    flash('You were logged out')
    return redirect(url_for('show_entries'))

init_db()
if  __name__=='__main__':
    app.run()

layout.html

<!doctype html> <title>Flaskr</title> <link rel=stylesheet type=text/css href="{{ url_for('static', filename='style.css') }}"> <div class=page> <h1>Flaskr</h1> <div class=metanav> {% if not session.logged_in %} <a href="{{ url_for('login') }}">log in</a> {% else %} <a href="{{ url_for('logout') }}">log out</a> {% endif %} </div> {% for message in get_flashed_messages() %} <div class=flash>{{ message }}</div> {% endfor %} {% block body %}{% endblock %} </div>

login.html

{% extends "layout.html" %} {% block body %} <h2>Login</h2> {% if error %}<p class=error><strong>Error:</strong> {{ error }}{% endif %} <!-- 即error不为None时,就显示出错误缘由。--> <form action="{{ url_for('login') }}" method=post> <!--action后面是form表单提交的地址 --> <dl> <dt>Username: <dd><input type="text" name="username"> <dt>Password: <dd><input type="password" name="password"> <dd><input type="submit" value="Login"> </dl> </form> {% endblock %}

show_entries.html

{% extends "layout.html" %} <!--模板继承--> {% block body %} {% if session.logged_in %} <form action="{{ url_for('add_entry') }}" method=post class=add-entry> <dl> <dt>Title: <dd><input type=text size=30 name=title> <dt>Text: <dd><textarea name=text rows=5 cols=40></textarea> <dd><input type=submit value=Share> </dl> </form> {% endif %} <ul class=entries> {% for entry in entries %} <li><h2>{{ entry.title }}</h2>{{ entry.text|safe }} {% else %} <li><em>Unbelievable. No entries here so far</em> {% endfor %} </ul> {% endblock %}