Python Django チュートリアル(6)

  • 25
    Like
  • 0
    Comment

勉強会用資料です
今回は本家のチュートリアルから完全にそれてbootstrapのことについて説明していきます.

チュートリアルのチュートリアル
チュートリアル1
チュートリアル2
チュートリアル3
チュートリアル4
チュートリアル5
他のチュートリアル

ソース(github)

Bootstrapを使おう

htmlのデザイン難しいですよね.
デザイナーがビシっと綺麗なhtmlを用意してくれたらいいですが,tutorialには残念ながらそんなもの望めません.
かと言って,綺麗なcssを素人が1から作っていくのは大変です.
そんなあなたにbootstrap!
bootstrapは無料(商用利用も可!)で使えるレスポンシブデザインのcss,javascript,font, iconなどを提供するフレームワークです.

レスポンシブデザインはブラウザの大きさに合わせて自動的に表示幅や配置などを調整してくれるデザインのことです.
PC,スマートフォン,タブレットで同じhtmlを使い回すことができます.

本家
とほほのBootstrap入門
Bootstrapとは?(wikipedia)

djangoではbootstrapとの連携用のライブラリもありますし,cssやjavascriptの配置の練習にちょうどいいので
tutorialで扱っていきたいと思います.

Bootstrapのdownloadと配置

ソース→6ce33b05

さっそく本家のページから一式をDownloadしてきましょう.

Kobito.2hc2qO.png

↓ Downloadボタンを押すと飛ばされます

Kobito.7n9C3e.png

今回は一番左の Bootstrap を使います.
bootstrap-3.3.6-dist.zip というzipファイルがdownloadできると思います.これを解凍しましょう.

Kobito.9C8A5v.png
3.3.6はこんなファイル構成になっています.

次に,tutorialのmanage.pyと同じ階層にstaticというフォルダを作り,そこに解凍したbootstrapの中身を全部放り込みます.
移動後のファイル構成は以下のようになっているはずです.

(tutorial)$ tree .
├── db.sqlite3
├── manage.py
├── polls
│   ├── __init__.py
│   ├── ...
│   └── views.py
├── requirements.txt
├── static  # 今回追加したディレクトリ
│   ├── css
│   │   ├── bootstrap-theme.css
│   │   ├── bootstrap-theme.css.map
│   │   ├── bootstrap-theme.min.css
│   │   ├── bootstrap-theme.min.css.map
│   │   ├── bootstrap.css
│   │   ├── bootstrap.css.map
│   │   ├── bootstrap.min.css
│   │   └── bootstrap.min.css.map
│   ├── fonts
│   │   ├── glyphicons-halflings-regular.eot
│   │   ├── glyphicons-halflings-regular.svg
│   │   ├── glyphicons-halflings-regular.ttf
│   │   ├── glyphicons-halflings-regular.woff
│   │   └── glyphicons-halflings-regular.woff2
│   └── js
│       ├── bootstrap.js
│       ├── bootstrap.min.js
│       └── npm.js
└── tutorial
    ├── __init__.py
    ├── ...
    └── settings.py

djangoの設定変更

ソース→73d94740a
公式ドキュメント(英語)
公式ドキュメント(日本語,ただしdjango1.4)

チュートリアルで作ってきた http://localhost:8000/polls/ のようなページはDBの内容によって表示される内容が変わります.
このようなページ(URL)は動的なページ,動的ページ,動的コンテンツなどと呼ばれます.
それに対し,bootstrap.cssのようなcssやjsファイルは同じURLにアクセスすると常に同じ内容が返って来ます.
このようなページは静的なページ,静的コンテンツ,静的ファイルなどと呼ばれます.
動的コンテンツはアクセス毎に内容が変化するため,プログラム(pythonだったりrubyだったりphpだったり)を実行し,
ページ内容を変更する必要があります.
これに対して静的コンテンツは内容がかわらないため,アクセス毎に問い合わせをする必要がなく,
ブラウザなどに内容がキャッシュされます.
キャッシュすることにより,読み込みが高速化され,サーバの負荷が軽減されますが,内容の変更が反映されないなどのデメリットもあります.

djangoでは画像ファイル,cssファイル,javascriptファイルなどの静的ファイルはstatic filesとして管理し,
運用時に一箇所にまとめることで管理しやすくしています.

一箇所にまとめる方法は $ ./manage.py collectstatic ですが,runserver で実行している間はこのコマンドを使う必要はありません.
deploy(サーバの設置)の説明もそのうちする予定なので,その時に詳しく説明します.

staticファイルは各アプリの下にあるstaticディレクトリの中がかき集められ,urls.pyにURLを追加することなく,
http://localhost:8000/static/ でアクセスすることができます.
本チュートリアルでは取り扱う予定はありませんが,polls独自のcssが必要な場合は
polls/static/polls/css/polls.css のように設置し,
http://localhost:8000/static/polls/css/polls.css のようにアクセスします.
ちなみにdjango管理サイトようの静的ファイルは django/contrib/admin/static/admin/以下にあります.

テンプレートの時も polls/templates/polls/ のようになっています.
templates/polls/static/polls/ のようにアプリ名を付けるのは冗長に感じるかもしれませんが,
名前衝突の回避や,コード的なメリットがありこのような構成になってるようです.

ただし,デフォルトで探索されるstaticディレクトリはアプリ(settings.pyの中のINSTALLED_APPS)の下にある
staticファイルのみです.
今回はmanage.pyと同じ階層にstaticディレクトリを作成したため,このディレクトリも読み込ませるように設定を追加する必要があります.
静的ファイルが置かれているディレクトリを追加するには,STATICFILES_DIRSに追加したいディレクトリをタプル(or リスト)で指定します.

Kobito.1pM05v.png
設定前は404になる.(諸事情で13000ポートで起動させてます)

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

STATIC_URL = '/static/'
STATICFILES_DIRS = (
    os.path.join(BASE_DIR, 'static'),
)

Kobito.6wqhns.png
設定後はcssが取得できる.

テンプレートの修正

ソース→63cdd0cc

template内から静的ファイルを読みこませるときは{% static %}というテンプレートタグを使います.
例えばbootstrap.cssを読み込むときは
<link rel="stylesheet" type="text/css" href="{% static 'css/bootstrap.css' %}" />
のように書きます.
{% static %} タグを使うには最初に {% load staticfiles %} と書く必要があります.

手始めに一覧ページでbootstrap.cssを読み込ませてみます.

polls/templates/polls/index.html
{% load staticfiles %}  <!-- ← 追加 -->
<html>
  <head> <!-- ← 追加 -->
    <link rel="stylesheet" type="text/css" href="{% static 'css/bootstrap.css' %}" /> <!-- ← 追加 -->
  </head> <!-- ← 追加 -->
<body>
  <table border="1">
    <tr>
      <th>質問内容</th>
      <th>公開日</th>
      <th></th>
    </tr>
    {% for question in questions %}
    <tr>
      <td>{{ question.question_text }}</td>
      <td>{{ question.pub_date }}</td>
      <td><a href="{% url 'polls:detail' question.pk %}">詳細画面へ</a></td>
    </tr>
    {% endfor %}
  </table>
</body>
</html>

Kobito.mXJ2pb.png
before

Kobito.aIXL4h.png
after

なんかちっさくなっただけでcssの恩恵を感じませんね.
cssはhtmlのタグにclassを指定することで真価を発揮します.

とりあえず試しにテーブル用のcssを当ててみましょう.
bootstrapのページによれば,tableクラスをつければtable用のデザインがあたるようです.
ついでにstripedも当ててみます.

polls/templates/polls/index.html
...
<body>
  <table border="1" class="table table-striped">
    <tr>
      <th>質問内容</th>
...

Kobito.RIiscq.png

はい.このようにtableタグにclassを追加するだけで,簡単にそれっぽいデザインになりました.

テンプレートの拡張

公式ドキュメント(英語)
公式ドキュメント(日本語,ただしdjango1.4)

すでに使用しているテンプレートですが,継承してこそ真価を発揮します.
qiita, bootstrapドキュメント, djangoドキュメント,どこでもいいですが,どのサイトでも
ページを移動した時のヘッダやフッタは基本的に同じで,ページの中身だけが変化しているでしょう?
サイト内の統一性を考えれば当然ですが,同一サイト内では枠組みは同じで,一部分のみが変化します.
クラス汎用ビューを使ったときと同じように,ベースのテンプレートを作り,そのテンプレートを継承して
変化させたい部分だけをオーバーライドすることでコード量を減らし,変更に強いサイトを作ることができるようになります.

本節ではベースとなるテンプレートを作成し,チュートリアルで作成したテンプレートを置き換え,
簡単にbootstrapのデザインを適用させる準備をします.

ベーステンプレートの作成

ソース→741a853886

何はともあれ,ベースとなるテンプレートを用意しましょう.
staticファイルの時もそうでしたが,templateの場合もtemplatesディレクトリをmanage.pyと同じ階層に用意し,
それをベースのテンプレート置き場とします.

dashboard など,如何にもベースになり得そうなアプリがある場合はそこに置くのもありだと思います.

templateの場合もstaticの時と同じく,デフォルトではアプリの下にあるtemplatesディレクトリしか見てくれません.
staticの時と同じようにsettings.pyを変更しましょう.
templateの場合はTEMPLATESの中のDIRSに追加したいディレクトリを記述します.

tutorial/settings.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',
            ],
        },
    },
]

次にtemplatesの中にbase.htmlを作りましょう.
肝心の中身ですが,bootstrapのstarter-templateを使うことにします.
中身の修正が必要ですが,とりあえず同じものをベタッと貼ってそれをbase.htmlとして保存しましょう.

継承の前準備とベースの修正

ソース→2cca1ce20eb

とりあえず見ながら修正するようにしたいので,
polls/templates/polls/index.htmlbase.htmlを継承するように修正しましょう.
やることは簡単で,頭に{% extends "base.html" %}と書くだけです.

polls/templates/polls/index.html
{% extends "base.html" %}
{% load staticfiles %}
<html>
...

この状態でhttp://localhost:8000/polls/ を見ると以下のようになっているはずです.

Kobito.VImUOU.png

はい,ぐちゃぐちゃですね.
cssのパスがちゃんとあたってないからこうなってます.

Kobito.PWaw7X.png

とりあえずcssやjsのパスを直して表示させたのがこちら.
htmlの修正はdjangoとはあまり関係ないのでさっくり飛ばします.ソースで確認してください.

変更用blockの追加と上書き

ソース→8a3fe39358

やっと本筋です.
今画面に出ている「Bootstrap starter template」の部分に質問一覧を表示させるようにしてみましょう.
htmlの<div class="container">の中身がこの部分に相当するので,ここを消去し,
継承先で上書きできるようにします.

templates/base.html(修正前)
    <div class="container">

      <div class="starter-template">
        <h1>Bootstrap starter template</h1>
        <p class="lead">Use this document as a way to quickly start any new project.<br> All you get is this text and a mostly barebones HTML document.</p>
      </div>

    </div><!-- /.container -->

templates/base.html(修正後)
    <div class="container">
    {% block contents %}{% endblock %}
    </div><!-- /.container -->

中身をごっそり消して{% block %}タグを用意します.
名前はなんでもいいですが,ここではcontentsとしています.

続いて継承先のほうのテンプレートを修正します.

polls/templates/polls/index.html(修正前)
{% extends "base.html" %}
{% load staticfiles %}
<html>
  <head>
    <link rel="stylesheet" type="text/css" href="{% static 'css/bootstrap.css' %}" />
  </head>
<body>
  <table border="1" class="table table-striped">
    <tr>
      <th>質問内容</th>
      <th>公開日</th>
      <th></th>
    </tr>
    {% for question in questions %}
    <tr>
      <td>{{ question.question_text }}</td>
      <td>{{ question.pub_date }}</td>
      <td><a href="{% url 'polls:detail' question.pk %}">詳細画面へ</a></td>
    </tr>
    {% endfor %}
  </table>
</body>
</html>

polls/templates/polls/index.html(修正後)
{% extends "base.html" %}

{% block contents %}
  <table border="1" class="table table-striped">
    <tr>
      <th>質問内容</th>
      <th>公開日</th>
      <th></th>
    </tr>
    {% for question in questions %}
    <tr>
      <td>{{ question.question_text }}</td>
      <td>{{ question.pub_date }}</td>
      <td><a href="{% url 'polls:detail' question.pk %}">詳細画面へ</a></td>
    </tr>
    {% endfor %}
  </table>
{% endblock contents %}

htmlタグ,headタグ,bodyタグは継承元(templates/base.html)で設定しているので必要ありません.
<table>タグの中身をcontents内に配置したいので,
{% block contents %}を頭に,{% endblock contents %}を最後につけています.
{% endblock %}には引数は必要ありませんが,blockの中身が大きくなるとどのblockを閉じたか分からなくなるので
{% block %}{% endblock %}が離れている場合はなるべく書くようにしたほうがいいでしょう.

ちなみに{% endblock content %}のように{% block contents %}と名前が対になっていない場合はエラーになります.

さて,どうなったかブラウザで確認してみましょう.

Kobito.OTzMq7.png

テーブルがヘッダにくっついちゃってますが,まぁ及第点ではないでしょうか.

base.htmlの修正とpolls/base.htmlの追加

ソース→e722778ee

せっかくTOPページが出来たので http://localhost:8000/でアクセスできるように
tutorial/urls.pyを編集しておきます.
と言っても,実際にTOPページ用のviewがあるわけではないのでとりあえずpolls/と同じ内容にしておきます.
名前はindexにしてますが,tophomeなど好みの名前をつけてください.

tutorial/urls.py
from django.conf.urls import include, url
from django.contrib import admin

from polls.views import index  # ← 追加

urlpatterns = [
    url(r'^admin/', include(admin.site.urls)),
    url(r'^polls/', include('polls.urls', namespace='polls')),
    url(r'^$', index, name='index'),  # ← 追加
]

で,base.htmlの文言やリンクを修正していきます.

templates/base.html
    <nav class="navbar navbar-inverse navbar-fixed-top">
      <div class="container">
        <div class="navbar-header">
        ...
          <a class="navbar-brand" href="{% url 'index' %}">Tutorial</a>
        </div>
        <div id="navbar" class="collapse navbar-collapse">
          <ul class="nav navbar-nav">
            <li class="active"><a href="{% url 'polls:index' %}">polls</a></li>
            <li class=""><a href="{% url 'admin:index' %}">admin</a></li>
          </ul>
        </div><!--/.nav-collapse -->
      </div>
    </nav>

ここでちょっと一工夫.<div id="navbar">内のpollsの<li>class="active"がついています.
activeがつくと,ヘッダが以下のようにハイライトされ,どこのページにいるかわかりやすくなります.
Kobito.fUGtFd.png

polls内のtemplateでは常にactiveを付けたいですが,他のアプリの場合はハイライトさせたくありません.
そこでこの部分を{% block %}にし,書き換えれるようにします.

templates/base.html
<li class="{% block nav_polls %}{% endblock %}"><a href="{% url 'polls:index' %}">polls</a></li>

ただし,このままではpolls/templates/polls内のテンプレート全てを変更しないといけなくなります.
そこで,polls/templats/polls/base.htmlを作成し,polls/templats/polls下のテンプレートでは
polls/base.htmlを継承するようにします.

base.html
   └ polls/base.html
       ├ polls/index.html
       ├ polls/detail.html
       └ polls/results.html
polls/templates/polls/base.html
{% extends "base.html" %}

{% block nav_polls %}active{% endblock %}

nav_pollsにactiveを付ける

polls/templtes/polls/index.html
{% extends "polls/base.html" %}

base.htmlではなくpolls/base.htmlを継承する

ついでにdetail.htmlresults.htmlも継承するように変更しておきましょう.
直し方はindex.htmlの時と同じく,一行目に{% extends "polls/base.html" %}をつけ,
中身を{% block contents %}で囲むだけです.

Kobito.pxgSZ5.png

TOPページ

Kobito.YlkGRA.png

選択ページ

Kobito.VjLnSb.png

結果ページ

Django Bootstrap

ここまではdjangoのtemplate機能を使って自分でbootstrapのcssを充てる作業だったので
あまり恩恵を感じなかったと思います.
本章ではdjangoのデザインを簡単に置き換えることができるdjango-bootstrapを紹介します.

https://github.com/dyve/django-bootstrap3
http://django-bootstrap3.readthedocs.org/en/latest/index.html

導入とFormの置き換え

ソース→498065d9fff

ソースをcheckoutsして試す場合は pip install -r requirements.txt を実行してください.

pipで簡単に入ります.

bootstrapのバージョンの関係で,名前はdjango-bootstrap3になります.
"3"のつけ忘れに注意.

(tutorial)$ pip install django-bootstrap3
Collecting django-bootstrap3
  Downloading django-bootstrap3-6.2.2.tar.gz
Building wheels for collected packages: django-bootstrap3
  Running setup.py bdist_wheel for django-bootstrap3 ... done
...  

Successfully built django-bootstrap3
Installing collected packages: django-bootstrap3
Successfully installed django-bootstrap3-6.2.2

次にtutorial/settings.pyを開き,INSTALLED_APPSにbootstrap3を追加します.

tutorial/settings.py
INSTALLED_APPS = (
    'django.contrib.admin',
...
    'django.contrib.staticfiles',
    'bootstrap3',  # ← 追加
    'polls',
)

インストール作業はこれで終わりです.
さっそくFormを書き換えてみましょう.
今回はpolls/templates/polls/detail.htmlで出力しているformを書き換えてみます.

{{ form }}でformを出力する代わりに,{% bootstrap_form %}テンプレートタグを使ってformを出力させます.
使用前にbootstrap3をloadしておく必要があります.

polls/templates/polls/detail.html
{% extends "polls/base.html" %}
{% load bootstrap3 %}  <!-- ← 追加 -->

{% block contents %}
<h1>{{ question.question_text }}</h1>

<form action="" method="post">
  {% csrf_token %}
  {% bootstrap_form form %} <!-- ← 修正 -->
  <input type="submit" value="Vote" />
</form>
{% endblock contents %}

1行の追加,1行の修正です.
画面上でどうなったか確認してみましょう.

Kobito.EUxhO5.png

無駄についてた黒ポチが消えました.
なにも選択せずにVoteボタンを押してみてください.

Kobito.d0ide3.png

こんな感じで,「いかにも」なエラーチェックが自動で付きます.

Kobito.0QI9Rr.png

選択肢にない値を送信するとこんな感じになります.

builtin設定

ソース→1f19af40

さて,たった2行書くだけでFormが見違えるように綺麗になりました.
が,いちいち{% load bootstrap3 %} を書くには面倒くさいですよね.
bootstrap3用のテンプレートタグを自動で読み込むようにsettings.pyに記述しましょう.

tutorial/settings.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',
            ],
            'builtins': [
                'bootstrap3.templatetags.bootstrap3',  # ←←← これを追加
            ],
        },
    },
]

ちなみに,ここに書けるのはdjango1.9からです.
django1.8以前は django.template.base.add_to_builtins メソッドを使うことで同じことができます.
{% load %}タグの時はbootstrap3だけでしたが,builtinsで指定する場合はbootstrap3.templatetags.bootstrap3
のようにtemplatetagsまでのPATHも含めないと行けないので注意してください.

polls/templates/polls/detail.htmlからloadの記述を消してエラーが出ないことを確かめましょう.

message表示

ソース→a2d08a455

今は選択肢を選んでVoteすると結果ページに飛ぶだけの味気ないものになっています.
結果ページにユーザが何を投票したのか出力してみましょう.

djangoドキュメント(英語)
djangoドキュメント(日本語)

djangoでは,次のページへなんらかの情報を持ちまわる仕組みをmessagesとして提供しています.
使用するにはsettings.pyのINSTALLED_APPSにdjango.contrib.messagesが含まれてる必要があります.

adminなどと同じようにデフォルトで入ってるのでsettingsの修正は必要ないです.

まずはメッセージを追加しましょう.
今回は投票した時に,どの選択をしたかを出したいので,polls/views.pyform_validを編集します.
メッセージの追加方法は messages.success関数にrequestと表示したいメッセージを渡すだけです.

successの他に,debuginfowarningerrorがあります.

polls/views.py
from django.contrib import messages
...

class Detail(SingleObjectMixin, FormView):
...
    def form_valid(self, form):
        form.vote()
        choice = form.cleaned_data['choice']
        messages.success(self.request, '"%s"に投票しました' % choice)
        return super().form_valid(form)

voteの戻り値でメッセージ(もしくはchoiceのインスタンス)を返すようにするともう少しスムーズになります.

続いて表示です.
メッセージの表示は全部のページで行いたいので,base.htmlに追加します.
django-bootstap3ではメッセージの表示もサポートしているので,これも簡単にかけます.

templates/base.html
...
    <div class="container">
    {% if messages %}
    {% bootstrap_messages messages %}
    {% endif %}
    {% block contents %}{% endblock %}
    </div><!-- /.container -->
...

Kobito.joC23A.png

こんな感じで,メッセージが表示されるようになりました.
右側にある x ボタンを押すとこのメッセージは消えます.



次のチュートリアルではテストについて説明していきます(本家チュートリアル5相当です)
次のチュートリアルへ

他のチュートリアル