Flaskでのシンプルなアプリケーションの設計

GitHubのFlaskリポジトリに投稿されたこの記事は、思いやりのあるプログラマーの集合的な創造性の成果であり、元の著者はBrice Leroyです。 初心者にとって非常に便利なFlaskマテリアルです。 私個人にとって、彼は多くの簡単な質問に対する答えであり、その主なものは「プロジェクトの構造化方法」でした。



少なくとも経験豊富なプログラマーにとっては、役に立つとは思われず、多くの人が説明された原則にまったく同意しないかもしれませんが、トレーニングの初期段階の人にとっては、それが私にとってのように、開発の推進力になります。 それが私がロシア語に翻訳した理由です-この記事のエントリのしきい値は非常に低く、さらに低くする価値があります。



説明されている例は、Python 3.5、Flask 0.10、Flask-SQLAlchemy 2.1、Flask-WTF 0.9でテストされています。



Flaskでのシンプルなアプリケーションの設計



このドキュメントは、Flaskの公式ドキュメントには含まれていません。 これは、さまざまな非公式ソースからのアドバイスをまとめたものであり、テストされたことはありません。 説明した手法は非常に便利ですが、同時に非常に危険です。 Githubに投稿された元のドキュメントには変更を加えないでください。StackOverflowへの多くの回答で参照されています。 修正やメモを作成できますが、投稿するには個人のWebサイトまたはブログを使用してください。



この記事は、 FlaskSQLAlchemyおよびWTFormsコアモジュールを使用してプロジェクトの構造を説明する試みです。



設置



フラスコ



フラスコのインストール手順



virtualenvを使用することをお勧めします -このシステムは非常にシンプルで、同じシステムで複数の仮想環境をホストでき、すべてのライブラリがローカルにインストールされるため、スーパーユーザー権限は不要です。



Flask-SQLAlchemy



SQLAlchemyは、オブジェクトとあらゆる種類のリレーショナルデータベースの相互作用のためのシンプルで強力なインターフェイスを提供します。 Flask-SQLAlchemyを仮想環境にインストールするには、pipを使用します。



pip install flask-sqlalchemy
      
      





Flask-SQLAlchemyパッケージのより完全な説明



フラスコ-wtf



WTFormsを使用すると、ユーザーからデータを簡単に取得できます。



 pip install Flask-WTF
      
      





Flask-WTFパッケージのより完全な説明



はじめに



そのため、必要なライブラリが準備されます。 これは、プロジェクトの基本構造のように見えるはずです。



 /app/users/__init__.py /app/users/views.py /app/users/forms.py /app/users/constants.py /app/users/models.py /app/users/decorators.py
      
      





モジュール(アプリケーション要素)ごとに次の構造が作成されます。



 /app/templates/404.html /app/templates/base.html /app/templates/users/login.html /app/templates/users/register.html ...
      
      





プレゼンテーションテンプレート(jinja)は、 templatesディレクトリとmodulesサブディレクトリに保存されます。



 /app/static/js/main.js /app/static/css/reset.css /app/static/img/header.png
      
      





不変のファイルを処理するには、別のWebサーバーを使用する必要がありますが、開発時には、この作業をFlaskに割り当てることができます。 このようなファイルは静的ディレクトリから自動的に発行され、別のディレクトリの使用を構成するには、 この記事の情報を使用できます。



問題のアプリケーション用に1つのモジュール、usersが作成されます。 ユーザー登録とログインの管理を提供し、そのプロファイルデータを表示します。



構成



/run.pyを使用してWebサーバーを起動します。



  from app import app app.run(debug=True)
      
      





/shell.pyは、コマンドを実行する機能を備えたコンソールへのアクセスを提供します。 おそらくpdbによるデバッグほど便利ではありませんが、非常に便利です(少なくともデータベースを初期化する場合)。



  #!/usr/bin/env python import os import readline from pprint import pprint from flask import * from app import * os.environ['PYTHONINSPECT'] = 'True'
      
      





翻訳者注:

Windowsを実行している場合(レンガを投げる必要はありません!)、readlineライブラリは利用できません。 この場合、仮想または実際のpython環境にpyreadlineライブラリをインストールし、インポートを次の形式の構造でラップする必要があります。



 try: import readline except: import pyreadline
      
      





原則として、このライブラリをまったく使用せずに、bashのような要素を追加することにより、コンソールとの対話を単純化します。



/config.pyは、アプリケーションの構成全体を保存します。 この例では、SQLiteが開発に非常に便利であるため、データベースとして使用されます。 /config.pyファイル 、テストシステムと産業システムで異なるため、リポジトリに含めるべきではありません。



  import os _basedir = os.path.abspath(os.path.dirname(__file__)) DEBUG = False ADMINS = frozenset(['youremail@yourdomain.com']) SECRET_KEY = 'This string will be replaced with a proper key in production.' SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(_basedir, 'app.db') DATABASE_CONNECT_OPTIONS = {} THREADS_PER_PAGE = 8 WTF_CSRF_ENABLED = True WTF_CSRF_SECRET_KEY = "somethingimpossibletoguess" RECAPTCHA_USE_SSL = False RECAPTCHA_PUBLIC_KEY = '6LeYIbsSAAAAACRPIllxA7wvXjIE411PfdB2gt2J' RECAPTCHA_PRIVATE_KEY = '6LeYIbsSAAAAAJezaIq3Ft_hSTo0YtyeFG-JgRtu' RECAPTCHA_OPTIONS = {'theme': 'white'}
      
      







モジュール



ユーザーモジュールを次の順序で構成します。定数モデルに関連付けられたモデルを定義し、次にフォーム、最後にプレゼンテーションとテンプレートを定義します。



モデル



/app/users/models.py



  from app import db from app.users import constants as USER class User(db.Model): __tablename__ = 'users_user' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(50), unique=True) email = db.Column(db.String(120), unique=True) password = db.Column(db.String(120)) role = db.Column(db.SmallInteger, default=USER.USER) status = db.Column(db.SmallInteger, default=USER.NEW) def __init__(self, name=None, email=None, password=None): self.name = name self.email = email self.password = password def getStatus(self): return USER.STATUS[self.status] def getRole(self): return USER.ROLE[self.role] def __repr__(self): return '<User %r>' % (self.name)
      
      





ファイル/app/users/constants.pyの定数:



  # User role ADMIN = 0 STAFF = 1 USER = 2 ROLE = { ADMIN: 'admin', STAFF: 'staff', USER: 'user', } # user status INACTIVE = 0 NEW = 1 ACTIVE = 2 STATUS = { INACTIVE: 'inactive', NEW: 'new', ACTIVE: 'active', }
      
      





定数について言えば、定数がモジュール内の別のファイルに保存されるのが好きです。 定数はモデル、フォーム、およびプレゼンテーションで使用される可能性が最も高いため、簡単に見つけられる便利に整理されたデータを取得できます。 さらに、大文字のモジュール名で定数をインポートすると(たとえば、 users.constantsの USERS )、名前の競合を回避できます。





目的のオブジェクトのモデルが作成されたら、それと連携するフォームを設計する必要があります。



登録フォームはユーザー名、メールアドレス、パスワードを要求し、バリデーターはユーザーが入力したデータの正確性を検証するために使用され、 Recaptchaフィールドはボットの登録を防ぎます。 ユーザー同意書を実装する必要がある場合、 accept_tosという名前のBooleanFieldフィールドも追加されています。 このフィールドは必須としてマークされています 。つまり、ユーザーはフォームによって生成されたチェックボックスをマークする必要があります。 ログインフォームには、同様の検証機能を備えた電子メールパスワードのフィールドがあります



フォームは/app/users/forms.pyで説明されています



  from flask.ext.wtf import Form, RecaptchaField from wtforms import TextField, PasswordField, BooleanField from wtforms.validators import Required, EqualTo, Email class LoginForm(Form): email = TextField('Email address', [Required(), Email()]) password = PasswordField('Password', [Required()]) class RegisterForm(Form): name = TextField('NickName', [Required()]) email = TextField('Email address', [Required(), Email()]) password = PasswordField('Password', [Required()]) confirm = PasswordField('Repeat Password', [ Required(), EqualTo('password', message='Passwords must match') ]) accept_tos = BooleanField('I accept the TOS', [Required()]) recaptcha = RecaptchaField()
      
      





各フィールドの最初のパラメーターはそのラベルです。たとえば、フォームの名前フィールドにNickNameラベルが指定されています。 パスワード入力フィールドには、2つのフィールドのデータを比較するEqualToバリデーターが使用されます。



WTForms機能の詳細については、ここをクリックしてください



提出



ビューでは、 ブループリントが宣言されています-モジュールスキームのオブジェクト。そのプロパティはurl_prefixを示し、 routeで指定されたURLの先頭で置換されます 。 また、ビューはform.validate_on_submitフォームメソッドも使用します 。これ 、HTTP POSTメソッドと有効なフォームに対してtrueを返します。 ログインに成功すると、ユーザーはプロファイルページ( / users / me )にリダイレクトされます。 権限のないユーザーによるアクセスを防ぐために、ファイル/app/users/decorators.pyに特別なデコレーターが作成されます:



  from functools import wraps from flask import g, flash, redirect, url_for, request def requires_login(f): @wraps(f) def decorated_function(*args, **kwargs): if g.user is None: flash(u'You need to be signed in for this page.') return redirect(url_for('users.login', next=request.path)) return f(*args, **kwargs) return decorated_function
      
      





このデコレータは、 g.user変数のデータをチェックします。 変数が設定されていない場合、ユーザーは認証されず、情報メッセージが設定され、ログインビュー(ログイン)にリダイレクトされます。 g.user変数のデータは、 before_request関数に配置されます。 ユーザープロファイルから大量のデータ(履歴データ、友人、メッセージ、アクション)を受信すると、データベースにアクセスするときに深刻なスローダウンが発生する可能性があるため、ユーザーデータをキャッシュすることでこの問題を解決できます(ただし、オブジェクトを集中的に変更し、毎回キャッシュをクリアする場合のみ)更新)。 添付のアプリコードは/app/users/views.pyです。



  from flask import Blueprint, request, render_template, flash, g, session, redirect, url_for from werkzeug import check_password_hash, generate_password_hash from app import db from app.users.forms import RegisterForm, LoginForm from app.users.models import User from app.users.decorators import requires_login mod = Blueprint('users', __name__, url_prefix='/users') @mod.route('/me/') @requires_login def home(): return render_template("users/profile.html", user=g.user) @mod.before_request def before_request(): """ pull user's profile from the database before every request are treated """ g.user = None if 'user_id' in session: g.user = User.query.get(session['user_id']) @mod.route('/login/', methods=['GET', 'POST']) def login(): """ Login form """ form = LoginForm(request.form) # make sure data are valid, but doesn't validate password is right if form.validate_on_submit(): user = User.query.filter_by(email=form.email.data).first() # we use werzeug to validate user's password if user and check_password_hash(user.password, form.password.data): # the session can't be modified as it's signed, # it's a safe place to store the user id session['user_id'] = user.id flash('Welcome %s' % user.name) return redirect(url_for('users.home')) flash('Wrong email or password', 'error-message') return render_template("users/login.html", form=form) @mod.route('/register/', methods=['GET', 'POST']) def register(): """ Registration Form """ form = RegisterForm(request.form) if form.validate_on_submit(): # create an user instance not yet stored in the database user = User(name=form.name.data, email=form.email.data, \ password=generate_password_hash(form.password.data)) # Insert the record in our database and commit it db.session.add(user) db.session.commit() # Log the user in, as he now has an id session['user_id'] = user.id # flash will display a message to the user flash('Thanks for registering') # redirect user to the 'home' method of the user module. return redirect(url_for('users.home')) return render_template("users/register.html", form=form)
      
      





模様



JinjaテンプレートエンジンはFlaskに組み込まれています。 その利点の1つは、ロジックを継承および組み込みできることです(依存関係、ループ、コンテキストの変更)。 他のテンプレートが継承されるテンプレート/app/templates/base.htmlを作成します。 複数の継承を指定することができます(たとえば、 main.htmlから順番に取得されるtwocolumn.htmlテンプレートからの継承 )。 基本テンプレートは、各継承テンプレートのget_flashed_messages変数からの情報(フラッシュ)メッセージの表示も簡素化します。



これで、ページの基本構造を設定する必要がなくなり、 base.htmlへのすべての変更が継承されたテンプレートに反映されます。 テンプレートの原因となる表現に従ってテンプレートに名前を付けることをお勧めします。これはテンプレートの名前/app/templates/users/register.htmlです。



  <html> <head> <title>{% block title %}My Site{% endblock %}</title> {% block css %} <link rel="stylesheet" href="/static/css/reset-min.css" /> <link rel="stylesheet" href="/static/css/main.css" /> {% endblock %} {% block script %} <script src="/static/js/main.js" type="text/javascript"></script> {% endblock %} </head> <body> <div id="header">{% block header %}{% endblock %}</div> <div id="messages-wrap"> <div id="messages"> {% for category, msg in get_flashed_messages(with_categories=true) %} <p class="message flash-{{ category }}">{{ msg }}</p> {% endfor %} </div> </div> <div id="content">{% block content %}{% endblock %}</div> <div id="footer">{% block footer %}{% endblock %}</div> </body> </html>
      
      





テンプレート/app/templates/users/login.html



  {% extends "base.html" %} {% block content %} {% from "forms/macros.html" import render_field %} <form method="POST" action="." class="form"> {{ form.csrf_token }} {{ render_field(form.email, class="input text") }} {{ render_field(form.password, class="input text") }} <input type="submit" value="Login" class="button green"> </form> <a href="{{ url_for('users.register') }}">Register</a> {% endblock %}
      
      





作成されたテンプレートは、マクロを使用してhtmlフィールドの作成を自動化します。 このマクロはさまざまなモジュールで使用されるため、別のファイル/app/templates/forms/macros.htmlに配置されます。



  {% macro render_field(field) %} <div class="form_field"> {{ field.label(class="label") }} {% if field.errors %} {% set css_class = 'has_error ' + kwargs.pop('class', '') %} {{ field(class=css_class, **kwargs) }} <ul class="errors">{% for error in field.errors %}<li>{{ error|e }}</li>{% endfor %}</ul> {% else %} {{ field(**kwargs) }} {% endif %} </div> {% endmacro %}
      
      





最後に、プリミティブな/app/templates/users/profile.htmlテンプレートが作成れました



  {% extends "base.html" %} {% block content %} Hi {{ user.name }}! {% endblock %}
      
      





アプリケーションの初期化



想像のとおり 、アプリケーションは/app/init.pyファイルで初期化されます。



  import os import sys from flask import Flask, render_template from flask.ext.sqlalchemy import SQLAlchemy app = Flask(__name__) app.config.from_object('config') db = SQLAlchemy(app) ######################## # Configure Secret Key # ######################## def install_secret_key(app, filename='secret_key'): """Configure the SECRET_KEY from a file in the instance directory. If the file does not exist, print instructions to create it from a shell with a random key, then exit. """ filename = os.path.join(app.instance_path, filename) try: app.config['SECRET_KEY'] = open(filename, 'rb').read() except IOError: print('Error: No secret key. Create it with:') full_path = os.path.dirname(filename) if not os.path.isdir(full_path): print('mkdir -p {filename}'.format(filename=full_path)) print('head -c 24 /dev/urandom > {filename}'.format(filename=filename)) sys.exit(1) if not app.config['DEBUG']: install_secret_key(app) @app.errorhandler(404) def not_found(error): return render_template('404.html'), 404 from app.users.views import mod as usersModule app.register_blueprint(usersModule) # Later on you'll import the other blueprints the same way: #from app.comments.views import mod as commentsModule #from app.posts.views import mod as postsModule #app.register_blueprint(commentsModule) #app.register_blueprint(postsModule)
      
      





SQLAlchemy DBインスタンスとユーザーモデルは2つの異なるファイルにあります。app.users.viewsimport modのをusersModuleとして使用して、両方を共通のネームスペースにインポートする必要があります。 そうしないと、 db.create_all()コマンドは結果を生成しません。



仮想環境virtualenvをアクティブにし、データベースを初期化します。



 user@Machine:~/Projects/dev$ . env/bin/activate (env)user@Machine:~/Projects/dev$ python shell.py >>> from app import db >>> db.create_all() >>> exit()
      
      





これで、 python run.pyコマンドを実行して、次のようなメッセージを取得できます。



 (env)user@Machine:~/Projects/dev$ python run.py * Running on http://127.0.0.1:5000/ * Restarting with reloader
      
      





ブラウザでアドレスhttp ://127.0.0.1►00/users/me/を開くと、ログインページにリダイレクトされ、登録ページへのリンクが表示されます。



All Articles