GIG

赴くままに技術を。

Django1.9+Bootstrap3でログイン表示を作る

認証・認可は、Djangoに限らず、Webフレームワークを使い出してまず外せない機能。

Django公式マニュアルを見てみると、機能としてデフォルトで持っていて、それを用途に応じて拡張していく方針とのこと。 Users, Groupモデル、パスワードをハッシュ化して保持して保持するといった提供されている。

1回やり方を確認しておけば何にでも応用できそうなのででBootstrap3を利用し、ログイン画面を表示してみる。

環境

環境としては、こちら。

項目 バージョン
Python 3.5.1
Django 1.9.5
jQuery v2.2.2
Bootstrap v3.3.6

Djangoの認証

Djangoで認証・認可の機能は、django.contrib.authで使用できる。これはプロジェクトを生成するときに元から含まれている。プロジェクト作成後、マイグレーション(python manage.py migrate)とすると認証・認可用のテーブルが作成される。

つくってみる

はじめにプロジェクトから作成する。 radishはただのプロジェクトの例なので、適宜変更してください。

$ django-admin startproject radish
$ python manage.py migrate

次にaccountsdashboardアプリを作成する。 accountsには認証・認可機能を含めで、dashboardには認証された後のリダイレクト先のアプリを含める想定。

$ python manage.py startapp accounts
$ python manage.py startapp dashboard

アプリを作ったら、忘れずsettings.pyINSTALLED_APPSに追記しておく。これを忘れると、後で出てくるテンンプレートが存在しないというエラーが発生するので、忘れずに。

  • radish/radish/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    # local
    'accounts.apps.AccountsConfig',
    'dashboard.apps.DashboardConfig',
]
Bootstrapを配置する

JavascriptCSSのライブラリは、radish/staticフォルダ内に入れておく。するとsettings.pyの下記の記載に沿って、参照可能となる。

  • radish/radish/settings.py
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.9/howto/static-files/

STATIC_URL = '/static/'

STATICFILES_DIRS = (
    os.path.join(BASE_DIR, "static"),
)

jQueryやらBootstrapやらをインストールする。それぞれダウンロードするのが面倒であったので、Bowerで入れる。

  • radish/static/
$ bower init
...
(対話型でbower.jsonを作成)
...
$ bower install jquery -S
$ bower install bootstrap -S
トップ画面、ログイン画面、ログイン後画面(ダッシュボード画面)を作る

各画面共通で使うNavbarなどはbase.htmlに記述し、他の画面はそれを継承する。またそういう共通のHTMLはradish/templates配下に入れておく。しかし、このパスはDjangoが見つけてくれないので、またsettigs.pyに追記する。

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],  # 追記箇所
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

それではbase.html{% load staticfiles %}と書くと、下のように{% static xxx%}でjsやcssを利用できる。{% if user.is_authenticated %}~{% else %}で条件分岐しているところは、認証されている場合のみ有効にするメニュー表示である。また {% block container %}~{% endblock %}は、base.htmlを継承した方のHTMLが埋め込まれる箇所となっている。

{% load staticfiles %}

<!DOCTYPE html>
  <html lang="ja">
    <head>
      <meta charset="utf-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1">

      <tile>{% block title %} Radish | {{ site_name }}{% endblock %}</tile>

      <!-- CSS -->
      <link href="{% static 'bootstrap/dist/css/bootstrap.min.css' %}" rel="stylesheet" />
      <link href="{% static 'bootstrap/dist/css/bootstrap-theme.min.css' %}" rel="stylesheet" />
      <link href="{% static 'font-awesome/css/font-awesome.css' %}" rel="stylesheet" />
  </head>
  <body>
    <nav class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
          <div class="navbar-header">
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
              <span class="sr-only">Toggle navigation</span>
              <span class="icon-bar"></span>
              <sapn class="icon-bar"></sapn>
              <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="#">Radish</a>
          </div><!-- /.navbar-header -->

          <div id="navbar" class="navbar-collapse collapse">
            {% if user.is_authenticated %}
              <ul class="nav navbar-nav navbar-right">
                  <li>
                      <a href="#">Dashboard</a>
                  </li>
                  <li>
                      <a href="#">Settings</a>
                  </li>
              </ul>
            {% else %}
              <form class="navbar-form navbar-right">
                  <button type="button" class="btn btn-primary"
                    onclick="location.href=&quot;{% url 'login' %}&quot;;">
                    Login</button>
              </form>
            {% endif %}
          </div><!-- /.navbar-collapse -->
        </div><!-- /.container -->
    </nav>

    {% block container %}
    {% endblock %}

    <hr/>
    <footer>
      <p>&copy; hermesian</p>
    </footer>

    <!-- JavaScript -->
    <script src="{% static 'jquery/dist/jquery.min.js' %}"></script>
    <script src="{% static 'bootstrap/dist/js/bootstrap.min.js' %}"></script>
  </body>
</html>

次にトップ画面home.htmlは下記。 これはBootstrapのjumbotronの例をそのまま利用。

{% extends 'base.html' %}

{% load staticfiles %}

{% block container %}
<!-- Main jumbotron for a primary marketing message or call to action -->
<div class="jumbotron">
    <div class="container">
      <h1>Radish</h1>
      <p>
          "Radish is a dashboard for collecting visualizing report."
      </p>
    </div>
</div>
<div class="container">
      <!-- Example row of columns -->
      <div class="row">
        <div class="col-md-4">
          <h2>Heading</h2>
          <p>Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui. </p>
          <p><a class="btn btn-default" href="#" role="button">View details »</a></p>
        </div>
        <div class="col-md-4">
          <h2>Heading</h2>
          <p>Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui. </p>
          <p><a class="btn btn-default" href="#" role="button">View details »</a></p>
       </div>
        <div class="col-md-4">
          <h2>Heading</h2>
          <p>Donec sed odio dui. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Vestibulum id ligula porta felis euismod semper. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.</p>
          <p><a class="btn btn-default" href="#" role="button">View details »</a></p>
        </div>
      </div>
  </div>
{% endblock %}

home.htmlのログインボタンで遷移する先は、accounts/tempalte/accounts/login.html<form>タグの下に{% csrf_token %}とあるのは、DjangoCSRF対策機能である。

{% extends 'base.html' %}

{% load staticfiles %}

{% block container %}
<div class="container">
  <div class="page-header">
      <h3>
        Login
      </h3>
  </div>
  <div class="row">
    <div class="col-md-4 col-md-offset-4">
        <div class="panel panel-default">
            <div class="panel-body">
                <form accept-charset="utf-8" role="form" action="{% url 'login' %}" method="post">
                    {% csrf_token %}
                    <fieldset>
                        <div class="form-group">
                            <input type="text" id="username" name="username" class="form-control"
                                   placeholder="Username" required autofocus>
                        </div>
                        <div class="form-group">
                            <input type="password" id="password" name="password" class="form-control"
                                   placeholder="Password" required>
                        </div>
                        {% if login_failed %}
                          <p class="text-danger">Sorry, that login was invalid.  Please try again.</p>
                        {% endif %}
                        <input class="btn btn-lg btn-success btn-block" type="submit" value="Log in">
                    </fieldset>
                </form>
            </div>
        </div>
    </div>
</div>
{% endblock %}

最後に認証後のdashboard画面dashboard/template/dashboard.html。といっても今回は空っぽ。

{% extends 'base.html' %}
ビューとルーティングを作る

accountsアプリ

  • radish/accounts/views.py

何かしらで認証されっぱなしの状態にならないように、logoutを最初に呼んで初期化してる。

import logging
from django.shortcuts import render_to_response, HttpResponseRedirect
from django.template import RequestContext
from django.contrib.auth import authenticate, login, logout

logger = logging.getLogger(__name__)


def login_view(request):

    #強制的にログアウト
    logout(request)
    username = password = ''

    login_failed = False

    if request.POST:
        username = request.POST['username'].replace(' ', '').lower()
        password = request.POST['password']
        user = authenticate(username=username, password=password)
        if user is not None:
            if user.is_active:
                login(request, user)
                return HttpResponseRedirect('/dashboard')
        else:
            login_failed = True

    return render_to_response('accounts/login.html',
                              {'login_failed': login_failed},
                              context_instance=RequestContext(request))

上で作ったビューをwebブラウザから/accounts/loginでアクセスできるよう設定する。

  • radish/accounts/urls.py
from django.conf.urls import url
from .views import login_view

urlpatterns = [
    url(r'^login/$', login_view, name='login'),
]

dashboardアプリ

  • radish/dashboard/views.py

@login_requiredをつけて、認証後のみアクセス可能なようにしている。

import logging

from django.shortcuts import render
from django.contrib.auth.decorators import login_required


@login_required
def index(request):
    return render(request, 'dashboard/index.html')
  • radish/dashboard/urls.py
from django.conf.urls import url
from .views import index

urlpatterns = [
    url(r'^$', index, name='dashboard'),
]
動作確認

ユーザを作成する。今回はスーパーユーザを造り、そのままそのアカウントでログインしてみる。

$ python manage.py createsuperuser
...
(対話的に作成)
...

動作確認として、開発サーバを起動$ python manage.py runserver、webブラウザからhttp://localhost:8000/にアクセスして、先ほど登録したスーパーユーザでログインし、NavbarにDashboard, Settingsというメニューが出ていることを確認する。