python flask實現小項目方法

本文目的是爲了完成一個項目用到的flask基本知識,例子會逐漸加深。最好對着源碼,一步一步走。
下載源碼,運行
pip install -r requirements.txt 創建環境
python db_create.py 建立本身數據庫
python db_migrate 遷移數據庫
————————————————————————————–
flask 不只簡介小巧,同時運用的時候十分靈活。下面簡單介紹一下如何編寫一個 flask項目。
涉及調用開發服務器,數據庫鏈接以及 ORM 映射,還有數據庫的遷移,模板使用。後期再完善會話,管理後臺,緩存等。
一 、安裝環境
咱們使用 flask web框架,並用 sqlalchemy來作數據庫映射,並使用 migrate作數據遷移。
$ pip install flask
$ pip install SQLAlchemy==0.7.9
$ pip install flask-sqlalchemy
$ pip install flask-migrate
$ pip install sqlalchemy-migrate
 
2、創建項目
flask 沒有 django 那樣原生的 manage管理工具(flask-admin能夠實現,往後再說)。所以咱們須要手動創建目錄。新建一個 myproject目錄,在裏面建 app tmp兩個文件夾,而後在 app文件夾裏面創建 static, templates 兩個文件夾,用來存儲靜態文件和模板。最後目錄結構以下:
├── app
│ ├── static
│ ├── templates
└── tmp
 
3、 初始化文件
創建一些文件,在 app文件裏創建 __init__.py models.py views.py 而後在與app 同目錄下創建 config.py 和 run.py 此時的目錄結構以下:

(env)admin@admindeMacBook-Air:~/project/python/flask/project$ tree

├── app
│ ├── static
│ ├── templates
│ ├── __init__.py
│ ├── models.py
│ ├── views.py
├── config.py
├── run.py
└── tmp

 
四 、開始項目
1 hello wrod
打開 (/app/__init.py) 文件,寫入
# -*- coding: utf-8 -*-
from flask import Flask # 引入 flask
app = Flask(__name__) # 實例化一個flask 對象
import views # 導入 views 模塊
# from app import views
注意,咱們的 app 文件夾實際上是一個python包,from app import views 這句話也就是從 包app裏面導入 views模塊,因此寫成註釋的那句話也沒錯,其餘代碼實踐喜歡寫成後面那種
如今咱們來開始寫咱們的視圖,打開 (/app/views.py)

# -*- coding: utf-8 -*-
from app import app
from models import User, Post, ROLE_USER, ROLE_ADMIN
@app.route(‘/’)
def index():
    return ‘hello world, hello flask’

下一步咱們將要啓動咱們的開發服務器,打開 (/run.py)
# -*- coding: utf-8 -*-
from app import app
if __name__ == ‘__main__’:
    app.debug = True # 設置調試模式,生產模式的時候要關掉debug
    app.run() # 啓動服務器
這段代碼須要注意第一句 from app import app。也許會有疑問,咱們的 app 包裏,貌似沒有 app.py 這樣的模塊。其實這是 flask 方式和python的導入方式。from app 指導入 app 包裏的 __iniy__.py 因此這句話的含義是導入 __.init__.py 裏面的 app實例。
打開shell 運行
$ python run.py
(env)admin@admindeMacBook-Air:~/project/python/flask/project$ python run.py

* Running on http://127.0.0.1:5000/
* Restarting with reloader
用瀏覽器打開 http://127.0.0.1:5000/ 將會看到一個輸入 hello world 的頁面
2 鏈接數據庫
下面開始鏈接數據庫引擎 先要作一個簡單的配置 打開 (/config.py)

# -*- coding: utf-8 -*-

import os
basedir = os.path.abspath(os.path.dirname(__file__))
SQLALCHEMY_DATABASE_URI = ‘sqlite:///%s’ % os.path.join(basedir, ‘app.db’)
SQLALCHEMY_MIGRATE_REPO = os.path.join(basedir, ‘db_repository’)
CSRF_ENABLED = True
SECRET_KEY = ‘you-will-never-guess’

SQLALCHEMY_DATABASE_URI是the Flask-SQLAlchemy必需的擴展。這是咱們的數據庫文件的路徑。
SQLALCHEMY_MIGRATE_REPO 是用來存儲SQLAlchemy-migrate數據庫文件的文件夾。
而後打開 (/app/__init__.py)

# -*- coding: utf-8 -*-
import os
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
from config import basedir

app = Flask(__name__)
app.config.from_object(‘config’) # 載入配置文件
db = SQLAlchemy(app) # 初始化 db 對象
# from app import views, models # 引用視圖和模型
  import views, models
 

 
打開 (/app/models.py)

# -*- coding: utf-8 -*-

from app import db
ROLE_USER = 0
ROLE_ADMIN = 1

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    nickname=db.Column(db.String(60), index=True, unique=True)
    email = db.Column(db.String(120), index=True, unique=True)
    role = db.Column(db.SmallInteger, default=ROLE_USER)
    
    def __repr__(self):
        return ‘<User %r>’ % self.nickname

咱們的模型創建了一個 user類,正好是 數據庫裏面的 user 表,表有四個字段
如今咱們的數據庫配置已經好了,但是尚未創建數據庫。新建一個文件 (/db_create.py)

# -*- coding: utf-8 -*-

from migrate.versioning import api
from config import SQLALCHEMY_DATABASE_URI
from config import SQLALCHEMY_MIGRATE_REPO
from app import db
import os.path

db.create_all()

if not os.path.exists(SQLALCHEMY_MIGRATE_REPO):
    api.create(SQLALCHEMY_MIGRATE_REPO, 'database repository')
    api.version_control(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)
else:
    api.version_control(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO, api.version(SQLALCHEMY_MIGRATE_REPO))

 
這個腳本是徹底通用的,全部的應用路徑名都是從配置文件讀取的。當你用在本身的項目時,你能夠把腳本拷貝到你的項目目錄下就能正常使用了。
打開shell 運行
$ python db_create.py

運行這條命令以後,你就建立了一個新的app.db文件。這是個支持遷移的空sqlite數據庫,同時也會生成一個帶有幾個文件的db_repository目錄,這是SQLAlchemy-migrate存儲數據庫文件的地方,注意若是數據庫已存在它就不會再從新生成了。這將幫助咱們在丟失了現有的數據庫後,再次自動建立出來。
運行
slqite3 app.db
>>> .tables
>>> user
或者使用 sqlite 圖形化客戶端,沒有須要安裝 (windows 下直接下載安裝包便可)
$ sudo apt-get install sqlitebrowser
3 數據庫遷移
每當咱們更改了 models 。等價於更改了數據庫的表結構,這個時候,須要數據庫同步。新建 (/db_migrate.py)

# -*- coding: utf-8 -*-

import imp
from migrate.versioning import api
from app import db
from config import SQLALCHEMY_DATABASE_URI
from config import SQLALCHEMY_MIGRATE_REPO

migration = SQLALCHEMY_MIGRATE_REPO + '/versions/%03d_migration.py' % (api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO) + 1)
tmp_module = imp.new_module('old_model')
old_model = api.create_model(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)

exec old_model in tmp_module.__dict__

script = api.make_update_script_for_model(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO, tmp_module.meta, db.metadata)

open(migration, 'wt').write(script)

api.upgrade(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)

print 'New migration saved as' + migration
print 'Current database version:' + str(api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO))

 這個腳本看起來很複雜,SQLAlchemy-migrate經過對比數據庫的結構(從app.db文件讀取)和models結構(從app/models.py文件讀取)的方式來建立遷移任務,二者之間的差別將做爲一個遷移腳本記錄在遷移庫中,遷移腳本知道如何應用或者撤銷一次遷移,因此它能夠方便的升級或者降級一個數據庫的格式。
開始數據庫遷移
$ python db_mirgate.py
New migration saved as db_repository/versions/001_migration.py Current database version: 1
相應的,咱們繼續建立數據庫 升級和回退的腳本
(/db_upgrade.py)

# -*- coding: utf-8 -*-

from migrate.versioning import api
from config import SQLALCHEMY_DATABASE_URI
from config import SQLALCHEMY_MIGRATE_REPO
api.upgrade(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)
print 'Current database version:' + str(api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO))

 
(/db_downgrade.py)

# -*- coding: utf-8 -*-


from migrate.versioning import api
from config import SQLALCHEMY_DATABASE_URI
from config import SQLALCHEMY_MIGRATE_REPO

v = api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)
api.downgrade(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO, v – 1)

print 'Current database version:' + str(api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO))

 
4 操做數據庫
接下來,咱們定義一個新的models class 並作第二次遷移歸併
打開(/app/models.py)

# -*- coding: utf-8 -*-

from app import db

ROLE_USER = 0
ROLE_ADMIN = 1

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    nickname=db.Column(db.String(60), index=True, unique=True)
    email = db.Column(db.String(120), index=True, unique=True)
    role = db.Column(db.SmallInteger, default=ROLE_USER)
    posts = db.relationship(‘Post’, db.backref = ‘author’, db.lazyload = ‘dynamic’)

    def __repr__(self):
        return ‘<User %r>’ % self.nickname

class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    body = db.Column(db.String(140))
    timestamp = db.Column(db.DateTime)
    user_id = db.Column(db.Integer, db.ForeignKey(‘user.id’))

    def __repr__(self):
        return ‘<Post %r>’ % (self.body)

 
這裏咱們進行了兩個class 的關聯。
$ python db_mirgrate.py
New migration saved as db_repository/versions/002_migration.py Current database version: 2
打開 (/app/views.py)

# -*- coding: utf-8 -*-

from flask import render_template, flash, redirect, session, url_for, request, g
from app import app, db
from models import User, Post, ROLE_USER, ROLE_ADMIN

@app.route(‘/’)
def index():
    return render_template(‘index.html’)

@app.route(‘/adduser/<nickname>/<email>’)
def adduser(nickname, email):
    u = User(nickname=nickname, email=email)
    try:
        db.session.add(u)
        db.session.commit()
        return ‘add successful’
    except Exception, e:
        return ‘something go wrong’

@app.route(‘/getuser/<nickname>’)
def getuser(nickname):
    user = User.query.filter_by(nickname=nickname).first()
        return render_template(‘user.html’, user=user)

@app.errorhandler(404)
def internal_error(error):
    return render_template(‘404.html’), 404

@app.errorhandler(500)
def internal_error(error):
    db.session.rollback()
    return render_template(‘500.html’), 500

 
此次咱們使用了模板, 新建(/app/templates/user.html)

<html>
<head>
<title>user</title>
</head>
<body>
<h1>user</h1>
<ul>
<li>user: {{ user.nickname }}</li>
<li>email: {{ user.email }}</li>
</ul>
</body>
</html>

 
最後運行
$ python run.py
用瀏覽器訪問 http://127.0.0.1:5000/adduser/username/useremail
最後還能夠用 sqlite 客戶端打開查看咱們的數據庫變換。關於更多的數據庫操做方法,請閱讀 sqlalchemy文檔。
最後咱們的代碼目錄結構以下:

(env)admin@admindeMacBook-Air:~/project/python/flask/project$ tree
.
├── app
│ ├── __init__.py
│ ├── models.py
│ ├── static
│ ├── templates
│ │ ├── 404.html
│ │ ├── 500.html
│ │ ├── index.html
│ │ └── user.html
│ ├── views.py
├── app.db
├── config.py
├── db_create.py
├── db_downgrade.py
├── db_migrate.py
├── db_repository
│ ├── __init__.py
│ ├── manage.py
│ ├── migrate.cfg
│ ├── README
│ └── versions
│ ├── 001_migration.py
│ ├── 002_migration.py
│ ├── __init__.py
├── db_upgrade.py
├── requirements.txt
├── run.py
├── tmp

 
5 表單
接下來,咱們將要接觸表單方面的內容。flask原生提供的表單處理,也比較簡單。固然,有了輪子,咱們就直接用好了。須要安裝一個
Flask-WTF==0.9.4。
安裝完以後,打開 (/app/forms.py)

# -*- coding: utf-8 -*-

import re
from flask_wtf import Form
from flask_wtf.html5 import EmailField
from wtforms import TextField, PasswordField, BooleanField
from wtforms.validators import DataRequired, ValidationError
from models import User
from hashlib import md5

class RegisterFrom(Form):
    nickname = TextField(‘nickname’, validators=[DataRequired()])
    email = EmailField(’email’, validators=[DataRequired()])
    password = PasswordField(‘password’, validators=[DataRequired()])
    conform = PasswordField(‘conform’, validators=[DataRequired()])

    def validate_nickname(self, field):
        nickname = field.data.strip()
if len(nickname) < 3 or len(nickname) > 20:
    raise ValidationError(‘nickname must be 3 letter at least’)
        elif not re.search(r’^\w+$’, nickname):
            raise ValidationError(‘User names can contain only alphanumeric characters and underscores.’)
else:
# 驗證是否已經註冊
            u = User.query.filter_by(nickname=nickname).first()
            if u:
raise ValidationError(‘The nickname already exists’)

    def validate_email(self, field):
        email = field.data.strip()
        email = User.query.filter_by(email=email).first()
        if email:
            raise ValidationError(‘The email already exists’)

    def validate_password(self, field):
        password = field.data.strip()
        if len(password) < 6:
            raise ValidationError(‘password must be 3 letter at least’)

    def validate_conform(self, field):
        conform = field.data.strip()
        if self.data[‘password’] != conform:
            raise ValidationError(‘the password and conform are different’)

 
上述代碼定義了一個 RegisterFrom class,正好是一個表單,class的字段映射爲表單的域。其中 wtforms 中有 TextField, PasswordField, BooleanField這些表單類型,flask_wtf.html5 中 EmailField 則是 html5 規範的。
表單有 form.validate_on_submit() 方法,這個方法執行以前會驗證表單域。驗證方法和django相似,都是 validate_field 的方法。其中一個參數 field正好是表單的value值 須要注意驗證兩次密碼是否相同的時候,須要用到 self.data 。這是一個字典,包含了表單域的name和value
6 用戶註冊
打開(/app/views.py)添加註冊方法。

@app.route(‘/account/signup’, methods=[‘GET’, ‘POST’])
def signup():
    form = RegisterFrom()
if request.method == ‘POST’:
if form.validate_on_submit():
            psdmd5 = md5(form.data[‘password’])
            password = psdmd5.hexdigest()
            u = User(nickname=form.data[‘nickname’], email=form.data[’email’], password=password)
try:
            db.session.add(u)
            db.session.commit()
            flash(‘signup successful’)

except Exception, e:
    return ‘something goes wrong’
    return redirect(url_for(‘signin’))

        return render_template(‘signup.html’, form=form)

 
模版 新建 (/app/tempaltes/signup.html)

<!– extend base layout –>
{% extends 「base.html」 %}

{% block content %}
<form method=」POST」 action=」>
{{ form.hidden_tag() }}
<p>{{ form.nickname.label }} {{ form.nickname(size=20) }}</p>
<p>{{ form.email.label }} {{ form.email(size=20) }}</p>
<p>{{ form.password.label }} {{ form.password(size=20) }}</p>
<p>{{ form.conform.label }} {{ form.conform(size=20) }}</p>
<input type=」submit」 value=」Sign Up」>
<input type=」reset」 value=」Reset」>
</form>

{{ form.errors }}
{% endblock %}

 
其中,主要是用到 form 對象的一些屬性。form.nickname.label 是指表單類定義的名字,form.nickname 會轉變成 表單域的 html 代碼 即 <input type=」text」 id=」nickname」 name=」nickaname」 value=」」/>
 
7 用戶登陸
先添加一個用戶登陸的表單,打開 (/app/forms.py)

class LoginForm(Form):
    nickname = TextField(‘nickname’, validators=[DataRequired()])
    password = PasswordField(‘password’, validators=[DataRequired()])
    remember_me = BooleanField(‘remember_me’, default=False)

    def validate_nickname(self, field):
        nickname = field.data.strip()
        if len(nickname) < 3 or len(nickname) > 20:
            raise ValidationError(‘nickname must be 3 letter at least’)
        elif not re.search(r’^\w+$’, nickname):
            raise ValidationError(‘User names can contain only alphanumeric characters and underscores.’)
        else:
            return nickname

 
用戶登陸,最簡單的方法就是 驗證用戶名,若是經過,則添加 session,登出的時候,清除session便可,模版裏面能夠直接使用 request.session用來判斷用戶登陸狀態
打開(/app/views.py) 添加登陸登出方法

@app.route(‘/account/signin’, methods=[‘GET’, ‘POST’])
def signin():
    form = LoginForm()
if request.method == ‘POST’:
if form.validate_on_submit():
            nickname = form.data[‘nickname’]
            psdmd5 = md5(form.data[‘password’])
            password = psdmd5.hexdigest()
            u = User.query.filter_by(nickname=nickname, password=password).first()
if u:
                session[‘signin’] = True
                flash(‘signin successful’)
                return redirect(url_for(‘index’))
else:
                flash(u’用戶名或者密碼錯誤’)
                return render_template(‘signin.html’, form=form)

@app.route(‘/account/signout’)
def signout():
    session.pop(‘signin’, None)
    flash(‘signout successful’)
    return redirect(‘index’)

 
注意,咱們使用 flask 的時候,用了中文,就必須是 unicode
除了原生的登陸方式,咱們還能夠用flask-login。安裝flask-login
此時須要初始化一個 login_manager 對象,打開 (/app/__init__.py)

# -*- coding: utf-8 -*-

import os
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
from flask.ext.login import LoginManager
from config import basedir

app = Flask(__name__)

app.config.from_object(‘config’)

db = SQLAlchemy(app) # 初始化數據庫管理對象
login_manager = LoginManager() # 初始化登陸管理對象
login_manager.init_app(app)
login_manager.login_view = 「signin」 # 登陸跳轉視圖
login_manager.login_message = u」Bonvolu ensaluti por uzi tio paĝo.」 # 登陸跳轉視圖前的輸出消息

# from app import views, models

import views, models

 
而後打開 (/app/views.py)
添加下面方法, 註釋掉以前的 登入登出方法

from flask.ext.login import login_user, logout_user, current_user, login_required
from app import app, db, login_manager

@login_manager.user_loader
def load_user(userid):
return User.query.get(userid)

@app.route(‘/account/signin’, methods=[‘GET’, ‘POST’])
def signin():
    form = LoginForm()
    if request.method == ‘POST’:
        if form.validate_on_submit():
            nickname = form.data[‘nickname’]
            psdmd5 = md5(form.data[‘password’])
            password = psdmd5.hexdigest()
            remember_me = form.data[‘remember_me’]
            user = User.query.filter_by(nickname=nickname, password=password).first()
if user:
                login_user(user, remember = remember_me)
                flash(‘signin successful’)
                return redirect(request.args.get(「next」) or url_for(「index」))
            else:
                flash(u’用戶名或者密碼錯誤’)
return render_template(‘signin.html’, form=form)

@app.route(‘/account/signout’)

@login_required
def signout():
    logout_user()
    flash(‘signout successful’)
return redirect(‘index’)

 
模版也會響應的改,具體看源碼吧。
base.html

<html>
<head>
<meta http-equiv=」Content-Type」 content=」text/html; charset=utf-8″ />
{% if title %}
<title>{{title}} – microblog</title>
{% else %}
<title>microblog</title>
{% endif %}
</head>
<body>
<div class="metanav">

<div>
<a href=」{{ url_for(‘index’) }}」>Home</a>
{% if current_user.is_authenticated() %}
<a href=」{{ url_for(‘signout’) }}」>signout</a>
{% else %}
<a href=」{{ url_for(‘signin’) }}」>signin</a>
{% endif %}
</div>

{% for message in get_flashed_messages() %}
<div class="flash">{{ message }}</div>
{% endfor %}
{% block content %}{% endblock %}
</body>
</html>
相關文章
相關標籤/搜索