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