├── .gitignore ├── README.md ├── img └── flask-developer-roadmap.png ├── template-flask-blueprint-with-factory └── flask │ ├── app │ ├── __init__.py │ ├── config │ │ └── config.py │ └── view │ │ └── auth.py │ ├── main.py │ └── requirements.txt ├── template-flask-factory-Application └── flask │ ├── app │ ├── __init__.py │ └── config │ │ └── config.py │ ├── main.py │ └── requirements.txt ├── template-flask-i18n └── flask │ ├── app │ ├── __init__.py │ ├── babel.cfg │ ├── config │ │ └── config.py │ ├── templates │ │ └── index.html │ ├── translations │ │ └── zh │ │ │ └── LC_MESSAGES │ │ │ └── messages.po │ └── view │ │ └── index.py │ ├── main.py │ └── requirements.txt ├── template-flask-jwt ├── app │ ├── __init__.py │ ├── config │ │ └── config.py │ ├── model │ │ └── users.py │ ├── templates │ │ └── index.html │ └── view │ │ └── v1 │ │ ├── __init__.py │ │ └── auth.py ├── main.py └── requirements.txt ├── template-flask-login ├── app │ ├── __init__.py │ ├── config │ │ ├── config.py │ │ └── test.db │ ├── model │ │ └── user.py │ ├── templates │ │ ├── login.html │ │ └── signup.html │ └── view │ │ ├── abort_msg.py │ │ └── auth.py ├── main.py └── requirements.txt ├── template-flask-sqlalchemy ├── hash_tag.py ├── hashtag.db ├── main.py ├── n1.db ├── n1_queries.py └── requirements.txt ├── template1-dockerfile-flask ├── Dockerfile ├── README.md ├── main.py └── requirements.txt ├── template2-dockerfile-nginx ├── Dockerfile ├── README.md ├── index.html ├── nginx.conf ├── ssl.conf ├── ssl.csr └── ssl.key └── template3-docker-compose-flask-nginx-postgres ├── docker-compose.yml ├── flask ├── Dockerfile ├── app.ini ├── app │ ├── __init__.py │ ├── config │ │ └── config.py │ └── models │ │ └── users.py ├── main.py └── requirements.txt └── nginx ├── Dockerfile ├── nginx.conf ├── ssl.csr └── ssl.key /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | postgres-data/ 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | .vscode 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # IPython 83 | profile_default/ 84 | ipython_config.py 85 | 86 | # pyenv 87 | .python-version 88 | 89 | # pipenv 90 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 91 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 92 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 93 | # install all needed dependencies. 94 | #Pipfile.lock 95 | 96 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 97 | __pypackages__/ 98 | 99 | # Celery stuff 100 | celerybeat-schedule 101 | celerybeat.pid 102 | 103 | # SageMath parsed files 104 | *.sage.py 105 | 106 | # Environments 107 | .flaskenv 108 | .env 109 | .venv 110 | env/ 111 | venv/ 112 | ENV/ 113 | env.bak/ 114 | venv.bak/ 115 | 116 | # Spyder project settings 117 | .spyderproject 118 | .spyproject 119 | 120 | # Rope project settings 121 | .ropeproject 122 | 123 | # mkdocs documentation 124 | /site 125 | 126 | # mypy 127 | .mypy_cache/ 128 | .dmypy.json 129 | dmypy.json 130 | 131 | # Pyre type checker 132 | .pyre/ 133 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Why Flask? 2 | 3 | Python 的 web 框架很多,推薦選擇 Flask 開始入門,因為 Flask 是一種極輕量化的框架,設計理念是 Micro,非常適合新手快速上手,簡單來說在架設 Flask 時就像是堆積木,可以自己決定要使用什麼積木 (擴充套件),不會有多餘的積木,達到簡單、輕量、高擴充性的架構。 4 | 5 | >The “micro” in microframework means Flask aims to keep the core simple but extensible. Flask won’t make many decisions for you, such as what database to use. Those decisions that it does make, such as what templating engine to use, are easy to change. Everything else is up to you, so that Flask can be everything you need and nothing you don’t. 6 | [Flask document #what-does-micro-mean](https://flask.palletsprojects.com/en/1.1.x/foreword/#what-does-micro-mean) 7 | 8 | 9 | 也因為 Flask 的彈性度很高,所以製作了一份 Flask 學習路線圖教學,從基礎的環境建置教學、到學習 ORM 資料庫操作、以及驗證和部署,是一份學習 Flask 的入門地圖,讓大家可以輕鬆學習 Flask,而本篇內容會持續更新,歡迎底下留言交流! 10 | 11 | 12 | 13 | # 一. Flask 入門篇 14 | 15 | 介紹當初入門學習 Flask 時忽略的幾個重點,此篇從 Flask 設計理念、Flask 運行的三種方法、到運行後終端機顯示的小細節,建議不管是 Flask 初心者或老司機都可以閱讀一下,說不定會有意外的發現。 16 | * [【Hello word】實作一個簡單的 Flask 入門](https://www.maxlist.xyz/2020/04/30/flask-helloworld/) 17 | 18 | *Nice To Have:* \ 19 | 當 Flask 架構越來越龐大,專案架構該怎麼切?如何避免遇到 Circular Imports 問題?以下第一篇介紹了 Flask 常見的兩種專案架構結構、Flask Blueprints 用法;而第二篇則是利用工廠模式來快速切換環境配置 。兩篇都是作者在官方文件中推薦的 Flask 實作方法,建議可以加入自己的 Flask 架構中: 20 | * [實作 Flask Blueprints 和淺談 Circular Imports](https://www.maxlist.xyz/2020/07/30/flask-blueprint/) 21 | * [實作 Flask Application Factories 工廠模式](https://www.maxlist.xyz/2020/08/06/flask-application-factories/) 22 | 23 | # 二. Flask 資料庫串接篇 24 | 25 | 資料庫操作上,我們使用 Flask-SQLAlchemy 套件,為什麼選擇 Flask-SQLAlchemy? 26 | 1. 可支援市面上常用的資料庫 sqlite、Mysql、PostgreSQL、MSSql、Oracle 27 | 2. 可以使用原生 SQL下指令,也同時支援 ORM 框架來操作資料庫,可以隨時切換很方便。 28 | 關於 Flask-SQLAlchemy 相關教學,可以參考: 29 | * [Flask-SQLAlchemy 資料庫連線&設定入門 (一)](https://www.maxlist.xyz/2019/11/10/flask-sqlalchemy-setting/) 30 | * [Flask-SQLAlchemy 參數設置(進階)](https://www.maxlist.xyz/2020/10/06/flask-sqlalchemy-parameter/) 31 | * [Flask-SQLAlchemy 資料庫操作-ORM篇 (二)](https://www.maxlist.xyz/2019/10/30/flask-sqlalchemy/) 32 | * [Flask-SQLAlchemy -ORM 一對多關聯篇 (三)](https://www.maxlist.xyz/2019/11/24/flask-sqlalchemy-orm/) 33 | * [Flask-SQLAlchemy -ORM 多對多關聯篇 (四)](https://www.maxlist.xyz/2019/11/24/flask-sqlalchemy-orm2/) 34 | * [Flask-SQLAlchemy 資料庫操作-SQL指令篇(五)](https://www.maxlist.xyz/2019/11/09/sqlalchemy-sql/) 35 | 36 | *Nice To Have:*\ 37 | 關於 raw sql 指令,也推薦以下此篇,將會使用 [子查詢 Subquery](https://www.maxlist.xyz/2020/06/05/postgresql-interview-questions/#%E4%B8%80_%E5%AD%90%E6%9F%A5%E8%A9%A2_Subquery) 、 [通用表達式 Common Table Expressions](https://www.maxlist.xyz/2020/06/05/postgresql-interview-questions/#%E4%BA%8C_%E9%80%9A%E7%94%A8%E8%A1%A8%E9%81%94%E5%BC%8F_Common_Table_Expressions) 、 [窗函式 Window Function](https://www.maxlist.xyz/2020/06/05/postgresql-interview-questions/#%E4%B8%89_%E7%AA%97%E5%87%BD%E5%BC%8F_Window_Function) ,來實戰電商的用戶留存、用戶活躍度 ( MAU/ WAU / DAU )、客戶分群 – RFM / NES 模型、分類貼標籤 … 等。 38 | * [PostgreSQL 基礎教學和練習題操作](https://www.maxlist.xyz/2020/06/05/postgresql-interview-questions/) 39 | 40 | 41 | # 三. Flask 使用者驗證 Http authentication 篇 42 | 43 | 當前端發請求 Http request 給後端時,該如何驗證使用者是否對此路徑 (route) 有權限能夠請求?實作了兩種方法,分別是使用 Session 的方式,和使用 JWT token 方式來驗證使用者請求! 44 | 45 | 1.Session-based Authentication 46 | * [Flask 實作 Cookie 操作和淺談](https://www.maxlist.xyz/2019/05/11/flask-cookie/) 47 | * [Flask 實作 Session 操作和淺談](https://www.maxlist.xyz/2019/06/29/flask-session/) 48 | * [Flask 實作 Session-base login 登入驗證](https://www.maxlist.xyz/2020/05/24/flask-session-base-login/) 49 | 50 | 2.Token-based Authentication 51 | * [Flask-JWT-Extended 實作](https://www.maxlist.xyz/2020/05/01/flask-jwt-extended/) 52 | 53 | 驗證完使用者請求後,Http request 的來源又是否安全呢?以下兩篇從同源政策開始解說,到使用 flask-cors 套件來設定 CORS 允許非同源的請求,最後介紹實作 CSRF token 和 Cookie Samesite 設定來避免受到 CSRF 攻擊,確保非同源的請求是安全的! 54 | * [Flask 實作 CORS 和淺談](https://www.maxlist.xyz/2020/05/08/flask-cors/) 55 | * [Flask 實作 CSRF Protection 和淺談](https://www.maxlist.xyz/2020/05/07/flask-csrf/) 56 | 57 | # 四. Flask 部署篇 58 | Flask run 之後,發生了什麼事?Flask 自帶的 Web Server 只適合測試環境使用,那正式環境怎麼辦?來來來,先了解一下什麼 WSGI、uWSGI 和 Nginx,他們與 Flask 之間的愛恨情仇到底是什麼! 59 | * [Flask 為甚麼需要 WSGI 與 Nginx](https://www.maxlist.xyz/2020/05/06/flask-wsgi-nginx/) 60 | 61 | 想更了解 Nginx 和 uWSGI 還有什麼進階配置嗎?那就快來看看以下兩篇配置教學文章 62 | * [淺談 Nginx 基本配置、負載均衡、緩存和反向代理](https://www.maxlist.xyz/2020/06/18/flask-nginx/) 63 | * [淺談 uWSGI 配置參數講解](https://www.maxlist.xyz/2020/06/20/flask-uwsgi/) 64 | 65 | 了解 WSGI、uWSGI 和 Nginx 關係之後直接實際在 GCP 上部署一個 Flask + Nginx +uWSGI Server 吧! 66 | * [實作 GCP 部署 Flask + Nginx + uWSGI](https://www.maxlist.xyz/2020/06/17/flask-nginx-uwsgi-on-gcp/) 67 | # 五. Flask 遇上 Docker 篇 68 | 每次部署 Flask Server 環境設定都好麻煩?Flask 遇上 Docker 系列會實作如何使用 Docker 部署 Flask,並且都有附上完整程式碼在 GitHub 上唷! 69 | * [第一集:實作 Dockerfile + lask 教學 (附GitHub完整程式)](https://www.maxlist.xyz/2020/01/11/docker-flask/) 70 | * [第二集:實作 Dockerfile + Nginx + SSL + Flask 教學 (附GitHub完整程式)](https://www.maxlist.xyz/2020/01/19/docker-nginx/) 71 | * [第三集:實作 Docker-compose (Flask+Nginx+PostgreSQL)](https://www.maxlist.xyz/2020/06/14/flask-docker-compose/) 72 | # 六. Flask Cache 篇 73 | 74 | 部署後,在網頁瀏覽時載入好慢?試試利用快取來優化吧,本篇實作 Server Side 和 Client Side 的 Cache 機制: 75 | * [Flask 實作 Cache + Redis & Nginx Cache 配置](https://www.maxlist.xyz/2020/08/24/flask-cache/) 76 | 77 | # 七. Flask Testing 測試篇 78 | 79 | 部署後 Server 總是出問題?快來試試單元測試吧!本篇除了實作 Flask 單元測試和程式的覆蓋率外,也介紹了單元測試的 F.I.R.S.T 原則,以及考量 Independent 時,會遵循 3A rule。 80 | * [實作 Flask 單元測試 Unit Testing](https://www.maxlist.xyz/2020/08/17/flask-unittest/) 81 | # 八. Flask CI / CD 篇 82 | 測試、部署好累?交給電腦處理吧!建立 CI / CD 的工具很多,像是 CircleCI、 TravisCI、 Jenkins、Drone CI,本篇要實作的是 GitHub 在 2019 年推出的 Action 來實作 Flask + Action 建置 CI / CD 83 | * [實作 Flask + GitHub Action CI/CD](https://www.maxlist.xyz/2020/07/29/flask-cicd-action/) 84 | # 九. 第三方 API 串接篇 85 | * [Python Flask 綠界金流 API 信用卡串接](https://www.maxlist.xyz/2020/02/14/python-ecpay/) 86 | # 十. 多國語系 87 | 開始實作前,請思考多國語系的「網址結構設計」要怎麼決定?「Hreflang」 和 「Canonical」的 SEO 標籤是什麼,要怎麼埋設?此外這篇還會分享一些國外在做多國語系上的小巧思案例分享: 88 | * [【SEO 優化篇】如何設計多語言係 Url 結構和 Hreflang SEO 優化](https://www.maxlist.xyz/2020/10/04/hreflang-seo-optimize/) 89 | 90 | 實作 Flask-Babel 來建置 i18n 的多國語系網站,並且有附 GitHub 完整的範例,歡迎大家 clone 使用: 91 | * [【Flask教學】實作 Flask i18n 多國語系](https://www.maxlist.xyz/2020/10/24/flask-i18n/) 92 | # 十一. 版本控制 93 | 94 | 雖然版本控制和 Flask 沒有特別關聯,但隨著專案架構越來越大、或需要與同事協作時、或是在建置 CI / CD 的 Repo 存放,版本控制都是很重要的一環,建議這塊技能可以越早點越好。 95 | * [Git 入門四步驟 – init & add & commit & push](https://www.maxlist.xyz/2020/05/10/git-tutorial/) 96 | * [Git 的平行時空 – 分支合併: merge 與 rebase 差異](https://www.maxlist.xyz/2020/05/02/git-merge-rebase/) 97 | * [Git 的時光機 – 回復版本: reset 與 checkout 差異](https://www.maxlist.xyz/2020/05/03/git-reset-checkout/) 98 | 99 | 100 | # 其他 Flask 相關學習資源 101 | 以下是我過去在學習 Flask 時,時常拜訪和受益留多的 Flask 教學網站,提供給大家參考: 102 | * 實體課程: 103 | * 臺灣大學資訊系統訓練班 – [Python Flask動態網站與聊天機器人實作班](https://train.csie.ntu.edu.tw/train/teacher.php?id=114) 104 | * 文章: 105 | * (繁體) shaoeChen 的 [Flask 實作系列教學](https://hackmd.io/@shaoeChen?tags=%5B%22flask%22%5D) 106 | * (簡體) [李輝](https://github.com/greyli) Flask 開發團隊 (Pallets Team)成員的 [Hello,Flask 專欄](https://zhuanlan.zhihu.com/flask) 107 | * (英文) Flask 的 [官方文件](https://flask.palletsprojects.com/en/1.1.x/) 108 | * 影片: 109 | * 沈弘哲 的 [Python Flask Tutorial](https://www.youtube.com/watch?v=o1KjeLUSCB8&list=PLteWjpkbvj7p1r1Tyj7437Sp4g-rt3YEd) 110 | * FlaskCon 111 | * 2020/07 第一屆 [FlaskCon](https://flaskcon.com/) 線上會議影片 112 | # 最後. Keep Learning 113 | 關於任何關於 Flask 問題都很歡迎私訊或留言,也歡迎加入 [Flask Taiwan 社團](https://www.facebook.com/groups/flasktaiwan/) ,或是加入 [Flask Taiwan Line 討論群](https://line.me/ti/g/QacB570RGt) (需使用手機點擊),我們會盡快回覆您 114 | -------------------------------------------------------------------------------- /img/flask-developer-roadmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hsuanchi/flask-template/f4becb01eed9574f7c6cfa53355c87af57db46bc/img/flask-developer-roadmap.png -------------------------------------------------------------------------------- /template-flask-blueprint-with-factory/flask/app/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask_sqlalchemy import SQLAlchemy 3 | from .config.config import config 4 | 5 | db = SQLAlchemy() 6 | 7 | 8 | def create_app(config_name): 9 | 10 | app = Flask(__name__) 11 | app.config.from_object(config[config_name]) 12 | 13 | register_extensions(app) 14 | register_blueprints(app) 15 | 16 | @app.route('/') 17 | def index(): 18 | return 'welcome' 19 | 20 | return app 21 | 22 | 23 | def register_extensions(app): 24 | """Register extensions with the Flask application.""" 25 | # flask_sqlalchemy 26 | db.init_app(app) 27 | 28 | 29 | def register_blueprints(app): 30 | """Register blueprints with the Flask application.""" 31 | from .view.auth import auth 32 | app.register_blueprint(auth, url_prefix='/auth') 33 | -------------------------------------------------------------------------------- /template-flask-blueprint-with-factory/flask/app/config/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | import datetime 3 | 4 | basedir = os.path.abspath(os.path.dirname(__file__)) 5 | 6 | 7 | def create_sqlite_uri(db_name): 8 | return "sqlite:///" + os.path.join(basedir, db_name) 9 | 10 | 11 | class BaseConfig: # 基本配置 12 | SECRET_KEY = 'THIS IS MAX' 13 | PERMANENT_SESSION_LIFETIME = datetime.timedelta(days=14) 14 | 15 | 16 | class DevelopmentConfig(BaseConfig): 17 | DEBUG = False 18 | SQLALCHEMY_TRACK_MODIFICATIONS = False 19 | SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://username:password@ip:3306/tablename' 20 | 21 | SQLALCHEMY_ENGINE_OPTIONS = { 22 | "pool_pre_ping": True, 23 | "pool_recycle": 3600, 24 | } 25 | 26 | 27 | class TestingConfig(BaseConfig): 28 | TESTING = True 29 | SQLALCHEMY_TRACK_MODIFICATIONS = False 30 | SQLALCHEMY_DATABASE_URI = create_sqlite_uri("test.db") 31 | WTF_CSRF_ENABLED = False 32 | 33 | 34 | config = { 35 | 'development': DevelopmentConfig, 36 | 'testing': TestingConfig, 37 | } 38 | -------------------------------------------------------------------------------- /template-flask-blueprint-with-factory/flask/app/view/auth.py: -------------------------------------------------------------------------------- 1 | from flask import jsonify, request, Blueprint, url_for, redirect, abort, make_response 2 | 3 | auth = Blueprint('autha', __name__) 4 | 5 | 6 | @auth.route('/') 7 | def index(): 8 | return 'welcome' -------------------------------------------------------------------------------- /template-flask-blueprint-with-factory/flask/main.py: -------------------------------------------------------------------------------- 1 | from app import create_app 2 | 3 | app = create_app('development') -------------------------------------------------------------------------------- /template-flask-blueprint-with-factory/flask/requirements.txt: -------------------------------------------------------------------------------- 1 | click==7.1.2 2 | Flask==1.1.2 3 | Flask-SQLAlchemy==2.4.4 4 | itsdangerous==1.1.0 5 | Jinja2==2.11.2 6 | MarkupSafe==1.1.1 7 | SQLAlchemy==1.3.18 8 | Werkzeug==1.0.1 9 | -------------------------------------------------------------------------------- /template-flask-factory-Application/flask/app/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask_sqlalchemy import SQLAlchemy 3 | from .config.config import config 4 | 5 | db = SQLAlchemy() 6 | 7 | 8 | def create_app(config_name): 9 | 10 | app = Flask(__name__) 11 | app.config.from_object(config[config_name]) 12 | 13 | db.init_app(app) 14 | 15 | @app.route('/') 16 | def index(): 17 | return 'welcome' 18 | 19 | return app 20 | -------------------------------------------------------------------------------- /template-flask-factory-Application/flask/app/config/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | import datetime 3 | 4 | basedir = os.path.abspath(os.path.dirname(__file__)) 5 | 6 | 7 | def create_sqlite_uri(db_name): 8 | return "sqlite:///" + os.path.join(basedir, db_name) 9 | 10 | 11 | class BaseConfig: # 基本配置 12 | SECRET_KEY = 'THIS IS MAX' 13 | PERMANENT_SESSION_LIFETIME = datetime.timedelta(days=14) 14 | 15 | 16 | class DevelopmentConfig(BaseConfig): 17 | DEBUG = False 18 | SQLALCHEMY_TRACK_MODIFICATIONS = False 19 | SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://username:password@ip:3306/tablename' 20 | 21 | SQLALCHEMY_ENGINE_OPTIONS = { 22 | "pool_pre_ping": True, 23 | "pool_recycle": 3600, 24 | } 25 | 26 | 27 | class TestingConfig(BaseConfig): 28 | TESTING = True 29 | SQLALCHEMY_TRACK_MODIFICATIONS = False 30 | SQLALCHEMY_DATABASE_URI = create_sqlite_uri("test.db") 31 | WTF_CSRF_ENABLED = False 32 | 33 | 34 | config = { 35 | 'development': DevelopmentConfig, 36 | 'testing': TestingConfig, 37 | } 38 | -------------------------------------------------------------------------------- /template-flask-factory-Application/flask/main.py: -------------------------------------------------------------------------------- 1 | from app import create_app 2 | 3 | app = create_app('development') -------------------------------------------------------------------------------- /template-flask-factory-Application/flask/requirements.txt: -------------------------------------------------------------------------------- 1 | click==7.1.2 2 | Flask==1.1.2 3 | Flask-SQLAlchemy==2.4.4 4 | itsdangerous==1.1.0 5 | Jinja2==2.11.2 6 | MarkupSafe==1.1.1 7 | SQLAlchemy==1.3.18 8 | Werkzeug==1.0.1 9 | -------------------------------------------------------------------------------- /template-flask-i18n/flask/app/__init__.py: -------------------------------------------------------------------------------- 1 | # 引用 flask 內建函式 2 | from flask import Flask, g, request, render_template, abort 3 | 4 | # 引用套件 5 | from flask_sqlalchemy import SQLAlchemy 6 | from flask_babel import Babel 7 | 8 | from .config.config import config 9 | 10 | db = SQLAlchemy() 11 | babel = Babel() 12 | 13 | 14 | def create_app(config_name): 15 | 16 | app = Flask(__name__) 17 | app.config.from_object(config[config_name]) 18 | 19 | register_blueprints(app) 20 | register_extensions(app) 21 | register_i18n(app) 22 | 23 | return app 24 | 25 | 26 | def register_blueprints(app): 27 | """Register blueprints with the Flask application.""" 28 | from .view.index import main 29 | app.register_blueprint(main, url_prefix='//main') 30 | 31 | 32 | def register_extensions(app): 33 | """Register extensions with the Flask application.""" 34 | babel.init_app(app) 35 | db.init_app(app) 36 | 37 | 38 | def register_i18n(app): 39 | """Register i18n with the Flask application.""" 40 | defalut_language_str = app.config['DEFAULT_LANGUAGE'] 41 | support_language_list = app.config['SUPPORTED_LANGUAGES'] 42 | 43 | # 1 Get parameter lang_code from route 44 | @app.url_value_preprocessor 45 | def get_lang_code(endpoint, values): 46 | if values is not None: 47 | g.lang_code = values.pop('lang_code', defalut_language_str) 48 | 49 | # 2 Check lang_code type is in config 50 | @app.before_request 51 | def ensure_lang_support(): 52 | lang_code = g.get('lang_code', None) 53 | if lang_code and lang_code not in support_language_list: 54 | g.lang_code = request.accept_languages.best_match( 55 | support_language_list) 56 | 57 | # 3 Setting babel 58 | @babel.localeselector 59 | def get_locale(): 60 | return g.get('lang_code') 61 | 62 | # 4 Check lang_code exist after step1 pop parameter of lang_code 63 | @app.url_defaults 64 | def set_language_code(endpoint, values): 65 | if 'lang_code' in values or not g.lang_code: 66 | return 67 | if app.url_map.is_endpoint_expecting(endpoint, 'lang_code'): 68 | values['lang_code'] = g.lang_code -------------------------------------------------------------------------------- /template-flask-i18n/flask/app/babel.cfg: -------------------------------------------------------------------------------- 1 | [python: **.py] 2 | [jinja2: **/templates/**.html] 3 | extensions=jinja2.ext.autoescape,jinja2.ext.with_ -------------------------------------------------------------------------------- /template-flask-i18n/flask/app/config/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | import datetime 3 | 4 | basedir = os.path.abspath(os.path.dirname(__file__)) 5 | 6 | 7 | def create_sqlite_uri(db_name): 8 | return "sqlite:///" + os.path.join(basedir, db_name) 9 | 10 | 11 | class BaseConfig: # 基本配置 12 | SECRET_KEY = 'THIS IS MAX' 13 | PERMANENT_SESSION_LIFETIME = datetime.timedelta(days=14) 14 | 15 | BABEL_TRANSLATION_DIRECTORIES = 'translations' 16 | SUPPORTED_LANGUAGES = ['zh', 'en'] 17 | DEFAULT_LANGUAGE = 'zh' 18 | 19 | 20 | class DevelopmentConfig(BaseConfig): 21 | DEBUG = False 22 | SQLALCHEMY_TRACK_MODIFICATIONS = False 23 | SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://username:password@ip:3306/tablename' 24 | 25 | SQLALCHEMY_ENGINE_OPTIONS = { 26 | "pool_pre_ping": True, 27 | "pool_recycle": 3600, 28 | } 29 | 30 | 31 | class TestingConfig(BaseConfig): 32 | TESTING = True 33 | SQLALCHEMY_TRACK_MODIFICATIONS = False 34 | SQLALCHEMY_DATABASE_URI = create_sqlite_uri("test.db") 35 | WTF_CSRF_ENABLED = False 36 | 37 | 38 | config = { 39 | 'development': DevelopmentConfig, 40 | 'testing': TestingConfig, 41 | } 42 | -------------------------------------------------------------------------------- /template-flask-i18n/flask/app/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 |
{{ _('Hello World!') }}
12 |
{{ _('My name is Max') }}
13 | 14 | 15 | -------------------------------------------------------------------------------- /template-flask-i18n/flask/app/translations/zh/LC_MESSAGES/messages.po: -------------------------------------------------------------------------------- 1 | # Chinese translations for PROJECT. 2 | # Copyright (C) 2020 ORGANIZATION 3 | # This file is distributed under the same license as the PROJECT project. 4 | # FIRST AUTHOR , 2020. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: PROJECT VERSION\n" 9 | "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" 10 | "POT-Creation-Date: 2020-09-16 16:35+0800\n" 11 | "PO-Revision-Date: 2020-09-16 16:35+0800\n" 12 | "Last-Translator: FULL NAME \n" 13 | "Language: zh\n" 14 | "Language-Team: zh \n" 15 | "Plural-Forms: nplurals=1; plural=0\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=utf-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Generated-By: Babel 2.8.0\n" 20 | 21 | #: templates/index.html:11 22 | msgid "Hello World!" 23 | msgstr "你好" 24 | 25 | #: templates/index.html:12 26 | msgid "My name is Max" 27 | msgstr "馬克思" 28 | 29 | -------------------------------------------------------------------------------- /template-flask-i18n/flask/app/view/index.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, render_template, jsonify, request, session, redirect, url_for, Blueprint 2 | 3 | main = Blueprint('main', __name__) 4 | 5 | 6 | @main.route('/') 7 | def do_some_thing(): 8 | return render_template('index.html') -------------------------------------------------------------------------------- /template-flask-i18n/flask/main.py: -------------------------------------------------------------------------------- 1 | from app import create_app 2 | 3 | app = create_app('development') 4 | -------------------------------------------------------------------------------- /template-flask-i18n/flask/requirements.txt: -------------------------------------------------------------------------------- 1 | click==7.1.2 2 | Flask==1.1.2 3 | Flask-SQLAlchemy==2.4.4 4 | itsdangerous==1.1.0 5 | Jinja2==2.11.2 6 | MarkupSafe==1.1.1 7 | SQLAlchemy==1.3.18 8 | Werkzeug==1.0.1 9 | -------------------------------------------------------------------------------- /template-flask-jwt/app/__init__.py: -------------------------------------------------------------------------------- 1 | # 引用 Flask 套件 2 | from flask import Flask, abort, render_template, request, jsonify, session, Blueprint 3 | 4 | # 引用 SQL 相關模組 5 | from flask_sqlalchemy import SQLAlchemy 6 | 7 | # 引用 JWT 相關模組 8 | from flask_jwt_extended import (JWTManager, jwt_required, create_access_token, 9 | jwt_refresh_token_required, 10 | create_refresh_token, get_jwt_identity, 11 | fresh_jwt_required) 12 | 13 | # 引用其他相關模組 14 | from .config.config import config 15 | 16 | db = SQLAlchemy() 17 | jwt = JWTManager() 18 | 19 | 20 | def create_app(config_name): 21 | app = Flask(__name__) 22 | 23 | # 設定 config 24 | app.config.from_object(config[config_name]) 25 | 26 | register_extensions(app) 27 | register_blueprints(app) 28 | 29 | @app.route('/') 30 | def index(): 31 | return render_template('index.html') 32 | 33 | return app 34 | 35 | 36 | def register_extensions(app): 37 | """Register extensions with the Flask application.""" 38 | db.init_app(app) 39 | jwt.init_app(app) 40 | 41 | 42 | def register_blueprints(app): 43 | """Register blueprints with the Flask application.""" 44 | 45 | from .view.v1 import api 46 | app.register_blueprint(api, url_prefix='/v1') 47 | -------------------------------------------------------------------------------- /template-flask-jwt/app/config/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | from datetime import timedelta 3 | import datetime 4 | 5 | basedir = os.path.abspath(os.path.dirname(__file__)) 6 | 7 | 8 | def create_sqlite_uri(db_name): 9 | return "sqlite:///" + os.path.join(basedir, db_name) 10 | 11 | 12 | class BaseConfig: # 基本配置 13 | SECRET_KEY = os.environ.get('key') 14 | PERMANENT_SESSION_LIFETIME = timedelta(days=14) 15 | 16 | JWT_ACCESS_TOKEN_EXPIRES = datetime.timedelta(minutes=1) 17 | JWT_REFRESH_TOKEN_EXPIRES = datetime.timedelta(days=30) 18 | JWT_TOKEN_LOCATION = ['cookies'] 19 | 20 | # Set True When Production Env 21 | JWT_COOKIE_SECURE = False 22 | 23 | class DevelopmentConfig(BaseConfig): 24 | DEBUG = False 25 | SQLALCHEMY_TRACK_MODIFICATIONS = False 26 | SQLALCHEMY_DATABASE_URI = os.environ.get('db') 27 | SQLALCHEMY_ENGINE_OPTIONS = { 28 | "pool_pre_ping": True, 29 | "pool_recycle": 3600, 30 | } 31 | # ?ssl_key=config/client-key.pem&ssl_cert=config/client-cert.pem" 32 | 33 | 34 | class TestingConfig(BaseConfig): 35 | TESTING = True 36 | SQLALCHEMY_TRACK_MODIFICATIONS = False 37 | SQLALCHEMY_DATABASE_URI = create_sqlite_uri("test.db") 38 | WTF_CSRF_ENABLED = False 39 | 40 | 41 | config = { 42 | 'development': DevelopmentConfig, 43 | 'testing': TestingConfig, 44 | 'default': DevelopmentConfig 45 | } 46 | -------------------------------------------------------------------------------- /template-flask-jwt/app/model/users.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from werkzeug.security import generate_password_hash, check_password_hash 4 | 5 | from marshmallow import Schema, fields, pre_load, validate 6 | from marshmallow import ValidationError 7 | 8 | from flask import session 9 | 10 | from .. import db 11 | 12 | 13 | class User(db.Model): 14 | __tablename__ = 'user' 15 | uid = db.Column(db.Integer, primary_key=True) 16 | name = db.Column(db.String(30), unique=True) 17 | password_hash = db.Column(db.String(255)) 18 | role = db.Column(db.String(10)) 19 | insert_time = db.Column(db.DateTime, default=datetime.now) 20 | update_time = db.Column(db.DateTime, 21 | onupdate=datetime.now, 22 | default=datetime.now) 23 | 24 | def __init__(self, name, passowrd, role='normal'): 25 | self.name = name 26 | self.password = passowrd 27 | self.role = role 28 | 29 | @property 30 | def password(self): 31 | raise AttributeError('passowrd is not readabilty attribute') 32 | 33 | @password.setter 34 | def password(self, password): 35 | self.password_hash = generate_password_hash(password) 36 | 37 | def verify_password(self, password): 38 | return check_password_hash(self.password_hash, password) 39 | 40 | @classmethod 41 | def get_user(cls, name): 42 | return cls.query.filter_by(name=name).first() 43 | 44 | def save_to_db(self): 45 | db.session.add(self) 46 | db.session.commit() 47 | 48 | 49 | class UserSchema(Schema): 50 | uid = fields.Integer(dump_only=True) 51 | name = fields.String(required=True) 52 | password = fields.String(required=True, validate=validate.Length(6)) 53 | role = fields.String() 54 | insert_time = fields.DateTime() 55 | update_time = fields.DateTime() 56 | -------------------------------------------------------------------------------- /template-flask-jwt/app/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 |
12 |
13 | 14 | 15 | 16 | 17 | 18 |
19 |
20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /template-flask-jwt/app/view/v1/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint 2 | 3 | api = Blueprint('api', __name__) 4 | 5 | from . import auth 6 | -------------------------------------------------------------------------------- /template-flask-jwt/app/view/v1/auth.py: -------------------------------------------------------------------------------- 1 | from flask import request,jsonify 2 | from flask_restful import Api, Resource, reqparse 3 | 4 | from marshmallow import ValidationError 5 | 6 | from flask_jwt_extended import (create_access_token, 7 | jwt_refresh_token_required, 8 | create_refresh_token, get_jwt_identity, 9 | fresh_jwt_required,set_refresh_cookies) 10 | 11 | from ...model import users 12 | from ... import db 13 | from . import api 14 | 15 | api = Api(api) 16 | users_schema = users.UserSchema() 17 | 18 | 19 | class Signup(Resource): 20 | def post(self): 21 | try: 22 | # 資料驗證 23 | user_data = users_schema.load(request.json, partial=True) 24 | name = user_data['name'] 25 | password = user_data['password'] 26 | 27 | # 註冊帳戶 28 | U = users.User(name, password) 29 | U.save_to_db() 30 | return create_jwt(name), 200 31 | 32 | except ValidationError as error: 33 | return {'errors': '資料驗證失敗', 'msg': str(error)}, 400 34 | 35 | except Exception as e: 36 | print(e) 37 | return {'msg': '重複註冊'}, 400 38 | 39 | 40 | class Login(Resource): 41 | def post(self): 42 | try: 43 | # 資料驗證 44 | user_data = users_schema.load(request.form) 45 | name = user_data['name'] 46 | password = user_data['password'] 47 | 48 | # 登入 49 | query = users.User.get_user(name=name) 50 | if query != None and query.verify_password(password): 51 | return create_jwt(name) 52 | else: 53 | return {'msg': '帳密錯誤'}, 400 54 | 55 | except ValidationError as error: 56 | return {'errors': '資料驗證失敗', 'msg': str(error)}, 400 57 | 58 | 59 | class JWT_refresh(Resource): 60 | @jwt_refresh_token_required 61 | def post(self): 62 | current_user = get_jwt_identity() 63 | new_token = create_access_token(identity=current_user) 64 | return {'access_token': new_token} 65 | 66 | 67 | def create_jwt(name): 68 | refresh_token = create_refresh_token(identity=name) 69 | access_token = create_access_token(identity=name) 70 | 71 | # recommend frontend save access_token in memery 72 | response = jsonify({ 73 | 'msg': 'ok', 74 | 'access_token': access_token, 75 | }) 76 | # Set refresh_token in cookie & remember use httponly 77 | set_refresh_cookies(response,refresh_token) 78 | return response 79 | 80 | 81 | api.add_resource(Signup, '/signup') 82 | api.add_resource(Login, '/login') 83 | api.add_resource(JWT_refresh, '/refresh') 84 | -------------------------------------------------------------------------------- /template-flask-jwt/main.py: -------------------------------------------------------------------------------- 1 | import os 2 | from dotenv import load_dotenv 3 | 4 | # load .env 5 | dotenv_path = os.path.join(os.path.dirname(__file__), '.flaskenv') 6 | if os.path.exists(dotenv_path): 7 | load_dotenv(dotenv_path, override=True) 8 | 9 | from app import create_app 10 | app = create_app('development') 11 | 12 | if __name__ == "__main__": 13 | app.run(debug=True) 14 | -------------------------------------------------------------------------------- /template-flask-jwt/requirements.txt: -------------------------------------------------------------------------------- 1 | aniso8601==8.0.0 2 | Click==7.0 3 | Flask==1.1.1 4 | Flask-JWT-Extended==3.24.1 5 | Flask-RESTful==0.3.8 6 | Flask-SQLAlchemy==2.4.1 7 | itsdangerous==1.1.0 8 | Jinja2==2.11.1 9 | MarkupSafe==1.1.1 10 | marshmallow==3.9.1 11 | PyJWT==1.7.1 12 | PyMySQL==0.9.3 13 | python-dotenv==0.13.0 14 | pytz==2020.1 15 | six==1.14.0 16 | SQLAlchemy==1.3.17 17 | Werkzeug==1.0.0 18 | -------------------------------------------------------------------------------- /template-flask-login/app/__init__.py: -------------------------------------------------------------------------------- 1 | # 引用 Flask 套件 2 | from flask import Flask, abort, render_template, request, jsonify, session, Blueprint 3 | 4 | # 引用 SQL 相關模組 5 | from flask_sqlalchemy import SQLAlchemy 6 | 7 | # 引用其他相關模組 8 | from .config.config import config 9 | 10 | db = SQLAlchemy() 11 | 12 | 13 | def create_app(config_name): 14 | app = Flask(__name__) 15 | 16 | # 設定 config 17 | app.config.from_object(config[config_name]) 18 | 19 | register_extensions(app) 20 | register_blueprints(app) 21 | 22 | @app.route('/') 23 | def index(): 24 | return 'success' 25 | 26 | @app.route('/create_all') 27 | def create_db(): 28 | db.create_all() 29 | return 'success' 30 | 31 | # 判斷權限需要 normal 以上 32 | @app.route('/normal_member') 33 | @check_login('normal') 34 | def member_normal_page(): 35 | name = session.get('username') 36 | role = session.get('role') 37 | uid = session.get('uid') 38 | return f'type:nornal,{name},{role},{uid}' 39 | 40 | # 判斷權限需要 admin 以上 41 | @app.route('/admin_member') 42 | @check_login('admin') 43 | def member_admin_page(): 44 | name = session.get('username') 45 | role = session.get('role') 46 | uid = session.get('uid') 47 | return f'type:admin,{name},{role},{uid}' 48 | 49 | return app 50 | 51 | 52 | def register_extensions(app): 53 | """Register extensions with the Flask application.""" 54 | db.init_app(app) 55 | 56 | 57 | def register_blueprints(app): 58 | """Register blueprints with the Flask application.""" 59 | from .view.auth import auth 60 | app.register_blueprint(auth, url_prefix='/auth') 61 | 62 | 63 | def check_login(check_role): 64 | def decorator(func): 65 | def wrap(*args, **kw): 66 | user_role = session.get('role') 67 | print(user_role) 68 | print(type(user_role)) 69 | 70 | if user_role == None or user_role == '': 71 | return abort(401) 72 | else: 73 | if check_role == 'admin' and check_role == user_role: 74 | return func(*args, **kw) 75 | if check_role == 'normal': 76 | return func(*args, **kw) 77 | else: 78 | return abort(401) 79 | 80 | wrap.__name__ = func.__name__ 81 | return wrap 82 | 83 | return decorator 84 | -------------------------------------------------------------------------------- /template-flask-login/app/config/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | from datetime import timedelta 3 | 4 | basedir = os.path.abspath(os.path.dirname(__file__)) 5 | 6 | 7 | def create_sqlite_uri(db_name): 8 | return "sqlite:///" + os.path.join(basedir, db_name) 9 | 10 | 11 | class BaseConfig: # 基本配置 12 | SECRET_KEY = os.environ.get('key') 13 | PERMANENT_SESSION_LIFETIME = timedelta(days=14) 14 | 15 | 16 | class DevelopmentConfig(BaseConfig): 17 | DEBUG = False 18 | SQLALCHEMY_TRACK_MODIFICATIONS = False 19 | SQLALCHEMY_DATABASE_URI = os.environ.get('db') 20 | SQLALCHEMY_ENGINE_OPTIONS = { 21 | "pool_pre_ping": True, 22 | "pool_recycle": 3600, 23 | } 24 | # ?ssl_key=config/client-key.pem&ssl_cert=config/client-cert.pem" 25 | 26 | 27 | class TestingConfig(BaseConfig): 28 | TESTING = True 29 | SQLALCHEMY_TRACK_MODIFICATIONS = False 30 | SQLALCHEMY_DATABASE_URI = create_sqlite_uri("test.db") 31 | WTF_CSRF_ENABLED = False 32 | 33 | 34 | config = { 35 | 'development': DevelopmentConfig, 36 | 'testing': TestingConfig, 37 | 'default': DevelopmentConfig 38 | } 39 | -------------------------------------------------------------------------------- /template-flask-login/app/config/test.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hsuanchi/flask-template/f4becb01eed9574f7c6cfa53355c87af57db46bc/template-flask-login/app/config/test.db -------------------------------------------------------------------------------- /template-flask-login/app/model/user.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from werkzeug.security import generate_password_hash, check_password_hash 3 | 4 | from marshmallow import Schema, fields, pre_load, validate 5 | from marshmallow import ValidationError 6 | 7 | from flask import session 8 | from .. import db 9 | 10 | 11 | class UserModel(db.Model): 12 | __tablename__ = 'user' 13 | uid = db.Column(db.Integer, primary_key=True) 14 | name = db.Column(db.String(30), unique=True) 15 | password_hash = db.Column(db.String(255)) 16 | role = db.Column(db.String(10), default='normal') 17 | insert_time = db.Column(db.DateTime, default=datetime.now) 18 | update_time = db.Column(db.DateTime, 19 | onupdate=datetime.now, 20 | default=datetime.now) 21 | 22 | def __init__(self, user_data): 23 | self.name = user_data['name'] 24 | self.password = user_data['password'] 25 | 26 | @property 27 | def password(self): 28 | raise AttributeError('passowrd is not readabilty attribute') 29 | 30 | @password.setter 31 | def password(self, password): 32 | self.password_hash = generate_password_hash(password) 33 | 34 | def verify_password(self, password): 35 | return check_password_hash(self.password_hash, password) 36 | 37 | @classmethod 38 | def get_user(cls, name): 39 | return cls.query.filter_by(name=name).first() 40 | 41 | def save_db(self): 42 | db.session.add(self) 43 | db.session.commit() 44 | 45 | def save_session(self): 46 | session['username'] = self.name 47 | session['role'] = self.role 48 | session['uid'] = self.uid 49 | 50 | @staticmethod 51 | def remove_session(): 52 | session['username'] = '' 53 | session['role'] = '' 54 | session['uid'] = '' 55 | 56 | 57 | class UserSchema(Schema): 58 | uid = fields.Integer(dump_only=True) 59 | name = fields.String(required=True, validate=validate.Length(3)) 60 | password = fields.String(required=True, validate=validate.Length(6)) 61 | role = fields.String() 62 | insert_time = fields.DateTime() 63 | update_time = fields.DateTime() -------------------------------------------------------------------------------- /template-flask-login/app/templates/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 |
12 |

13 |

14 |

15 |

16 | 17 | 18 | -------------------------------------------------------------------------------- /template-flask-login/app/templates/signup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 |
12 |

13 |

14 |

15 |

16 | 17 | 18 | -------------------------------------------------------------------------------- /template-flask-login/app/view/abort_msg.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import traceback 3 | 4 | 5 | def abort_msg(e): 6 | error_class = e.__class__.__name__ # 引發錯誤的 class 7 | detail = e.args[0] # 得到詳細的訊息 8 | cl, exc, tb = sys.exc_info() # 得到錯誤的完整資訊 Call Stack 9 | lastCallStack = traceback.extract_tb(tb)[-1] # 取得做後一行的錯誤訊息 10 | fileName = lastCallStack[0] # 錯誤的檔案位置名稱 11 | lineNum = lastCallStack[1] # 錯誤行數 12 | funcName = lastCallStack[2] # function 名稱 13 | 14 | # generate the error message 15 | errMsg = {error_class: [detail]} 16 | return errMsg 17 | -------------------------------------------------------------------------------- /template-flask-login/app/view/auth.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, render_template, request, session, Blueprint, make_response 2 | from flask_restful import Api, Resource, reqparse 3 | 4 | from marshmallow import ValidationError 5 | 6 | from ..model.user import UserModel, UserSchema 7 | from .. import db 8 | from .abort_msg import abort_msg 9 | 10 | auth = Blueprint('auth', __name__) 11 | api = Api(auth) 12 | 13 | users_schema = UserSchema() 14 | 15 | 16 | class Signup(Resource): 17 | def post(self): 18 | try: 19 | # 資料驗證 20 | user_data = users_schema.load(request.form, partial=True) 21 | # 註冊 22 | new_user = UserModel(user_data) 23 | new_user.save_db() 24 | new_user.save_session() 25 | return {'msg': 'registration success'}, 200 26 | 27 | except ValidationError as error: 28 | return {'errors': error.messages}, 400 29 | 30 | except Exception as e: 31 | return {'errors': abort_msg(e)}, 500 32 | 33 | def get(self): 34 | return make_response(render_template('signup.html')) 35 | 36 | 37 | class Login(Resource): 38 | def post(self): 39 | try: 40 | # 資料驗證 41 | user_data = users_schema.load(request.form) 42 | name = user_data['name'] 43 | password = user_data['password'] 44 | 45 | # 登入 46 | query = UserModel.get_user(name) 47 | if query != None and query.verify_password(password): 48 | query.save_session() 49 | return {'msg': 'ok'}, 200 50 | else: 51 | return {'errors': 'incorrect username or password'}, 400 52 | 53 | except ValidationError as error: 54 | return {'errors': error.messages}, 400 55 | 56 | except Exception as e: 57 | return {'errors': abort_msg(e)}, 500 58 | 59 | def get(self): 60 | return make_response(render_template('login.html')) 61 | 62 | 63 | class Logout(Resource): 64 | def get(self): 65 | UserModel.remove_session() 66 | return {'msg': 'logout'}, 200 67 | 68 | 69 | api.add_resource(Signup, '/signup') 70 | api.add_resource(Login, '/login') 71 | api.add_resource(Logout, '/logout') 72 | -------------------------------------------------------------------------------- /template-flask-login/main.py: -------------------------------------------------------------------------------- 1 | import os 2 | from dotenv import load_dotenv 3 | 4 | # load .env 5 | # dotenv_path = os.path.join(os.path.dirname(__file__), '.flaskenv') 6 | # if os.path.exists(dotenv_path): 7 | # load_dotenv(dotenv_path, override=True) 8 | 9 | from app import create_app 10 | app = create_app('testing') 11 | # app = create_app('development') 12 | 13 | if __name__ == "__main__": 14 | app.run(debug=True) 15 | -------------------------------------------------------------------------------- /template-flask-login/requirements.txt: -------------------------------------------------------------------------------- 1 | aniso8601==8.0.0 2 | Click==7.0 3 | Flask==1.1.1 4 | Flask-RESTful==0.3.8 5 | Flask-SQLAlchemy==2.4.1 6 | itsdangerous==1.1.0 7 | Jinja2==2.11.1 8 | MarkupSafe==1.1.1 9 | marshmallow==3.6.0 10 | PyMySQL==0.9.3 11 | python-dotenv==0.13.0 12 | pytz==2020.1 13 | six==1.14.0 14 | SQLAlchemy==1.3.17 15 | Werkzeug==1.0.0 16 | -------------------------------------------------------------------------------- /template-flask-sqlalchemy/hash_tag.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask_sqlalchemy import SQLAlchemy 3 | 4 | import os 5 | basedir = os.path.abspath(os.path.dirname(__file__)) 6 | 7 | #TODO: 第二篇增加欄位選項 8 | #TODO: 第二篇增加讀取全部選項 9 | 10 | app = Flask(__name__) 11 | 12 | app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False 13 | app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join( 14 | basedir, 'hashtag.db') 15 | db = SQLAlchemy(app) 16 | 17 | # 多 18 | relations = db.Table( 19 | 'relations', db.Column('tid', db.Integer, 20 | db.ForeignKey('hashtag_table.id')), 21 | db.Column('pid', db.Integer, db.ForeignKey('post_table.id'))) 22 | 23 | 24 | # 一 25 | class Hashtag(db.Model): 26 | __tablename__ = 'hashtag_table' 27 | id = db.Column(db.Integer, primary_key=True) 28 | tag = db.Column(db.String(20), nullable=False) 29 | tag_post_rel = db.relationship("Post", 30 | secondary=relations, 31 | backref="hashtag") 32 | 33 | # backref='post', # ref 可以讓我們使用 Post.tags 進行對 tags 操作 34 | # lazy='dynamic' # 有使用才載入,提昇效能 35 | 36 | 37 | # ㄧ 38 | class Post(db.Model): 39 | __tablename__ = 'post_table' 40 | id = db.Column(db.Integer, primary_key=True) 41 | title = db.Column(db.String(80), nullable=False) 42 | # tag_id = db.Column(db.Integer, db.ForeignKey('hashtag_table.id')) 43 | 44 | 45 | @app.route('/create_db') 46 | def index(): 47 | # db.create_all() 48 | 49 | # t1 = Hashtag(tag='Max') 50 | # t2 = Hashtag(tag='Ben') 51 | 52 | # db.session.add_all([t1, t2]) 53 | # db.session.commit() 54 | 55 | # p1 = Post(title='Post_1') 56 | # p2 = Post(title='Post_2') 57 | # p3 = Post(title='Post_3') 58 | 59 | # db.session.add_all([p1, p2, p3]) 60 | # db.session.commit() 61 | 62 | tag1 = Hashtag.query.filter_by(tag='Max').first() 63 | print(tag1.id) 64 | 65 | tags = Hashtag.query.all() 66 | print(tags) 67 | 68 | for tag in tags: 69 | print(tag.id) 70 | print(tag.tag) 71 | 72 | return 'ok' 73 | 74 | 75 | @app.route('/show') 76 | def show(): 77 | 78 | # u = Hashtag(tag='Max') 79 | # db.session.add(u) 80 | # db.session.commit() 81 | 82 | a = Hashtag(tag='Max') 83 | print(a.id) 84 | # # p = Post(title='0703', tag_id=1) 85 | # p = Post(title='070301', tag_id='') 86 | # # p = Pet('dog', u.id) 87 | # db.session.add(p) 88 | # db.session.commit() 89 | return 'ok' 90 | 91 | 92 | # @app.route('/one_to_many') 93 | # def db_relation(): 94 | # u = Person.query.filter_by(username='Max').first() 95 | # print('==========', u.pets) 96 | # print('==========', u.pets[0].petname) 97 | 98 | # p = Pet.query.filter_by(petname='dog').first() 99 | # print('---------', p.owner.username) 100 | # print('---------', p.owner_id) 101 | # return 'ok' 102 | 103 | if __name__ == "__main__": 104 | app.run(debug=True) 105 | -------------------------------------------------------------------------------- /template-flask-sqlalchemy/hashtag.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hsuanchi/flask-template/f4becb01eed9574f7c6cfa53355c87af57db46bc/template-flask-sqlalchemy/hashtag.db -------------------------------------------------------------------------------- /template-flask-sqlalchemy/main.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask_sqlalchemy import SQLAlchemy 3 | 4 | import os 5 | basedir = os.path.abspath(os.path.dirname(__file__)) 6 | 7 | app = Flask(__name__) 8 | 9 | app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False 10 | app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join( 11 | basedir, 'test.db') 12 | db = SQLAlchemy(app) 13 | 14 | # 一 15 | class Person(db.Model): 16 | __tablename__ = 'person' 17 | id = db.Column(db.Integer, primary_key=True) 18 | username = db.Column(db.String(80), nullable=False) 19 | pets = db.relationship('Pet', backref='owner') 20 | 21 | def __init__(self, username): 22 | self.username = username 23 | 24 | # 多 25 | class Pet(db.Model): 26 | __tablename__ = 'pet' 27 | id = db.Column(db.Integer, primary_key=True) 28 | petname = db.Column(db.String(80), nullable=False) 29 | owner_id = db.Column(db.Integer, db.ForeignKey('person.id')) 30 | 31 | def __init__(self, petname, owner_id): 32 | self.petname = petname 33 | self.owner_id = owner_id 34 | 35 | 36 | @app.route('/create_db') 37 | def index(): 38 | db.create_all() 39 | 40 | u = Person('Max') 41 | db.session.add(u) 42 | db.session.commit() 43 | 44 | p = Pet('dog', u.id) 45 | db.session.add(p) 46 | db.session.commit() 47 | 48 | return 'ok' 49 | 50 | 51 | @app.route('/one_to_many') 52 | def db_relation(): 53 | u = Person.query.filter_by(username='Max').first() 54 | print('==========', u.pets) 55 | print('==========', u.pets[0].petname) 56 | 57 | p = Pet.query.filter_by(petname='dog').first() 58 | print('---------', p.owner.username) 59 | print('---------', p.owner_id) 60 | return 'ok' 61 | 62 | 63 | if __name__ == "__main__": 64 | app.run(debug=True) 65 | -------------------------------------------------------------------------------- /template-flask-sqlalchemy/n1.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hsuanchi/flask-template/f4becb01eed9574f7c6cfa53355c87af57db46bc/template-flask-sqlalchemy/n1.db -------------------------------------------------------------------------------- /template-flask-sqlalchemy/n1_queries.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, jsonify 2 | from flask_sqlalchemy import SQLAlchemy 3 | from flask_sqlalchemy import get_debug_queries 4 | 5 | from sqlalchemy.orm import joinedload 6 | 7 | import os 8 | basedir = os.path.abspath(os.path.dirname(__file__)) 9 | 10 | app = Flask(__name__) 11 | 12 | app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False 13 | app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join( 14 | basedir, 'n1.db') 15 | 16 | app.config['SQLALCHEMY_RECORD_QUERIES'] = True 17 | 18 | # app.config['SQLALCHEMY_ECHO'] = True 19 | 20 | db = SQLAlchemy(app) 21 | 22 | relations = db.Table( 23 | 'relations', 24 | db.Column('tid', db.Integer, db.ForeignKey('hashtag_table.id'), 25 | index=True), 26 | db.Column('pid', db.Integer, db.ForeignKey('post_table.id'), index=True), 27 | db.Index("ix_tid_pid", "tid", "pid", unique=True)) 28 | 29 | 30 | class Hashtag(db.Model): 31 | __tablename__ = 'hashtag_table' 32 | id = db.Column(db.Integer, primary_key=True) 33 | tag = db.Column(db.String(20), nullable=False) 34 | 35 | 36 | class Post(db.Model): 37 | __tablename__ = 'post_table' 38 | id = db.Column(db.Integer, primary_key=True) 39 | title = db.Column(db.String(80), nullable=False) 40 | post_tag_rel = db.relationship("Hashtag", 41 | secondary=relations, 42 | lazy='joined', 43 | backref=db.backref("post", lazy='joined')) 44 | 45 | 46 | @app.after_request 47 | def after_request(reponse): 48 | for query in get_debug_queries(): 49 | query_time = query.duration 50 | query_param = query.parameters 51 | query_statement = query.statement 52 | print(f'------------------------------------------\ 53 | \n花費時間:{query_time},\ 54 | \n傳入參數:{query_param},\ 55 | \n查詢語法:{query_statement}\ 56 | \n------------------------------------------') 57 | return reponse 58 | 59 | 60 | @app.route('/create_db') 61 | def index(): 62 | db.create_all() 63 | 64 | t1 = Hashtag(tag='Max') 65 | t2 = Hashtag(tag='Ben') 66 | t3 = Hashtag(tag='Andy') 67 | 68 | db.session.add_all([t1, t2]) 69 | db.session.commit() 70 | 71 | p1 = Post(title='Post_1') 72 | p2 = Post(title='Post_2') 73 | p3 = Post(title='Post_3') 74 | 75 | p1.post_tag_rel = [t1, t2, t3] 76 | p2.post_tag_rel = [t1, t2] 77 | p3.post_tag_rel = [t1] 78 | 79 | db.session.add_all([p1, p2, p3]) 80 | db.session.commit() 81 | 82 | return 'ok' 83 | 84 | 85 | @app.route('/posts', methods=['GET']) 86 | def get_all_post(): 87 | 88 | post_datas = [] 89 | 90 | querys = Post.query.all() 91 | # querys = Post.query.options(joinedload("post_tag_rel")).all() 92 | 93 | for query in querys: 94 | post_dict = { 95 | 'id': query.id, 96 | 'title': query.title, 97 | 'tags': [tags.tag for tags in query.post_tag_rel] 98 | } 99 | post_datas.append(post_dict) 100 | 101 | return jsonify(post_datas), 200 102 | 103 | 104 | @app.route('/post/', methods=['GET']) 105 | def get_post_by_id(id): 106 | query = Post.query.filter_by(id=id).first() 107 | 108 | return jsonify({ 109 | 'id': query.id, 110 | 'title': query.title, 111 | 'tags': [tags.tag for tags in query.post_tag_rel] 112 | }) 113 | 114 | 115 | if __name__ == "__main__": 116 | app.run(debug=True) 117 | -------------------------------------------------------------------------------- /template-flask-sqlalchemy/requirements.txt: -------------------------------------------------------------------------------- 1 | click==7.1.2 2 | Flask==1.1.2 3 | Flask-SQLAlchemy==2.4.1 4 | itsdangerous==1.1.0 5 | Jinja2==2.11.2 6 | MarkupSafe==1.1.1 7 | SQLAlchemy==1.3.17 8 | Werkzeug==1.0.1 9 | -------------------------------------------------------------------------------- /template1-dockerfile-flask/Dockerfile: -------------------------------------------------------------------------------- 1 | # FROM:基底映像檔 2 | FROM python:3.7.2-stretch 3 | 4 | # WORKDI:建立 working directory 5 | WORKDIR /app 6 | 7 | # ADD:將檔案加到 images 內 8 | ADD . /app 9 | 10 | # 只有build 時使用,會執行此命令 11 | RUN pip install -r requirements.txt 12 | 13 | # run container 時要執行的命令 14 | CMD python main.py -------------------------------------------------------------------------------- /template1-dockerfile-flask/README.md: -------------------------------------------------------------------------------- 1 | ## template1 dockerfile+flask 2 | 3 | 詳細設定寫於: 4 | [實作 Dockerfile + flask 教學 (附GitHub完整程式) | Max行銷誌](https://www.maxlist.xyz/2020/01/11/docker-flask/) 5 | 6 | ### step 1 - git clone 7 | ``` 8 | git clone https://github.com/hsuanchi/template-docker-flask.git 9 | cd template1-dockerfile-flask 10 | ``` 11 | 12 | ### step 2 - 將 dockerfile 打包成 image 13 | 14 | ``` 15 | docker image build -t dockerfile_test . 16 | ``` 17 | 18 | ### step 3 - 透過 image 產生隔離的執行環境 container 19 | 20 | ``` 21 | docker run -p 80: 8888 dockerfile_test 22 | ``` 23 | 24 | 連線 0.0.0.0:80 或 127.0.0.1:80 ,就可以看到 Flask Dockerized 就表示成功囉! 25 | -------------------------------------------------------------------------------- /template1-dockerfile-flask/main.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | 3 | app = Flask(__name__) 4 | 5 | 6 | @app.route('/') 7 | def hello_world(): 8 | return 'Flask Dockerized' 9 | 10 | 11 | if __name__ == "__main__": 12 | app.run(debug=True, host='0.0.0.0', port=8888) 13 | -------------------------------------------------------------------------------- /template1-dockerfile-flask/requirements.txt: -------------------------------------------------------------------------------- 1 | Click==7.0 2 | Flask==1.1.1 3 | itsdangerous==1.1.0 4 | Jinja2==2.10.3 5 | MarkupSafe==1.1.1 6 | Werkzeug==0.16.0 7 | -------------------------------------------------------------------------------- /template2-dockerfile-nginx/Dockerfile: -------------------------------------------------------------------------------- 1 | # Use the Nginx image 2 | FROM nginx:stable 3 | 4 | COPY index.html /usr/share/nginx/html/ 5 | 6 | # Remove the default nginx.conf 7 | RUN rm /etc/nginx/conf.d/default.conf 8 | 9 | 10 | # Replace with our own nginx.conf 11 | # COPY ssl.conf /etc/nginx/conf.d/ssl.conf 12 | 13 | COPY nginx.conf /etc/nginx/conf.d/ 14 | 15 | COPY ssl.csr /etc/nginx/ssl.csr 16 | COPY ssl.key /etc/nginx/ssl.key 17 | 18 | EXPOSE 443 -------------------------------------------------------------------------------- /template2-dockerfile-nginx/README.md: -------------------------------------------------------------------------------- 1 | ## template1 dockerfile+nginx+ssl 2 | 3 | 詳細設定寫於: 4 | [實作 Dockerfile + nginx + ssl 教學 (附GitHub完整程式) | Max行銷誌](https://www.maxlist.xyz/2020/01/19/docker-nginx/) 5 | 6 | ### step 1 - git clone 7 | ``` 8 | git clone https://github.com/hsuanchi/template-docker-flask.git 9 | cd template2-dockerfile-nginx 10 | ``` 11 | 12 | ### step 2 - 將 dockerfile 打包成 image 13 | 14 | ``` 15 | docker image build -t docker_nginx_ssl . 16 | ``` 17 | 18 | ### step 3 - 透過 image 產生隔離的執行環境 container 19 | 20 | ``` 21 | docker run -p 80:80 -p 443:443 docker_nginx_ssl 22 | ``` 23 | 24 | 瀏覽 http://localhost/ 如果有看到 helloworld \ 25 | 瀏覽 https://localhost/ 會看到瀏覽器提示不安全 ( 因為是使用自簽憑證 ) 就代表成功囉! 26 | -------------------------------------------------------------------------------- /template2-dockerfile-nginx/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 |

Helloworld

13 | 14 | 15 | -------------------------------------------------------------------------------- /template2-dockerfile-nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name 127.0.0.1; 4 | 5 | location / { 6 | root /usr/share/nginx/html; 7 | index index.html index.htm; 8 | } 9 | } 10 | 11 | server { 12 | listen 443 ssl; 13 | 14 | server_name 127.0.0.1; 15 | 16 | # 憑證與金鑰的路徑 17 | ssl_certificate /etc/nginx/ssl.csr; 18 | ssl_certificate_key /etc/nginx/ssl.key; 19 | 20 | location / { 21 | root /usr/share/nginx/html; 22 | index index.html index.htm; 23 | } 24 | } 25 | 26 | 27 | 28 | # error_page 497 https://$host:$server_port$request_uri; 29 | -------------------------------------------------------------------------------- /template2-dockerfile-nginx/ssl.conf: -------------------------------------------------------------------------------- 1 | 2 | server { 3 | listen 443 ssl; 4 | 5 | server_name localhost; 6 | 7 | # 憑證與金鑰的路徑 8 | ssl_certificate /etc/nginx/ssl.csr; 9 | ssl_certificate_key /etc/nginx/ssl.key; 10 | 11 | location / { 12 | root /usr/share/nginx/html; 13 | index index.html index.htm; 14 | } 15 | error_page 497 https://$host:$server_port$request_uri; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /template2-dockerfile-nginx/ssl.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDgjCCAmoCCQDPv9oe0a2tqjANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMC 3 | VFcxDzANBgNVBAgMBlRhaXdhbjEPMA0GA1UEBwwGVGFpcGVpMQswCQYDVQQKDAJU 4 | ZDELMAkGA1UECwwCUmQxEjAQBgNVBAMMCTEyNy4wLjAuMTEjMCEGCSqGSIb3DQEJ 5 | ARYUYTEyMzQ1Njc4OUBnbWFpbC5jb20wHhcNMjAwMTE0MDc0MzAzWhcNMzAwMTEx 6 | MDc0MzAzWjCBgjELMAkGA1UEBhMCVFcxDzANBgNVBAgMBlRhaXdhbjEPMA0GA1UE 7 | BwwGVGFpcGVpMQswCQYDVQQKDAJUZDELMAkGA1UECwwCUmQxEjAQBgNVBAMMCTEy 8 | Ny4wLjAuMTEjMCEGCSqGSIb3DQEJARYUYTEyMzQ1Njc4OUBnbWFpbC5jb20wggEi 9 | MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC5shj+Zjj6DGirAR+GFs7qgpPq 10 | rXdKcQFJzDAwRHn+h70TVmBoS4DwgvrmLdYRIJBcVpJ8owv9nwOJSccsCS9SHDzv 11 | KomW7zDHwLWv+PR0tTVwd8G5rpQxAeN/JzwzTMIf0+p19cy7DsTai2QTYkZFhudS 12 | KDOeW6QaQrWbkaApBqanWLmuK4LwOuDjQTV2KL+G1sSQW2o8sCdWZ4py32cEBs+J 13 | zHzBBIvsKCS0P3i2kCpIuT6PyxrZB6WhI49EUlSi+4FIW6obTKnl6MRxxzzVZpBN 14 | KteBhR2l3QWzaKrENkmXsGFyuZe4CCFzKlA7ryzWsGLvYwM5CmzoTNtvT0JDAgMB 15 | AAEwDQYJKoZIhvcNAQELBQADggEBAEsJZxLFyNJvPnwhz1+/5erO/c4TMn4RZ9VE 16 | h06OW5NNQ7cIZlXbeb+xyziGIXGja03LqkgdM/7W/hodvFldOhtmpRfYvpcTpi9t 17 | eNpQiMYG9izX7aQeX+JkfDNiubGGP5V+snH/YNVzhsTvrW5vjcI7DvZSbfgaLJvm 18 | MSEIvIoFI2VLLROaArslaMLgsyFasp25uPaVow3nb2R922F2xFqX38EsQCgj3Twf 19 | AUgpfh/WVVEdKCWVhdbvrSdgDwL2d3sKOWXFpGg+UGHx9H+YrWqggfvM/XrN5XO2 20 | hcZ5L1eG9ICSAq3HaRGi4TTr8WSVqEw31AlwRl+4VTszCjdwGvI= 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /template2-dockerfile-nginx/ssl.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC5shj+Zjj6DGir 3 | AR+GFs7qgpPqrXdKcQFJzDAwRHn+h70TVmBoS4DwgvrmLdYRIJBcVpJ8owv9nwOJ 4 | SccsCS9SHDzvKomW7zDHwLWv+PR0tTVwd8G5rpQxAeN/JzwzTMIf0+p19cy7DsTa 5 | i2QTYkZFhudSKDOeW6QaQrWbkaApBqanWLmuK4LwOuDjQTV2KL+G1sSQW2o8sCdW 6 | Z4py32cEBs+JzHzBBIvsKCS0P3i2kCpIuT6PyxrZB6WhI49EUlSi+4FIW6obTKnl 7 | 6MRxxzzVZpBNKteBhR2l3QWzaKrENkmXsGFyuZe4CCFzKlA7ryzWsGLvYwM5Cmzo 8 | TNtvT0JDAgMBAAECggEBAKHrlDOwexo+W75Q+mX32XFPbx+BJrW4mAD7lUFhVGcH 9 | gW0tMl9/Bu2xqImxiinhFUAPFSitG7WJRJIfzRau455blR6PHu6Hnydu+H57PN3P 10 | bMunATuh6QJavT6QvcOVuOf750P4kw7BMBHI3fixBsl1ept/BpOdIRjs9mKyts9N 11 | yvXhEzR+qrndMbexZ9TFgXPDGfnl2kSaoNxZdXMCNyJJGW8mAaIps4W8iKe/O3sR 12 | w4uPqFveqSmiEyfx22wg9OA02xWg0yxX+WwLoubRMDdkJLZp74hbRKSsnT8Y8GNS 13 | M/eWjLCJnC5Tmfh1JcCZY8yO05qHMlwIkmHcPMfOLTECgYEA4LyM6Aw0ghNiyhnA 14 | nBzLaGm6+cJftQzWUX+I5Q51HWMrqjWuwoiHyv5eKBkTXNQQxB8jwirvlPplCoLT 15 | HnxfhD/b4uELjUApENDS5tJTDVWvwQ85lnll9K69YTkoqUqrNOnLuqEnD8wmtkRV 16 | H0f0jAo71Ln5l4n75SRkh7KGPbUCgYEA04czvoq5dskLP+qO0eRI5Mji/8KBwlis 17 | 5ZX05wJ0FGgYtTVMRMB1tDMuHc8Oq+O6fkQ7qs5sPcDW2q+fud9t/mgv1GZ9M8vL 18 | lLEuom8wRtzYuIbxCgvBdP73bHIx0PLsjfAsXL7ayUEhmCQ4PyCMXk91WigjoY1E 19 | K0Y60H1LOxcCgYBdZJ3d9OyBPdMpD6imd6TkQEQOdQNW9v29opVeMzLiQosr0eFN 20 | QHXGGw0/9qPASPSqvBIdJ8CmlaQVySY5HhCHog4b68/kJEysi0uJ1s/i08AVJ+GT 21 | seF33IIg/CL0r24UsDAU39Ge1AUma6FAPaPX6ozQq3SY0CPZJtWMOtlknQKBgQC5 22 | uFqZMGnYPnpPGx1ccegCX9LWpiuRvJPJXBlTfpb2l9MhvvMA5k4x8kHKUFLcXq0O 23 | UdBljqoAqkC6bzp5Uw/bMBTWk0nYYVWUbuC4I5Gqlhr+IRSfMmUf2QDaSYUtpSxH 24 | DxmUMModq77YOuzbmDNGVtN9XgKyxDqXGClphqi37wKBgHbWyjNFMm6FOw2sfy0h 25 | sl7ZVwPVJ6K4nnMtGUHuVUYYhZqQ7KpzBAOvngqgDPfpxPiI8Zezctvrxm6LNHvN 26 | qRe5XBc1QyBdqJ8TMy5tM2CYr8AxHUzlIW8wo940+8LzkHRIYz+LX3/mYACOWU15 27 | xVD9CX6XI1LF6Yj7XxUuwnPE 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /template3-docker-compose-flask-nginx-postgres/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | 3 | services: 4 | 5 | flask: 6 | build: ./flask 7 | container_name: template_flask 8 | # restart: always 9 | environment: 10 | - APP_NAME=FlaskApp 11 | expose: 12 | - 8080 13 | depends_on: 14 | - db 15 | 16 | db: 17 | image: postgres 18 | container_name: postgres 19 | volumes: 20 | - ./postgres-data:/var/lib/postgresql/data 21 | environment: 22 | POSTGRES_DB: db_name 23 | POSTGRES_USER: postgres_n 24 | POSTGRES_PASSWORD: postgres_p 25 | ports: 26 | - 5432:5432 27 | 28 | 29 | nginx: 30 | build: ./nginx 31 | container_name: template_nginx 32 | # restart: always 33 | ports: 34 | - "80:80" 35 | - "443:443" 36 | 37 | depends_on: 38 | - flask 39 | 40 | -------------------------------------------------------------------------------- /template3-docker-compose-flask-nginx-postgres/flask/Dockerfile: -------------------------------------------------------------------------------- 1 | 2 | # FROM python:alpine 3 | # Use the Python3.7.2 image 4 | # 指定一個 image 來源 5 | FROM python:3.7.2-stretch 6 | 7 | # Set the working directory to /app 8 | WORKDIR /app 9 | 10 | # Copy the current directory contents into the container at /app 11 | # ADD:將檔案加到 images 內 12 | ADD . /app 13 | 14 | # Install the dependencies 15 | # build 時使用,會執行此命令 16 | RUN pip install --upgrade pip 17 | RUN pip install -r requirements.txt 18 | 19 | # run the command to start uWSGI 20 | # run container 時要執行的命令 21 | CMD ["uwsgi", "app.ini"] 22 | 23 | 24 | # ENV:環境變數設定 -------------------------------------------------------------------------------- /template3-docker-compose-flask-nginx-postgres/flask/app.ini: -------------------------------------------------------------------------------- 1 | [uwsgi] 2 | wsgi-file = main.py 3 | callable = app 4 | socket = :8080 5 | processes = 4 6 | threads = 2 7 | master = true 8 | chmod-socket = 660 9 | vacuum = true 10 | die-on-term = true -------------------------------------------------------------------------------- /template3-docker-compose-flask-nginx-postgres/flask/app/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask_sqlalchemy import SQLAlchemy 3 | 4 | from .config.config import config 5 | 6 | db = SQLAlchemy() 7 | 8 | 9 | def create_app(config_name): 10 | app = Flask(__name__) 11 | 12 | # 設定config 13 | app.config.from_object(config['development']) 14 | 15 | db.init_app(app) 16 | 17 | from .models.users import Person 18 | 19 | @app.route('/') 20 | def index(): 21 | return 'hello world' 22 | 23 | @app.route('/create') 24 | def create_db(): 25 | db.create_all() 26 | return 'ok' 27 | 28 | @app.route('/max') 29 | def insert_max(): 30 | u = Person('Max') 31 | db.session.add(u) 32 | db.session.commit() 33 | return 'ok' 34 | 35 | return app 36 | -------------------------------------------------------------------------------- /template3-docker-compose-flask-nginx-postgres/flask/app/config/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | import datetime 3 | 4 | basedir = os.path.abspath(os.path.dirname(__file__)) 5 | 6 | 7 | def create_sqlite_uri(db_name): 8 | return "sqlite:///" + os.path.join(basedir, db_name) 9 | 10 | 11 | class BaseConfig: # 基本配置 12 | SECRET_KEY = 'CHANGE THIS KEY' 13 | PERMANENT_SESSION_LIFETIME = datetime.timedelta(days=14) 14 | 15 | JWT_SECRET_KEY = 'CHANGE THIS JWT KEY' 16 | JWT_ACCESS_TOKEN_EXPIRES = datetime.timedelta(minutes=15) 17 | JWT_REFRESH_TOKEN_EXPIRES = datetime.timedelta(days=30) 18 | 19 | 20 | class DevelopmentConfig(BaseConfig): 21 | DEBUG = False 22 | SQLALCHEMY_TRACK_MODIFICATIONS = False 23 | SQLALCHEMY_DATABASE_URI = 'postgresql://postgres_n:postgres_p@db:5432/db_name' 24 | 25 | 26 | class TestingConfig(BaseConfig): 27 | TESTING = True 28 | SQLALCHEMY_TRACK_MODIFICATIONS = False 29 | SQLALCHEMY_DATABASE_URI = create_sqlite_uri("test.db") 30 | WTF_CSRF_ENABLED = False 31 | 32 | 33 | config = { 34 | 'development': DevelopmentConfig, 35 | 'testing': TestingConfig, 36 | 'default': DevelopmentConfig, 37 | 'base': BaseConfig 38 | } 39 | -------------------------------------------------------------------------------- /template3-docker-compose-flask-nginx-postgres/flask/app/models/users.py: -------------------------------------------------------------------------------- 1 | from .. import db 2 | 3 | 4 | class Person(db.Model): 5 | __tablename__ = 'person' 6 | id = db.Column(db.Integer, primary_key=True) 7 | username = db.Column(db.String(80), nullable=False) 8 | 9 | def __init__(self, username): 10 | self.username = username -------------------------------------------------------------------------------- /template3-docker-compose-flask-nginx-postgres/flask/main.py: -------------------------------------------------------------------------------- 1 | import os 2 | from dotenv import load_dotenv 3 | 4 | # load .env 5 | dotenv_path = os.path.join(os.path.dirname(__file__), '.flaskenv') 6 | if os.path.exists(dotenv_path): 7 | load_dotenv(dotenv_path, override=True) 8 | 9 | from flask_migrate import Migrate, upgrade, migrate 10 | from app import create_app, db 11 | from app import create_app 12 | 13 | app = create_app('development') 14 | migrates = Migrate(app=app, db=db) 15 | 16 | 17 | @app.shell_context_processor 18 | def make_shell_context(): 19 | return dict(app=app, db=db) 20 | 21 | 22 | @app.cli.command() 23 | def deploy(): 24 | # migrate database to latest revision 25 | migrate() 26 | upgrade() 27 | 28 | 29 | @app.cli.command() 30 | def test(): 31 | import unittest 32 | import sys 33 | 34 | tests = unittest.TestLoader().discover("tests") 35 | result = unittest.TextTestRunner(verbosity=2).run(tests) 36 | if result.errors or result.failures: 37 | sys.exit(1) 38 | -------------------------------------------------------------------------------- /template3-docker-compose-flask-nginx-postgres/flask/requirements.txt: -------------------------------------------------------------------------------- 1 | alembic==1.2.1 2 | Click==7.0 3 | Flask==1.1.1 4 | Flask-Cors==3.0.8 5 | Flask-Migrate==2.5.2 6 | Flask-SQLAlchemy==2.4.1 7 | itsdangerous==1.1.0 8 | Jinja2==2.10.3 9 | Mako==1.1.0 10 | MarkupSafe==1.1.1 11 | psycopg2-binary==2.8.5 12 | python-dateutil==2.8.0 13 | python-dotenv==0.10.3 14 | python-editor==1.0.4 15 | six==1.12.0 16 | SQLAlchemy==1.3.10 17 | uWSGI==2.0.18 18 | Werkzeug==0.16.0 19 | -------------------------------------------------------------------------------- /template3-docker-compose-flask-nginx-postgres/nginx/Dockerfile: -------------------------------------------------------------------------------- 1 | # Use the Nginx image 2 | FROM nginx 3 | 4 | # Remove the default nginx.conf 5 | RUN rm /etc/nginx/conf.d/default.conf 6 | 7 | # Replace with our own nginx.conf 8 | COPY nginx.conf /etc/nginx/conf.d/ 9 | COPY ssl.csr /etc/nginx/ssl.csr 10 | COPY ssl.key /etc/nginx/ssl.key 11 | -------------------------------------------------------------------------------- /template3-docker-compose-flask-nginx-postgres/nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | 2 | server { 3 | listen 80; 4 | # server_name 0.0.0.0; 5 | location / { 6 | include uwsgi_params; 7 | uwsgi_pass flask:8080; 8 | } 9 | } 10 | 11 | server { 12 | listen 443 ssl; 13 | # server_name 0.0.0.0; 14 | location / { 15 | include uwsgi_params; 16 | uwsgi_pass flask:8080; 17 | } 18 | # 憑證與金鑰的路徑 19 | ssl_certificate /etc/nginx/ssl.csr; 20 | ssl_certificate_key /etc/nginx/ssl.key; 21 | } -------------------------------------------------------------------------------- /template3-docker-compose-flask-nginx-postgres/nginx/ssl.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDkjCCAnoCCQCp8bKdjOKC2DANBgkqhkiG9w0BAQsFADCBijELMAkGA1UEBhMC 3 | VFcxDzANBgNVBAgMBlRhaXdhbjEPMA0GA1UEBwwGVGFpcGVpMRAwDgYDVQQKDAdN 4 | YXhsaXN0MQwwCgYDVQQLDANNYXgxFjAUBgNVBAMMDTM0LjgwLjIwMC4yMjMxITAf 5 | BgkqhkiG9w0BCQEWEmEwMDI1MDcxQGdtYWlsLmNvbTAeFw0xOTEwMjkwMzAxMDZa 6 | Fw0yOTEwMjYwMzAxMDZaMIGKMQswCQYDVQQGEwJUVzEPMA0GA1UECAwGVGFpd2Fu 7 | MQ8wDQYDVQQHDAZUYWlwZWkxEDAOBgNVBAoMB01heGxpc3QxDDAKBgNVBAsMA01h 8 | eDEWMBQGA1UEAwwNMzQuODAuMjAwLjIyMzEhMB8GCSqGSIb3DQEJARYSYTAwMjUw 9 | NzFAZ21haWwuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuAm/ 10 | K++Abv3n1C3c9mKF9HwdLjUZSSo/f18Y/CWKuFrmYwx9U/IVHd9Ye+PDrElgH9a2 11 | M2zyf693qj1/cjXzCR6bNAyGhz8USStEYRCYKrddURjWdg9yIdhQ+obJ9PmcazLK 12 | 3MQjcnRvqSv3W2wqzKiWjQVx1ejupOm8YbYjMEM/ve+ZfvVcpBgiaZXnpexKhyTO 13 | vNrvyRCosWbGGxSk8I6KIkyvEFkYFi02GgG4ALkBAadbFiuzvvw/9OwqmReMp7zX 14 | Rhe3A8bVqQyLOKfYhRmaomxnA9A9PaOTAwhJwHCrgpSib8Z/hRfUm7Wcqbm5YMas 15 | ztdHNMp0ZXoppK6FkwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBBgIANe6LKpkJG 16 | QE0B37pEsaqBg+cynMclICSz6odsLUkv3sk4qbuH3TbX9f5Ow24/QjEU4shCU6Yw 17 | /fpKfnDSRoSen+QFTMYpmSPJytWqUv23GWFEO28jsSKOILHHI0jTd3fza0JfLNif 18 | R+rLiMU2lbhP3iaagPKw6VQZcJqeZongjERdAlmnZFL5ILL+CITS5XL78KGU9GZT 19 | RUQ14wZj5LW1wzP1ebXjcV8MweV+SLo/zqF6Yhl625t4l18bOnhbM5oNUUMPZmng 20 | c+a/RPhbMJj8o5ZlQmn/XmEVBcRYRmWagnWgKIj5oK/LBGTKtnajLS+3wLFr++AR 21 | 1YZ7QXl9 22 | -----END CERTIFICATE----- 23 | -------------------------------------------------------------------------------- /template3-docker-compose-flask-nginx-postgres/nginx/ssl.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC4Cb8r74Bu/efU 3 | Ldz2YoX0fB0uNRlJKj9/Xxj8JYq4WuZjDH1T8hUd31h748OsSWAf1rYzbPJ/r3eq 4 | PX9yNfMJHps0DIaHPxRJK0RhEJgqt11RGNZ2D3Ih2FD6hsn0+ZxrMsrcxCNydG+p 5 | K/dbbCrMqJaNBXHV6O6k6bxhtiMwQz+975l+9VykGCJpleel7EqHJM682u/JEKix 6 | ZsYbFKTwjooiTK8QWRgWLTYaAbgAuQEBp1sWK7O+/D/07CqZF4ynvNdGF7cDxtWp 7 | DIs4p9iFGZqibGcD0D09o5MDCEnAcKuClKJvxn+FF9SbtZypublgxqzO10c0ynRl 8 | eimkroWTAgMBAAECggEADy5OpLMm5wDuxKPb3D8YSaiLR6p9oXlor3sKtpHqNXba 9 | FkWLeQc5ErO/ush49Zc/5KBzex57imjQ+CwGfUdR+uiZaNu3pSRg6gA1rcQOqUdi 10 | 3TOtTSPNCFmtpFzF//0vF/Cipz9OpTxRkaTczJ/JkARAFvXiBAEo3lWhgnXhxTNC 11 | 5I0r0E85GfG/4eHVb3AmL4fBiSN3g8sBTi214+vgAnP/3lgpSPj01mOzhf8oBdji 12 | tzsbnDYoalA7A3inelg5IappfU4BZxLy8fSdo9iifkMVDZPnEvfn1zwlaUFoPnsf 13 | JTcnRvYj6ADO6b5uAdn/Ac9hPgXtNmW2HfeMRTGAMQKBgQDxrb93BwwtnQiSghTs 14 | 5cWHcDieWc5y4XQgKWCpyaUkOdkudxtBczZMtRZrVGbhgwLDoC4on8HboRFW29Nj 15 | 3cpMXTYIQrqOX4UcbQI9sKPe5Ll8jao/pC1nszqF14D+7LMlTtdpVXGNBDB+g6YM 16 | XoWCdy2L/Yygbk482j1zkUs8nwKBgQDC8ZgDheD4zGUwE14on+AIJeO1mdaRNSdr 17 | lsAd8hL5jC7zMBG3z/vIeWdk4PGXRbILv1bHGBf8Rgs9BlwqISDzrSQRCgR6Iq1o 18 | T0l+SO0KED9hY2q0u9Y5HwKjyV7f///u71g9Q79cGRFwTWnVax71ejOMuFPf3q/n 19 | b1BODKyejQKBgQChcjQpS2fzQKftV0CrUIM4CtuHzO6BB+MPaRTN14qePJa814w5 20 | mMF5VK95W5SuqVo7XNH5CV/zXBG9OHRqjksJ4Gqr8ge1/FFrv9ZzZ4DQ8XKHpgtJ 21 | IF/EmpJJvsDJi03Ram20TAPi9B2BJmjScoI1uW+PyP0cXxOcyx2qCjF8eQKBgEkb 22 | EtfXYAvNkvDZgokXk4tasi9LNsUTuunFCdzxCB6fbIf0ceCN1a1ToeuZ09/X2jI/ 23 | mgplxbDsj5BeDzgZXmMjfhAJwq4OzRr+COCb9pC8kRgzkTOf8XFQaMwFW1gDh/YR 24 | ufSXsG6YVArabSME3gJOxoAyK/obZ7oR63qplB6NAoGAQuNPMIZ7mgRo0mP63/3y 25 | f5O7VvRXMu2+H8WmTuqjZ/p7IbnyaNB4gmoZ6BWxfj1TJXlcN4Hy6zwsutHGVskL 26 | WZay5cDk1AuHVIYhiLHSSt4DgA/aNDorKHiPxRx3k2eAiFqXvwwJsR7YTCiepBoP 27 | 5IO+9FEd9g8ooNeS2Yv5zHg= 28 | -----END PRIVATE KEY----- 29 | --------------------------------------------------------------------------------