1. 程式人生 > >Flask框架搭建一個日程表

Flask框架搭建一個日程表

目錄

前言

build status

用Flask框架,SQLalchemy,SQlite

Vertabelo 搭建一個日程表。

這個並不是最終產品,目的是展示python web開發的流程,歡迎不吝賜教!

github下載原始碼

專案介紹

使用Flask開發一個增刪改查(CRUD)應用程式:

  • add/update 更新、新增一個類別
  • delete/mark 標記或刪除
  • manage categories. 管理類別

三個檢視:

  1. 建立類別

  1. 建立待辦事項

使用者可以通過填寫帶有描述的表單,從現有優先順序中選擇優先順序並從現有類別中選擇類別來建立新待辦事項。

  1. 管理檢視

主檢視列出了所有待辦事項和可用類別,通過單擊螢幕左側類別選單中的類別,將列出所選類別的待辦事項列表。

技術棧

  • 資料庫:SQLite
  • 資料庫ORM:SQLAlchemy
  • 路由框架:Flask
  • CSS框架:Bootstrap

Flask Web開發流程

  • SQLAlchemy:獲取資料庫ORM物件
  • Flask:Web伺服器閘道器介面(WSGI)
  • Bootstrap:組合HTML,CSS和JavaScript

注:ORM (Object-relational Mapping) ,關係模型對映到一個物件。pyrhon中最流行是SQLAlchemy;

一、搭建環境

1.1: 建立虛擬環境

win系統

virtualenv venv
venv\Scripts\activate

1.2: 安裝依賴包

  • Flask:web骨架
  • SQLAlchemy:資料庫ORM框架
pip install Flask
pip install Flask-SQLAlchemy

1.3: 建立依賴包列表檔案

requirements.txt

$ pip freeze > requirements.txt.

1.4: 測試hello word

run.py

from flask import Flask

app = Flask(__name__)

from app.views import *
if __name__ == '__main__':
    app.run()

views.py

from run import app
 
@app.route('/')
def index():
  return '<h1>Hello World!</h1>'
python .\run.py

二、應用程式開發

2.1:應用程式結構

|-- app/
|   |-- __init__.py
|   |-- data/
|   |   `-- todoapp.db
|   |-- models.py   # SQLAlchemy 資料庫模型
|   |-- static/     #靜態資料夾包含所有的CSS和JavaScript檔案
|   |   |-- css/
|   |   |-- fonts/
|   |   `-- js/
|   |-- templates/  #該模板資料夾中包含Jinja2的模板
|   |   |-- layout.html
|   |   |-- list.html
|   |   |-- macros.html
|   |   |-- new-category.html
|   |   `-- new-task.html
|   `-- views.py    #檢視函式
|-- config.py       #配置函式
|-- manage.py       #Flask-Script支援命令列選項
|-- readme.md       
`-- run.py  

2.2:資料庫設計

業務邏輯:

  • 將代辦事項分類
  • 待辦事項按條記錄在都將在todos表中
  • 待辦事項表將具有所需的id,description,creation_date等欄位
  • 代辦事項有優先順序,按照優先順序排序

線上建模工具:vertabelo

根據需求

app\models.py

from datetime import datetime
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from app import app

app = Flask(__name__)
db = SQLAlchemy(app)
 
class Todo (db.Model):
    __tablename__ = "todo"
    id = db.Column('id', db.Integer, primary_key=True)
    category_id = db.Column('category_id', db.Integer, db.ForeignKey('category.id'))
    priority_id = db.Column('priority_id', db.Integer, db.ForeignKey('priority.id'))
    description = db.Column('description', db.Unicode)
    creation_date = db.Column('creation_date', db.Date,default=datetime.utcnow)
    is_done = db.Column('is_done', db.Boolean, default=False)
 
    priority = db.relationship('Priority', foreign_keys=priority_id)
    category = db.relationship('Category', foreign_keys=category_id)
 
class Priority (db.Model):
    __tablename__ = "priority"
    id = db.Column('id', db.Integer, primary_key=True)
    name = db.Column('name', db.Unicode)
    value = db.Column('value', db.Integer)

class Category (db.Model):
    __tablename__ = "category"
    id = db.Column('id', db.Integer, primary_key=True)
    name = db.Column('name', db.Unicode)

2.2:配置和初始檔案

Flask-SQLAlchemy資料庫URI格式:

Database engine URL
MySQL mysql://username:[email protected]/database
Postgres postgresql://username:[email protected]/database
SQLite (Unix) sqlite:////absolute/path/to/database
SQLite (Windows) sqlite:///c:/absolute/path/to/database

config.py

class Config(object):
    SECRET_KEY = 'you-will-never-guess'
    SQLALCHEMY_DATABASE_URI = 'sqlite:///data/todoapp.db '
    SQLALCHEMY_TRACK_MODIFICATIONS = False

app/__init__.py

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from config import Config

app = Flask(__name__)
app.config.from_object(Config)
db = SQLAlchemy(app)

from app import views, models

2.3: 建立資料庫和表

  • 安裝ipython,建立初始資料庫更加友好
pip install ipython
  • 建立資料庫和表。
from models import db
from app.models import db
db.create_all()
  • 插入資料分三步
    1. 建立一個Python物件
    2. 將其新增到會話中
    3. 提交會話
  • 提交一些類別
from app.models import Category
work = Category(name=u'work')
home = Category(name=u'home')
db.session.add(work)
db.session.add(home)
db.session.commit()
  • 建立優先順序表
from models import Priority
high = Priority(name=u'high', value=3)
medium = Priority(name=u'medium', value=2)
low = Priority(name=u'low', value=1)
db.session.add(high)
db.session.add(medium)
db.session.add(low)
db.session.commit()
  • 更多更改
db.session.add(object)
  • 檢查資料庫

2.4:檢視函式

檢視函式是python指令碼,接受一個http請求並返回一個http響應。此響應可以是HTML頁面或重定向。

2.4.1 @app.route('/')

  • @app.route('/') 響應的路徑
  • route將函式註冊為URL
  • 函式的返回值叫做響應,是瀏覽器接收的值

views.py

from flask import render_template, request
from models import Category, Todo, Priority, db
# 列出我們按優先順序值和所有類別降序排列的所有TODO專案。
@app.route('/')
def list_all():
   return render_template(
       'list.html',
       categories=Category.query.all(),
       todos=Todo.query.join(Priority).order_by(Priority.value.desc())

2.4.2 render_template

Flask和Jinja2之間的整合是通過該render_template功能實現。

Jinja2 負責生成HTML,Flask提供模板的名稱和引數作為Keyword,keyword是鍵值對

上面的函式中,categoriestodos是在分配了當前值的模板中寫入的佔位符。這些當前值是從資料庫中檢索的準確值。

2.4.3 建立代辦事項

從資料庫讀取資料,在瀏覽器展現代辦事項。

@app.route('/new-task', methods=['POST']),函式響應一個POST請求

伺服器從瀏覽器接收到POST後,將資料插入到資料庫,操作成功重新定向頁面;如果出現問題,將再次呈現新待辦事項。

@app.route('/new-task', methods=['POST'])
def new():
   if request.method == 'POST':
       category = Category.query.filter_by(id=request.form['category']).first()
       priority = Priority.query.filter_by(id=request.form['priority']).first()
       todo = Todo(category, priority, request.form['description'])
       db.session.add(todo)
       db.session.commit()
       return redirect('/')
   else:
       return render_template(
           'new-task.html',
           page='new-task',
           categories=Category.query.all(),
           priorities=Priority.query.all()
       )

2.4.4 更新代辦事項

@app.route('/<int:todo_id>', methods=['GET', 'POST'])

GET:伺服器收到瀏覽器GET請求,渲染模板,並檢索資料庫追加給新的頁面
POST:如果瀏覽器輸入值,伺服器將更新資料庫

瀏覽器

@app.route('/<int:todo_id>', methods=['GET', 'POST'])
def update_todo(todo_id):
   todo = Todo.query.get(todo_id)
   if request.method == 'GET':
       return render_template(
           'new-task.html',
           todo=todo,
           categories=Category.query.all(),
           priorities=Priority.query.all()
       )
   else:
       category = Category.query.filter_by(id=request.form['category']).first()
       priority = Priority.query.filter_by(id=request.form['priority']).first()
       description = request.form['description']
       todo.category = category
       todo.priority = priority
       todo.description = description
       db.session.commit()
       return redirect('/')

2.4.5 檢視完整程式碼

app/views.py


from flask import render_template, request, redirect, flash,url_for
from app.models import Category, Todo, Priority, db
from app import app

@app.route('/')
def list_all():
    return render_template(
        'list.html',
        categories=Category.query.all(),
        todos=Todo.query.all(),#join(Priority).order_by(Priority.value.desc())
    )


@app.route('/<name>')
def list_todos(name):
    category = Category.query.filter_by(name=name).first()
    return render_template(
        'list.html',
        todos=Todo.query.filter_by(category=category).all(),# .join(Priority).order_by(Priority.value.desc()),
        categories=Category.query.all(),

    )


@app.route('/new-task', methods=['GET', 'POST'])
def new():
    if request.method == 'POST':
        category = Category.query.filter_by(id=request.form['category']).first()
        #priority = Priority.query.filter_by(id=request.form['priority']).first()
        #todo = Todo(category=category, priority=priority, description=request.form['description'])
        todo = Todo(category=category, description=request.form['description'])
        db.session.add(todo)
        db.session.commit()
        return redirect(url_for('list_all'))
    else:
        return render_template(
            'new-task.html',
            page='new-task',
            categories=Category.query.all(),
            #priorities=Priority.query.all()
        )


@app.route('/<int:todo_id>', methods=['GET', 'POST'])
def update_todo(todo_id):
    todo = Todo.query.get(todo_id)
    if request.method == 'GET':
        return render_template(
            'new-task.html',
            todo=todo,
            categories=Category.query.all(),
            #priorities=Priority.query.all()
        )
    else:
        category = Category.query.filter_by(id=request.form['category']).first()
        #priority = Priority.query.filter_by(id=request.form['priority']).first()
        description = request.form['description']
        todo.category = category
        #todo.priority = priority
        todo.description = description
        db.session.commit()
        return redirect('/')


@app.route('/new-category', methods=['GET', 'POST'])
def new_category():
    if request.method == 'POST':
        category = Category(name=request.form['category'])
        db.session.add(category)
        db.session.commit()
        return redirect('/')
    else:
        return render_template(
            'new-category.html',
            page='new-category.html')


@app.route('/edit_category/<int:category_id>', methods=['GET', 'POST'])
def edit_category(category_id):
    category = Category.query.get(category_id)
    if request.method == 'GET':
        return render_template(
            'new-category.html',
            category=category
        )
    else:
        category_name = request.form['category']
        category.name = category_name
        db.session.commit()
        return redirect('/')


@app.route('/delete-category/<int:category_id>', methods=['POST'])
def delete_category(category_id):
    if request.method == 'POST':
        category = Category.query.get(category_id)
        if not category.todos:
            db.session.delete(category)
            db.session.commit()
        else:
            flash('You have TODOs in that category. Remove them first.')
        return redirect('/')


@app.route('/delete-todo/<int:todo_id>', methods=['POST'])
def delete_todo(todo_id):
    if request.method == 'POST':
        todo = Todo.query.get(todo_id)
        db.session.delete(todo)
        db.session.commit()
        return redirect('/')


@app.route('/mark-done/<int:todo_id>', methods=['POST'])
def mark_done(todo_id):
    if request.method == 'POST':
        todo = Todo.query.get(todo_id)
        todo.is_done = True
        db.session.commit()
        return redirect('/')

2.5:模板

2.5.1 模板簡介

模板能使前後端分開,是包含響應的HTML檔案;檔案包含的變數僅在請求的上下文有效;通過Jinja2模板引擎渲染

Jinja2模板引擎官網

2種分隔符:

  • {{ variable }} 用於變數
  • {% control structures %} 用於控制結構

2.5.2 解析一個檢視函式:

在主頁上顯示類別和代辦事項,

為了獲得所有類別和TODO,用for語句{% ... %}和特定類別的塊內迴圈迭代,顯示其名稱。

@app.route('/')
def list_all():
   return render_template(
       'list.html',
       categories=Category.query.all(),
       todos=Todo.query.join(Priority).order_by(Priority.value.desc())

相應的模板如下所示。

<h3>Category</h3><font></font>
    <table><font></font>
    {%- for category in categories %}<font></font>
        <tr><font></font>
            <td>{{ category.name }}</td><font></font>
        </tr><font></font>
    {%- endfor %}<font></font>
</table><font></font>

2.5.3 模板繼承

在主頁頂部有一個導航欄,其中包含指向create_category頁面和create_todo頁面以及頁尾的連結。我希望在每個頁面上重複這些元件。為了在所有頁面上保留公共元素,我將使用模板繼承。

繼承是塊控制語句。在此結構中,您可以定義派生模板可以插入其內容的位置。

layout.html負責一般結構。extends塊建立三個模板之間的繼承(它用於從另一個模板擴充套件模板)。

2.6:Bootstrap美化Web應用

Bootstrap是最流行的Web開發前端框架。

2.6.1 下載外掛

下載Bootstrap,解壓到/static

Bootstrap教程

Navbar圖示元素

2.6.2 Layout.html基本模板

layout.html是其餘模板的基本佈局。用navbar元素構建一個漂亮的導航欄,container-fluid使頁面全屏

Menu
<nav class="navbar navbar-inverse">
<div class="container-fluid">
<div class="navbar-header">
    <a class="navbar-brand" href="/">TODOapp</a>
</div>
            <ul class="nav navbar-nav">
            <li><a href="/new-task">New TODO</li>
            <li><a href="/new-category">New category</li>     
            </ul>
</div>
</nav>
 
<div class="container-fluid">
Messages
</div>
 
<div class="container-fluid">
    Footer
</div>

2.6.3 List.html

List.html負責列出TODO和類別。此模板繼承自layout.html。在這裡,我添加了Bootstrap網格系統。

預設情況下,網格系統最多可擴充套件到12列,可以更改為四個裝置(大屏桌上型電腦,桌上型電腦,平板電腦和手機) 。

由於每行有12個單元,我製作了兩列,一個是3個單元,第二個是9個單元。

網格系統要求將行放在.container.container-fluid中以進行正確的對齊和填充。

Bootstrap網格系統的更多資訊

2.6.4 基本結構

要建立佈局:

  • 建立一個容器 <div class="container">
  • 建立一行 <div class="row">
  • 在一行中新增所需的列數。

結構如下:

<div class="container">
    <div class="row">
        <div class="col-md-2">Table with categories</div>
        <div class="col-md-10">Table with TODOs</div>
    </div>
</div>

2.6.5 new-task.html

使用自定義的.selectpicker 對類別表單和優先順序表單進行簡單的選擇

  • 下載Bootstrap-select,解壓到/static
  • layout.html啟用 Bootstrap-Select via JavaScript
    $('.selectpicker').selectpicker();

    三 、驗證

python .\run.py