Tornado + Bootstrap 快速搭建自己的web應用


前言

最近用 python tordado 框架, 整了一個模板頁面, 用於接入與發布數據的展示,

tornado 簡單易用, bootstrap 比較流行, 用起來也省事, 配合起來做些小案例非常迅速.


技術儲備

  1. python

    基礎知識, 面向對象封裝,繼承

  2. 數據庫

    mysql

  3. 框架

    tornado, sqlalchemy (ORM), template

  4. 開發工具

    pycharm, chrome


功能開發

一. 需求分析

  1. 每頁展示 5 條統計數據, 按日期倒序排列

    沒有統計數據時, 要有文字提示: "暫沒有對接統計信息"

  2. 支持按日期進行查詢

    查詢時不顯示分頁情況

  3. 支持分頁查詢, 提示目前所在頁碼, 可跳轉到任意一頁


二. 頁面原型

個人前端水平有限, 弄這樣一個頁面從無到有估計得花半天時間, 所以就找了 bootstrap 的模板, 里邊有很多現成的組件可以直接使用, 然后改下布局, 調下樣式, 做一個簡單的頁面足夠.

bootstrap : https://v3.bootcss.com/components/


三. 搭建 tornado 框架

熟悉 python 的同學, 直接 easy_install 或者 pip 安裝最新的 tornado, sqlalchemy, pymysql 等模塊

bootstrap.js 直接到官網下載就行, 當然還需要 bootstrap 依賴的 JQuery.js

app.py

配置靜態資源路徑:

"static_path": os.path.join(os.path.dirname(__file__), "statics"),

配置模板路徑:

"template_path": os.path.join(os.path.dirname(__file__), 'templates'),

配置運行模式:

# 通過腳本傳參的方式指定
define("debug", default=1, help="debug mode: 1 to open, 2 to test env, other to production")
"debug": bool(options.debug),

開發, 調試模式, 開啟單進程:

application.listen(options.port)
tornado.ioloop.IOLoop.instance().start()

生產模式, 開啟多進程:

application.bind(options.port)
application.start(3)  # 開啟 3 個進程
tornado.ioloop.IOLoop.instance().start()

四. 連接 mysql

獲取了DBSession 類, 在需要使用的地方, 創建對象[ session = DBSession() ]即可,

如果數據庫操作不是特別平凡, 會話回收的時間周期可以設置的長一點, 例如: pool_recycle = 60

# coding: utf-8
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy import create_engine
from settings.setting import MYSQL_SERVER, MYSQL_DRIVER, MYSQL_USERNAME, MYSQL_PASSWORD, DB_NAME, DB_CHARSET

# MYSQL_USERNAME = os.getenv('MYSQL_USER') or settings.MYSQL_USERNAME
# MYSQL_PASSWORD =

# 數據庫
engine = create_engine("mysql+{driver}://{username}:{password}@{server}/{database}?charset={charset}"
                       .format(driver=MYSQL_DRIVER,
                               username=MYSQL_USERNAME,
                               password=MYSQL_PASSWORD,
                               server=MYSQL_SERVER,
                               database=DB_NAME,
                               charset=DB_CHARSET),
                       pool_size=20,
                       max_overflow=100,
                       pool_recycle=1,
                       echo=False)
engine.execute("SET NAMES {charset};".format(charset=DB_CHARSET))
MapBase = declarative_base(bind=engine)
DBSession = sessionmaker(bind=engine)

五. 處理類

基礎類, 定義了一些 API 規范和常用的工具方法

先封裝了基礎類 GlobalBaseHandler , 數據庫會話和獲取參數的方法封裝

# 全局基類方法
class GlobalBaseHandler(BaseHandler):
    @property
    def session(self):
        if hasattr(self, "_session"):
            return self._session
        self._session = DBSession()
        return self._session

    # 關閉數據庫會話
    def on_finish(self):
        if hasattr(self, "_session"):
            self._session.close()

    def prepare(self):
        pass

查詢, 分頁業務處理類

需要注意的是: (小於第1頁情況); (大於總頁數的情況, 但總頁數為 0 的情況).

class IllegalStats(GlobalBaseHandler):
    def get(self):
        """
        獲取接入,發布統計數據
        :return:
        """
        date = self.get_argument("date")
        current_page = self.get_argument("current_page")  # 當前頁
        if date:
            return self.query_by_date(date)
        elif current_page:
            return self.query_paginate(current_page)
        # 默認返回第一頁的數據
        else:
            return self.query_paginate()

    def query_paginate(self, current_page=None):
        if current_page is None:
            current_page = 1
        else:
            current_page = int(current_page)
        #
        page_size = 5  # 分頁條數
        # 總記錄數, 總頁數
        total_count = IllegalAccessStats.query_count(self.session)
        if total_count % page_size == 0:
            total_page = int(total_count / page_size)
        else:
            total_page = int(total_count / page_size) + 1

        # 小於第一頁
        if current_page <= 0:
            current_page = 1
        # 大於最后一頁
        if total_page > 0:
            if current_page > total_page:
                current_page = total_page
        else:
            current_page = 1

        # 當前頁的數據
        stats_list = IllegalAccessStats.query_paginate(self.session, current_page=current_page, page_size=page_size)
        return self.render("statistics.html", stats_list=stats_list, date="", total_count=total_count, total_page=total_page, current_page=current_page)

    def query_by_date(self, date):
        stats_list = IllegalAccessStats.query_by_date(self.session, date=date)
        #
        return self.render("statistics.html", stats_list=stats_list, date=date, total_count=None, total_page=None, current_page=None)

六. 模板渲染

template, 就是在 html 里邊寫 python 代碼, if 判斷, for range 語句和python語法一模一樣, 沒有太大難度, 慢慢調試就出來了, 模板渲染報錯提示非常詳細, 很好調試,

感覺寫起來賊惡心, 又是標簽, 又是邏輯處理.

statistics.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <!-- viewport視口:網頁可以根據設置的寬度自動進行適配,在瀏覽器的內部虛擬一個容器,容器的寬度與設備的寬度相同。
    width: 默認寬度與設備的寬度相同
    initial-scale: 初始的縮放比,為1:1 -->
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>接入&發布統計信息</title>
    <!-- Bootstrap -->
    <link href="{{static_url('css/bootstrap.min.css')}}" rel="stylesheet">
    <script src="{{static_url('js/jquery-2.1.0.min.js')}}"></script>
    <script src="{{static_url('js/bootstrap.min.js')}}"></script>
    <style type="text/css">
        th, td {
            text-align: center;
            height: 50px;
            vertical-align:middle;
        }
    </style>
</head>
<body>
<div class="container">
    <h2 style="text-align: center">接入 & 發布統計信息</h2>
    <br>
    <br>

    <div style="float: left;">
        <form class="form-inline" action="/illegal/stats" method="get">
            <div class="form-group">
                <label for="exampleInputName1">日期</label>
                <input type="text" name="date" value="{{ date }}" class="form-control" id="exampleInputName1" placeholder="yyyy-mm-dd" >
            </div>
            <button type="submit" class="btn btn-default" style="margin: 5px;">查詢</button>
        </form>
    </div>

    <br>
    <br>
    <br>
    <br>
    <br>
    <table border="1" class="table table-bordered table-hover">
        <div>
            <a class="btn btn-primary" href="/illegal/stats?" style="float: left; margin: 3px;">接入</a>
            <a class="btn btn-primary" href="/illegal/stats?" style="position: relative; left: 872px; margin: 3px">發布</a>
        </div>

        <tr class="access">
            <th style="background: #f8efc0; vertical-align:middle">日期</th>
            <th style="background: #4cae4c; vertical-align:middle">違法接收</th>
            <th style="background: #4cae4c; vertical-align:middle">入庫成功</th>
            <th style="vertical-align: middle">讀取成功</th>
            <th style="background: #d9534f; vertical-align:middle">讀取失敗</th>
            <th style="vertical-align: middle">下載成功</th>
            <th style="background: #d9534f; vertical-align:middle">下載失敗</th>
            <th style="vertical-align: middle">寫入成功</th>
            <th style="background: #d9534f; vertical-align:middle">寫入失敗</th>
            <th style="background: #4cae4c; vertical-align:middle">審核接收</th>
            <th style="background: #4cae4c; vertical-align:middle">發布成功</th>
        </tr>


        {% if len(stats_list) > 0 %}
            {% for stats in stats_list %}
                <tr>
                <td style="vertical-align: middle">{{ stats["date"] }}</td>
                <td style="vertical-align: middle">{{ stats["access_received_total"] }}</td>
                <td style="vertical-align: middle">{{ stats["access_inserted_total"] }}</td>
                <td style="vertical-align: middle">{{ stats["read_success_total"] }}</td>
                <td style="vertical-align: middle">{{ stats["read_false_total"] }}</td>
                <td style="vertical-align: middle">{{ stats["download_success_total"] }}</td>
                <td style="vertical-align: middle">{{ stats["download_false_total"] }}</td>
                <td style="vertical-align: middle">{{ stats["write_success_total"] }}</td>
                <td style="vertical-align: middle">{{ stats["write_false_total"] }}</td>
                <td style="vertical-align: middle">{{ stats["publish_received_total"] }}</td>
                <td style="vertical-align: middle">{{ stats["publish_send_total"] }}</td>
                </tr>
            {% end %}
        {% else %}
            <tr>
                <td colspan="11" style="vertical-align: middle">暫沒有對接統計信息</td>
            </tr>
        {% end %}

        </table>

<!--    查詢的時候不顯示分頁內容-->
    {% if total_count is not None or total_page is not None %}
        <div>
            <nav aria-label="Page navigation">
                <ul class="pagination">
                    {% if current_page == 1 %}
                        <li class="disabled">
                    {% end %}

                    {% if current_page != 1 %}
                        <li>
                    {% end %}
                            <a href="/illegal/stats?current_page={{current_page - 1}}" aria-label="Previous">
                                <span aria-hidden="false">&laquo;</span>
                            </a>
                        </li>

                    {% for page in range(1, total_page + 1) %}
                        {% if current_page == page %}
                            <li class="active"><a href="/illegal/stats?current_page={{ page }}">{{ page }}</a></li>

                        {% end %}
                        {% if current_page != page %}
                            <li><a href="/illegal/stats?current_page={{ page }}">{{ page }}</a></li>

                        {% end %}

                    {% end %}

                    {% if current_page == total_page or current_page == 1 %}
                        <li class="disabled">
                    {% end %}

                    {% if current_page != total_page and current_page != 1 %}
                        <li>
                    {% end %}
                        <a href="/illegal/stats?current_page={{current_page + 1}}" aria-label="Next">
                            <span aria-hidden="true">&raquo;</span>
                        </a>
                    </li>
                    <span style="font-size: 25px;margin-left: 5px;">
                            共{{ total_count }}條記錄,共{{ total_page }}頁
                    </span>
                </ul>
            </nav>
        </div>
    {% end %}
</div>
</body>
</html>

需要注意的地方

  1. 模板渲染的時候, 需要使用 static_url() 函數獲取靜態資源( statics ) 的路徑, 不然模板會找不到 css, js 文件, 不能正常加載頁面.

    <link href="{{static_url('css/bootstrap.min.css')}}" rel="stylesheet">

    <script src="{{static_url('js/jquery-2.1.0.min.js')}}"></script>

    <script src="{{static_url('js/bootstrap.min.js')}}"></script>


  1. 給當前頁添加激活狀態


項目改進

xxx


最后給出web項目地址: https://github.com/kaichenkai/TornadoWebApp


ending ~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM