青写真とビュー(Blueprints and Views) Blueprints and Views

viewの関数は、アプリケーションへのリクエストに対して応答するために書くコードになります。Flaskは、受信する(incoming)リクエストのURLを、それを処理すべきviewへと照合するためにパターンを使用します。送信用の(outgoing)responseへとFlaskが変換するデータを、viewは返します。URLからview名とは別の向きで、viewに対応するURLを(viewの)名前と引数に基づいて生成することも、Flaskでは可能です。 A view function is the code you write to respond to requests to your application. Flask uses patterns to match the incoming request URL to the view that should handle it. The view returns data that Flask turns into an outgoing response. Flask can also go the other direction and generate a URL to a view based on its name and arguments.

blueprintの作成 Create a Blueprint

Blueprintは、関連するviewおよびその他のコードをグループへと編成する方法です。viewおよびその他のコードを直接Flaskアプリケーションに登録するよりも、代わりにそれらをblueprintに登録します。それから、factory関数の中でFlaskアプリケーションが利用可能になったときに、blueprintをFlaskアプリケーションに登録します。 A :class:`Blueprint` is a way to organize a group of related views and other code. Rather than registering views and other code directly with an application, they are registered with a blueprint. Then the blueprint is registered with the application when it is available in the factory function.

Flaskrでは2つのblueprintがあり、ひとつは認証に関する関数のためであり、ひとつはブログへの投稿記事に関する関数のためです。それぞれのblueprintのコードは、異なるモジュールにして進めます。ブログ(のコード)は認証に関して知っている必要があるので、まずは認証のモジュールから書いていきます。 Flaskr will have two blueprints, one for authentication functions and one for the blog posts functions. The code for each blueprint will go in a separate module. Since the blog needs to know about authentication, you'll write the authentication one first.

flaskr/auth.py
import functools

from flask import (
    Blueprint, flash, g, redirect, render_template, request, session, url_for
)
from werkzeug.security import check_password_hash, generate_password_hash

from flaskr.db import get_db

bp = Blueprint('auth', __name__, url_prefix='/auth')

これは'auth'と名付けられたBlueprintを作成します。Flaskアプリケーションのオブジェクトと似たように、blueprintは自分がどこで定義されているか知る必要があり、従って__name__が2番目の引数として渡されています。url_prefixは、blueprintと関連付けられる全てのURLの(パス部分の)先頭に付けられます。 This creates a :class:`Blueprint` named ``'auth'``. Like the application object, the blueprint needs to know where it's defined, so ``__name__`` is passed as the second argument. The ``url_prefix`` will be prepended to all the URLs associated with the blueprint.

app.register_blueprint()を使用して、factoryからblueprintをimportして登録します。新しいコードはfactory関数の最後で、appを返す直前に置くようにします。 Import and register the blueprint from the factory using :meth:`app.register_blueprint() <Flask.register_blueprint>`. Place the new code at the end of the factory function before returning the app.

flaskr/__init__.py
def create_app():
    app = ...
    # existing code omitted

    from . import auth
    app.register_blueprint(auth.bp)

    return app

この認証のblueprintは、新しいユーザの登録と、ログインとログアウトのviewを持つようにします。 The authentication blueprint will have views to register new users and to log in and log out.

最初のビュー: 登録(Register) The First View: Register

ユーザが/auth/registerのURLへ訪ずれたときは、registerのviewはユーザが記入すべきformを持つHTMLを返します。ユーザがformを提出(submit)したときは、ユーザの入力を検証し、それから、エラーメッセージと一緒にフォームを再表示するか、または、新しいユーザを作成してログインページへ行くようにします。 When the user visits the ``/auth/register`` URL, the ``register`` view will return `HTML`_ with a form for them to fill out. When they submit the form, it will validate their input and either show the form again with an error message or create the new user and go to the login page.

まずは、viewのコードだけ書いていきます。次のページでは、HTMLのformを生成するテンプレートを書いていきます。 For now you will just write the view code. On the next page, you'll write templates to generate the HTML form.

flaskr/auth.py
@bp.route('/register', methods=('GET', 'POST'))
def register():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        db = get_db()
        error = None

        if not username:
            error = 'Username is required.'
        elif not password:
            error = 'Password is required.'
        elif db.execute(
            'SELECT id FROM user WHERE username = ?', (username,)
        ).fetchone() is not None:
            error = 'User {} is already registered.'.format(username)

        if error is None:
            db.execute(
                'INSERT INTO user (username, password) VALUES (?, ?)',
                (username, generate_password_hash(password))
            )
            db.commit()
            return redirect(url_for('auth.login'))

        flash(error)

    return render_template('auth/register.html')

registerのviewの関数が何をしているか、以下に示します: Here's what the ``register`` view function is doing:

  1. @bp.routeは、URLの/registerregisterのviewの関数とを関連付けます。Flaskが/auth/registerへのリクエストを受信したとき、Flaskはregisterのviewを呼び出し、その戻り値をレスポンスとして使用します。 :meth:`@bp.route <Blueprint.route>` associates the URL ``/register`` with the ``register`` view function. When Flask receives a request to ``/auth/register``, it will call the ``register`` view and use the return value as the response.

  2. もしユーザがformを提出した場合、Request.methodはPOSTになります。その場合は、入力データの検証を開始します。 If the user submitted the form, :attr:`request.method <Request.method>` will be ``'POST'``. In this case, start validating the input.

  3. request.formは、提出されたformのキーと値とを対応付ける、dictの特別なタイプです。ユーザは自分のusernamepasswordを入力します。 :attr:`request.form <Request.form>` is a special type of :class:`dict` mapping submitted form keys and values. The user will input their ``username`` and ``password``.

  4. usernamepasswordが空でないか検証します。 Validate that ``username`` and ``password`` are not empty.

  5. usernameが登録済でないことを、データベースに問合せして結果が返るか調べることで検証します。db.executeは、ユーザのどの入力にも対応するプレースホルダ?を持つSQLのqueryと、プレースホルダを置き換える値のtupleを受け取ります。データベースライブラリはSQLインジェクション攻撃に対して脆弱にならないように、値のエスケープを取り計らいます。 Validate that ``username`` is not already registered by querying the database and checking if a result is returned. :meth:`db.execute <sqlite3.Connection.execute>` takes a SQL query with ``?`` placeholders for any user input, and a tuple of values to replace the placeholders with. The database library will take care of escaping the values so you are not vulnerable to a *SQL injection attack*.

    fetchone()はquery(の結果)から1行を返します。もしqueryが何も結果を返さない場合は、Noneを返します。後程、すべての結果のリストを返すfetchall()が使用されます。 :meth:`~sqlite3.Cursor.fetchone` returns one row from the query. If the query returned no results, it returns ``None``. Later, :meth:`~sqlite3.Cursor.fetchall` is used, which returns a list of all results.

  6. もし検証が成功した場合は、新しいユーザのデータをデータベースへinsertします。セキュリティのために、パスワードは決してデータベースへ直接格納しないようにするべきです。代わりに、generate_password_hash()を使用してパスワードを安全にハッシュし、そのハッシュを格納します。このqueryはデータを変更するため、変更を保存した後にdb.commit()を呼び出す必要があります。 If validation succeeds, insert the new user data into the database. For security, passwords should never be stored in the database directly. Instead, :func:`~werkzeug.security.generate_password_hash` is used to securely hash the password, and that hash is stored. Since this query modifies data, :meth:`db.commit() <sqlite3.Connection.commit>` needs to be called afterwards to save the changes.

  7. ユーザ情報を格納した後、ログインページへリダイレクトさせます。url_for()は、ログインのviewの名前(関数の名前)から対応するURLを生成します。これはURLを後から変更しても、そのURLにリンクしている全てのコードを変更させずにすますことが可能になるため、URLを(コードの中に)直接書き込むよりも好ましいやり方です。redirect()は、生成されたURLへリダイレクトさせるレスポンスを生成します。 After storing the user, they are redirected to the login page. :func:`url_for` generates the URL for the login view based on its name. This is preferable to writing the URL directly as it allows you to change the URL later without changing all code that links to it. :func:`redirect` generates a redirect response to the generated URL.

  8. もし検証が失敗した場合は、エラーをユーザへ示します。flash()は、テンプレートを変換(render)するときに取得可能なメッセージを格納します。 If validation fails, the error is shown to the user. :func:`flash` stores messages that can be retrieved when rendering the template.

  9. ユーザが最初にauth/registerへ進んだときは、または検証でのエラーがあったときには、登録formのあるHTMLページを表示すべきです。render_template()は、このチュートリアルの次のステップで作成する、登録formのあるHTMLを含んだテンプレートを変換します。 When the user initially navigates to ``auth/register``, or there was a validation error, an HTML page with the registration form should be shown. :func:`render_template` will render a template containing the HTML, which you'll write in the next step of the tutorial.

ログイン Login

このviewは上記のregisterのviewと同じパターンに従います。 This view follows the same pattern as the ``register`` view above.

flaskr/auth.py
@bp.route('/login', methods=('GET', 'POST'))
def login():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        db = get_db()
        error = None
        user = db.execute(
            'SELECT * FROM user WHERE username = ?', (username,)
        ).fetchone()

        if user is None:
            error = 'Incorrect username.'
        elif not check_password_hash(user['password'], password):
            error = 'Incorrect password.'

        if error is None:
            session.clear()
            session['user_id'] = user['id']
            return redirect(url_for('index'))

        flash(error)

    return render_template('auth/login.html')

registerのviewとは違うところが少しあります: There are a few differences from the ``register`` view:

  1. ユーザ情報は最初に問合せ(query)され、後で使用するために変数へ格納されます。 The user is queried first and stored in a variable for later use.

  2. check_password_hash()は提出(submit)されたパスワードを、格納されたハッシュと同じやり方でハッシュし、セキュリティに注意しながらそれらを比較します。もし一致した場合、パスワードは適正です。 :func:`~werkzeug.security.check_password_hash` hashes the submitted password in the same way as the stored hash and securely compares them. If they match, the password is valid.

  3. sessionは、リクエストを跨いで格納されるデータのdictです。検証が成功したときは、ユーザのidは新しいsessionに格納されます。そのデータはブラウザへ送信されるcookieに格納され、それからブラウザは以降のリクエストで(cookieを)送信し返します。Flaskはデータを改ざんできないようにするために、安全にデータを署名します :data:`session` is a :class:`dict` that stores data across requests. When validation succeeds, the user's ``id`` is stored in a new session. The data is stored in a *cookie* that is sent to the browser, and the browser then sends it back with subsequent requests. Flask securely *signs* the data so that it can't be tampered with.

この時点でユーザのidsessionへ格納され、それは以降のリクエストで利用可能になります。各リクエストの開始時に、ユーザがログインしていた場合にはユーザの情報は読み込まれて、他のviewからも利用可能にされるべきです。 Now that the user's ``id`` is stored in the :data:`session`, it will be available on subsequent requests. At the beginning of each request, if a user is logged in their information should be loaded and made available to other views.

flaskr/auth.py
@bp.before_app_request
def load_logged_in_user():
    user_id = session.get('user_id')

    if user_id is None:
        g.user = None
    else:
        g.user = get_db().execute(
            'SELECT * FROM user WHERE id = ?', (user_id,)
        ).fetchone()

bp.before_app_request()はどのURLがリクエストされたかに関わらず、viewの関数の前に実行する関数を登録します。load_logged_in_userは、ユーザidがsessionに格納されているかチェックし、それからデータベースからユーザのデータを取得し、それをリクエストの期間中は存続するg.userへ格納します。もしユーザidが(セッションに)ない場合、もしくはそのidが(データベースに)存在しない場合、g.userNoneになります。 :meth:`bp.before_app_request() <Blueprint.before_app_request>` registers a function that runs before the view function, no matter what URL is requested. ``load_logged_in_user`` checks if a user id is stored in the :data:`session` and gets that user's data from the database, storing it on :data:`g.user <g>`, which lasts for the length of the request. If there is no user id, or if the id doesn't exist, ``g.user`` will be ``None``.

ログアウト Logout

ログアウトするには、ユーザidをsessionから取り除く必要があります。そうするとload_logged_in_userは以降のリクエストでユーザ情報を読み込まなくなります。 To log out, you need to remove the user id from the :data:`session`. Then ``load_logged_in_user`` won't load a user on subsequent requests.

flaskr/auth.py
@bp.route('/logout')
def logout():
    session.clear()
    return redirect(url_for('index'))

他のviewでの認証の要求 Require Authentication in Other Views

ブログの投稿記事を作成、編集、削除するにはユーザがログインしている必要があります。デコレータ(decorator)を使用すると、decoratorを適用した各viewでログインをチェックできます。 Creating, editing, and deleting blog posts will require a user to be logged in. A *decorator* can be used to check this for each view it's applied to.

flaskr/auth.py
def login_required(view):
    @functools.wraps(view)
    def wrapped_view(**kwargs):
        if g.user is None:
            return redirect(url_for('auth.login'))

        return view(**kwargs)

    return wrapped_view

このdecoratorは適用した元のview(の関数)を包み込む(wrap)、新しいviewの関数を返します。その新しい関数はユーザ情報が読み込まれているかチェックして、読み込まれていない場合はログインページへリダイレクトします。もしユーザ情報が読み込まれている場合は、元のviewが呼び出されて通常どおりに続けます。ブログのviewを作成するとき、このdecoratorを使用していきます。 This decorator returns a new view function that wraps the original view it's applied to. The new function checks if a user is loaded and redirects to the login page otherwise. If a user is loaded the original view is called and continues normally. You'll use this decorator when writing the blog views.

エンドポイントとURL Endpoints and URLs

url_for()関数はviewの名前と引数に基づいて対応するURLを生成します。viewに関連付けられた名前はエンドポイントとも呼ばれ、標準設定ではviewの関数の名前と同じになります。(訳注: エンドポイントはview用の関数などを特定するもので、Flask実装上はdictionaryであるview_functions属性のキーになります。Flask実装では、アプリが使用するview用の関数などは全てview_functions属性に、エンドポイントをキーに関数本体を値にして登録されます。たいていは関数名とエンドポイントは同じですが異なる値がエンドポイントになる場合もあります。Flaskではroute()デコレータ使うと内部的にWerkzeugのRuleインスタンスを作成してURLのパターンとエンドポイントを対応付けます) The :func:`url_for` function generates the URL to a view based on a name and arguments. The name associated with a view is also called the *endpoint*, and by default it's the same as the name of the view function.

例えば、このチュートリアルの前の方でapp factoryに追加されたviewのhello()は、名前(訳注: エンドポイント、特に指定しなければ関数名と同じ)が'hello'であり、url_for('hello')を使ってリンクすることができます。もし引数を取る場合は、それは後で確認していきますが、それはurl_for('hello', who='World')を使用してリンクできるでしょう。 For example, the ``hello()`` view that was added to the app factory earlier in the tutorial has the name ``'hello'`` and can be linked to with ``url_for('hello')``. If it took an argument, which you'll see later, it would be linked to using ``url_for('hello', who='World')``.

blueprintを使用するときは、blueprintの名前が関数名の前に付けられ、従って先程書いたlogin関数のエンドポイントは、blueprintの'auth'にlogin関数を追加しているため'auth.login'になります。 When using a blueprint, the name of the blueprint is prepended to the name of the function, so the endpoint for the ``login`` function you wrote above is ``'auth.login'`` because you added it to the ``'auth'`` blueprint.

テンプレートへ続きます。 Continue to :doc:`templates`.