Django入門
- Django とは
 - インストール
 - SQLite3をバージョンアップする
 - プロジェクトを作成する
 - 簡易サーバを起動する
 - アプリケーションを作成する
 - アプリケーションディレクトリを集約する
 - モデルを作成する
 - 管理者サイトを使用する
 - テンプレートを使用する
 - テンプレートで共通なレイアウトページを参照する
 - スタティックファイルを読み込む
 - ヘッダやメニューバーを表示する
 - 詳細画面
 - 編集画面
 - 多言語対応
 - 言語設定画面を用意する
 - MariaDB と接続する
 - Apache+mod_wsgiで動かす
 
Django とは
- Python ベースでは最も有名な Webアプリケーションフレームワーク。
 - ライセンスは BSD License。商用利用可能。
 - MVC(Model-View-Controller)モデルをサポートする。
 - 現在(2019年4月)の最新版は 2.2.0。
 - バージョン 2.0 からは Python 3.5 以上のみに対応する。
 - 1.8, 1.11, 2.2 は LTS(Long Term Support) と呼ばれ、3年間サポートされる。
 - 他のバージョンは 1年4ヶ月程度でサポートが切れる。
 - URLディスパッチャ、O/Rマッパー、ビューシステム、テンプレートエンジン等の機能を含む。
 - データベースとして SQLite, MySQL, PostgreSQL, Oracle に対応している。
 - 開発評価版の簡易 HTTP サーバが同梱されている。
 - プロダクト利用では、Apache+mod_python、または、Nginx+uWSGI で動作させることが多い。
 
インストール
CentOS 7 に Django 2.0 をインストールします。
# yum -y install https://centos7.iuscommunity.org/ius-release.rpm # yum -y install python36u python36u-pip # pip3.6 install --upgrade pip # pip3.6 install Django==2.2 # django-admin --version
SQLite3をバージョンアップする
データベースに SQLite を使用する場合、Django 2.2 は SQLite のバージョンが古いと、「django.core.exceptions.ImproperlyConfigured: SQLite 3.8.3 or later is required (found 3.7.17).」の様なエラーとなるので、SQLite を最新版にバージョンアップします。
# yum install -y wget gcc make # wget https://www.sqlite.org/2019/sqlite-autoconf-3290000.tar.gz # tar zxvf ./sqlite-autoconf-3290000.tar.gz # cd ./sqlite-autoconf-3290000 # ./configure --prefix=/usr/local # make # make install # cd .. # rm -rf ./sqlite-autoconf-3290000 ./sqlite-autoconf-3290000.tar.gz # mv /usr/bin/sqlite3 /usr/bin/sqlite3_old # ln -s /usr/local/bin/sqlite3 /usr/bin/sqlite3 # echo 'export LD_LIBRARY_PATH="/usr/local/lib"' >> ~/.bashrc # source ~/.bashrc
プロジェクトを作成する
まず、Django プロジェクトを作成します。Django では、コンフィグディレクトリの名前もプロジェクト名と同じになってしまうため、一度、config という名前でプロジェクトを作成し、その後、ディレクトリ名を変更するのがおすすめです。
$ django-admin startproject config $ mv ./config ./myproj $ cd ./myproj
下記のファイルが作成されます。
./myproj ./myproj/manage.py ./myproj/config ./myproj/config/__init__.py ./myproj/config/settings.py ./myproj/config/urls.py ./myproj/config/wsgi.py
簡易サーバを起動する
外部のクライアントから接続すると 「Invalid HTTP_HOST header: '192.168.xx.xx'. You may need to add '192.168.56.102' to ALLOWED_HOSTS.」 といったエラーとなることがあります。./config/settings.py に接続を許可するホストの情報を設定する必要があります。開発時はとりあえず、すべて('*')を指定していてもよいですが、本番移行の際にはセキュリティ確保のため、HTTP の Host ヘッダで送信されてくる IPアドレスや URL を指定してください。
ALLOWED_HOSTS = ['*']
開発用の簡易サーバを起動します。LISTEN する IPアドレスとポート番号を指定することができます。
$ python3.6 manage.py runserver 0.0.0.0:80
80番ポート番号が解放されていない場合は、例えば下記の様にして開放する必要があります。
# yum -y install firewalld # systemctl enable firewalld # systemctl start firewalld # firewall-cmd --add-port 80/tcp --permanent # firewall-cmd --reload
ブラウザから http://サーバアドレス/ にアクセスすることで、Django のサンプルページが表示されれば成功です。
アプリケーションを作成する
books アプリケーションを作成します。
$ python3.6 manage.py startapp books
下記のファイルが作成されます。
./books/__init__.py ./books/admin.py ./books/apps.py ./books/models.py ./books/tests.py ./books/views.py ./books/migrations/__init__.py
./books/apps.py に定義されたクラス名をアプリケーションとして ./config/settings.py の INSTALLED_APPS に登録します。
INSTALLED_APPS = [
    'books.apps.BooksConfig',
    'django.contrib.admin',
    'django.contrib.auth',
./config/urls.py に、http://サーバアドレス/books/ が要求されたら、./books/urls.py を参照するように指定します。
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
    path('admin/', admin.site.urls),
    path('books/', include('books.urls')),
]
./books/urls.py ファイルを新規に作成し、http://サーバアドレス/books/ の次に何もなければ、view.py の list_books 関数を呼び出すように指定します。
from django.urls import path
from . import views
urlpatterns = [
    path('', views.list_books, name='list_books'),
]
./books/views.py ビューを下記の様に修正します。
from django.http import HttpResponse
def list_books(request):
    return HttpResponse("Hello world!")
ブラウザで http://サーバアドレス/books/ にアクセスして Hello world! が表示されれば成功です。
アプリケーションディレクトリを集約する
Django の標準では BASE_DIR(./) 直下にアプリケーションディレクトリが乱雑に並んでしまうため、アプリケーションを集約して格納するための ./apps ディレクトリを用意します。
$ mkdir ./apps
./apps ディレクトリを ./config/settings.py に登録します。
import os import sys BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, os.path.join(BASE_DIR, 'apps'))
アプリケーションディレクトリを ./apps 配下に移動します。
$ mv ./books ./apps
ブラウザで http://サーバアドレス/books/ にアクセスして Hello world! が表示されれば成功です。
モデルを作成する
本(Book)を管理するモデルを作成します。本(Book)は、管理番号(book_id)、タイトル(title)、著者(author)の属性を持つものとします。
from django.db import models
class Book(models.Model):
    book_id = models.CharField(max_length=32)
    title = models.CharField(max_length=256)
    author = models.CharField(max_length=256)
    def __str__(self):
        return self.title
DB に対するマイグレーションを作成し、マイグレーションを実行します。これにより、モデルで定義したテーブルやカラムが自動的に作成されます。テーブルやカラムを変更して再度マイグレーションを行うことで、テーブル追加やカラム追加がマイグレーションされます。
$ python3.6 manage.py makemigrations $ python3.6 manage.py migrate
作成されたテーブルは次のようにして確認することができます。
$ python3.6 manage.py dbshell sqlite> .tables auth_group books_book auth_group_permissions django_admin_log auth_permission django_content_type auth_user django_migrations auth_user_groups django_session auth_user_user_permissions sqlite> .schema books_book CREATE TABLE IF NOT EXISTS "books_book" (...); sqlite> select * from books_book; sqlite> (Ctrl-D)
管理者サイトを使用する
Django は簡易的な管理サイト機能を標準で装備しています。管理者サイトからモデルで定義した DB に対して簡単な追加・一覧・更新・削除を行うことができます。まず、管理者ユーザを作成します。
$ python3.6 manage.py createsuperuser Username (leave blank to use 'root'): admin Email address: admin@example.com Password: ***password*** Password (again): ***password*** Superuser created successfully.
Books アプリケーションの Book モデルを管理者サイトで管理できるようにします。
from django.contrib import admin from .models import Book admin.site.register(Book)
ブラウザから http://サーバアドレス/admin/ にアクセスすることで、上記で作成した管理者ユーザで管理者サイトにログインすることができます。Groups と Users のみ表示され、Books が表示されない場合は、「manage.py runserver」を再起動してみてください。管理者サイトから、Book の情報を何冊が登録してみましょう。
テンプレートを使用する
HTMLテンプレートを使用して Book の一覧をテーブル表示してみます。まず、テンプレートディレクトリを作成します。
$ mkdir ./templates
./templates を ./config/settings.py に登録します。
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        'APP_DIRS': True,
           :
    },
]
Books のためのテンプレートディレクトリを作成します。
$ mkdir ./templates/books
./templates/books の下に list_books.html テンプレートファイルを作成します。
<table>
  <thead>
    <tr>
      <th>Book ID</th>
      <th>Title</th>
      <th>Author</th>
      <th>Action</th>
    </tr>
  </thead>
  <tbody>
    {% if books %}
      {% for book in books %}
        <tr>
          <td><a href="/books/{{ book.book_id }}">{{ book.book_id }}</a></td>
          <td>{{ book.title }}</a></td>
          <td>{{ book.author }}</a></td>
          <td><a href="/books/{{ book.book_id }}/edit">[Edit]</a></td>
        </tr>
      {% endfor %}
    {% else %}
      <tr>
        <td colspan=4>No books.</td>
      </tr>
    {% endif %}
  </tbody>
</table>
./apps/books/views.py ファイルを下記の様に修正します。
from django.http import HttpResponse
from django.template import loader
from .models import Book
def list_books(request):
    books = Book.objects.all()
    context = {
        'title': 'List Books',
        'books': books,
    }
    template = loader.get_template('books/list_books.html')
    return HttpResponse(template.render(context, request))
http://サーバアドレス/books/ にアクセスして、管理者サイトで登録した本の一覧が表示されれば成功です。
テンプレートで共通なレイアウトページを参照する
{% block %} や extends を用いて、複数のテンプレートで共有するレイアウトを作成することができます。子テンプレート(list_books.html)で定義した title や content というブロックを、親テンプレート(layout.html)が参照して表示します。まず、./templates/layout.html を新規作成します。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{{ title }}</title>
</head>
<body>
  <h1>{{ title }}</h1>
  {% block content %}{% endblock %}
</body>
</html>
テンプレートファイルに下記を追記します。
{% extends 'layout.html' %}
{% block content %}
<table>
  :
</table>
{% endblock %}
http://サーバアドレス/books/ にアクセスして、「List Books」のタイトルが表示されれば成功です。
スタティックファイルを読み込む
CSS や JavaScript などのスタティックファイルを格納するスタティックディレクトリ(./static)を作成します。
$ mkdir ./static $ mkdir ./static/css ./static/js ./static/img
./static を ./config/settings.py に登録します。
    :
STATIC_URL = '/static/'
STATICFILES_DIRS = (
    os.path.join(BASE_DIR, 'static'),
)
./static/css/style.css ファイルを作成します。
table { border-collapse: collapse; margin-bottom: .5rem; }
table th, table td { border: 1px solid #999; padding: .1rem .3rem; }
table th { background-color: #ddd; }
button { line-height: 1.2rem; min-width: 6rem; }
レイアウトファイルから style.css を読み込ませます。
<head>
<meta charset="utf-8">
<title>{{ title }}</title>
<link rel="stylesheet" href="/static/css/style.css">
</head>
http://サーバアドレス/books/ にアクセスして、表の枠線が表示されたら成功です。うまく表示されない場合は、「manage.py runserver」を再起動したり、ブラウザのキャッシュをクリアしてみましょう。
ヘッダやメニューバーを表示する
レイアウトファイルにヘッダやメニューバーを追加します。
<body>
  <div class="header-block">
    @ Django Sample
  </div>
  <div class="menu-block">
    <a href="/books/">Book</a>
    <a href="/settings/">Settings</a>
  </div>
  <div class="content-block">
    <h1>{{ title }}</h1>
    {% block content %}{% endblock %}
  </div>
</body>
ヘッダやメニューのためのスタイルを追記します。
* { margin: 0; padding: 0; }
.header-block { background-color: #000; color: #fff; line-height: 2rem;
    font-weight: bold; padding: 0 .5rem; }
.menu-block { background-color: #ddd; line-height: 2rem; padding: 0 .5rem; }
.content-block { padding: 0 .5rem; }
table { border-collapse: collapse; margin-bottom: .5rem; }
http://サーバアドレス/books/ にアクセスして、ヘッダやメニューが表示されれば成功です。
詳細画面
一覧画面と同様に詳細画面を作成します。まず、テンプレートを作成します。
{% extends 'layout.html' %}
{% block content %}
<table>
  <tr><th>Book ID</th><td>{{ book.book_id }}</td></tr>
  <tr><th>Title</th><td>{{ book.title }}</td></tr>
  <tr><th>Author</th><td>{{ book.author }}</td></tr>
</table>
<div class="basic-block">
  <button onclick="location.href='/books/'">Return</button>
</div>
{% endblock %}
ビューファイルを作成します。
    :
def detail_book(request, book_id):
    try:
        book = Book.objects.get(book_id=book_id)
    except Book.DoesNotExist:
        book = None
    context = {
        'title': 'Detail Book',
        'book': book,
    }
    template = loader.get_template('books/detail_book.html')
    return HttpResponse(template.render(context, request))
ビューを urls.py に登録します。
urlpatterns = [
    path('', views.list_books, name='list_books'),
    path('<str:book_id>', views.detail_book, name='detail_book'),
]
http://サーバアドレス/books/ から Book ID のリンクをクリックして、詳細画面が表示されれば成功です。
編集画面
詳細画面と同様に編集画面を作成します。まず、テンプレートを作成します。{% url 名前 引数 %} は、urls.py の name="..." で指定した名前に対応するパス名を取得します。{% if ... %} ... {% elif ... %} ... {% endif %} は条件式を記述します。
{% extends 'layout.html' %}
{% block content %}
<form method="POST" action="{% url 'edit_book' book.book_id %}">
  {% csrf_token %}
  <input type="hidden" name="mode" value="{{ mode }}">
  <table>
    <tr>
      <th>Book ID</th>
      <td><input type="text" name="book_id" readonly value="{{ book.book_id }}"></td>
    </tr>
    <tr>
      <th>Title</th>
      <td><input type="text" name="title"
          {% if mode != 'input' %}readonly{% endif %} value="{{ book.title }}"></td>
    </tr>
    <tr>
      <th>Author</th>
      <td><input type="text" name="author"
          {% if mode != 'input' %}readonly{% endif %} value="{{ book.author }}"></td>
    </tr>
  </table>
  <div class="basic-block">
    {% if mode == 'input' %}
      <button type="button" onclick="location.href='{% url 'list_books' %}'">Return</button>
      <button type="submit">OK</button>
    {% elif mode == 'confirm' %}
      <button type="button" onclick="history.back()">Back</button>
      <button type="submit">OK</button>
    {% elif mode == 'result' %}
      <button type="button" onclick="location.href='{% url 'list_books' %}'">Return</button>
    {% endif %}
  </div>
</form>
{% endblock %}
ビューファイルを作成します。
def edit_book_input(request, book_id):
    try:
        book = Book.objects.get(book_id=book_id)
    except Book.DoesNotExist:
        book = None
    context = {
        'title': 'Edit Book(input)',
        'mode': 'input',
        'book': book,
    }
    template = loader.get_template('books/edit_book.html')
    return HttpResponse(template.render(context, request))
def edit_book_confirm(request, book_id):
    book = Book()
    book.book_id = request.POST['book_id']
    book.title = request.POST['title']
    book.author = request.POST['author']
    context = {
        'title': 'Edit Book(confirm)',
        'mode': 'confirm',
        'warning_message': 'Are you sure you want to save?',
        'book': book,
    }
    template = loader.get_template('books/edit_book.html')
    return HttpResponse(template.render(context, request))
def edit_book_result(request, book_id):
    try:
        book = Book.objects.get(book_id=book_id)
        book.book_id = request.POST['book_id']
        book.title = request.POST['title']
        book.author = request.POST['author']
        book.save()
    except Book.DoesNotExist:
        book = None
    context = {
        'title': 'Edit Book(result)',
        'mode': 'result',
        'success_message': 'Success!',
        'book': book,
    }
    template = loader.get_template('books/edit_book.html')
    return HttpResponse(template.render(context, request))
def edit_book(request, book_id):
    if request.method == 'GET':
        return edit_book_input(request, book_id)
    elif request.method == 'POST':
        if request.POST['mode'] == 'input':
            return edit_book_confirm(request, book_id)
        if request.POST['mode'] == 'confirm':
            return edit_book_result(request, book_id)
ビューを urls.py に登録します。
urlpatterns = [
    path('', views.list_books, name='list_books'),
    path('<str:book_id>', views.detail_book, name='detail_book'),
    path('<str:book_id>/edit', views.edit_book, name='edit_book'),
]
レイアウトにメッセージ表示欄を追加します。
  <div class="content-block">
    <h1>{{ title }}</h1>
    {% if success_message %}
    <div class="msg-success">{{ success_message }}</div>
    {% endif %}
    {% if warning_message %}
    <div class="msg-warning">{{ warning_message }}</div>
    {% endif %}
    {% if error_message %}
    <div class="msg-error">{{ error_message }}</div>
    {% endif %}
    {% block content %}{% endblock %}
  </div>
style.css にスタイルを追加します。
button { line-height: 1.2rem; min-width: 6rem; }
input[type="text"], select { border: 1px solid #ccc; height: 1.5rem;
    border-radius: .2rem; padding: 0 .3rem; width: 20rem; }
input[readonly] { border: 0; }
.msg-success { padding: .2rem; color: #080; border: 1px solid #9c9;
    background-color: #cfc; margin-bottom: .5rem; }
.msg-warning { padding: .2rem; color: #880; border: 1px solid #cc9;
    background-color: #ffc; margin-bottom: .5rem; }
.msg-error   { padding: .2rem; color: #800; border: 1px solid #c99;
    background-color: #fcc; margin-bottom: .5rem; }
http://サーバアドレス/books/ から [Edit] のリンクをクリックして、編集操作ができれば成功です。
多言語対応
Webページを、多言語に対応させます。まず、gettext をインストールします。
# yum install -y gettext
./config/settings.py に下記の設定を行います。LocaleMiddleware は必ず SessionMiddleware と CommonMiddleware の間に記述してください。
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.locale.LocaleMiddleware',
    'django.middleware.common.CommonMiddleware',
       :
]
LANGUAGE_CODE = 'ja'
LOCALE_PATHS = (
    os.path.join(BASE_DIR, 'locale'),
)
テンプレートの中で翻訳対象の文字列を {% trans ... %} で囲みます。
{% extends 'layout.html' %}
{% load i18n %}
{% block content %}
<table>
  <thead>
    <tr>
      <th>{% trans 'Book ID' %}</th>
      <th>{% trans 'Title' %}</th>
      <th>{% trans 'Author' %}</th>
      <th>{% trans 'Action' %}</th>
    </tr>
  </thead>
プログラムの中で翻訳対象の文字列を _(...) で囲みます。
from django.utils.translation import gettext_lazy as _
def list_books(request):
    books = Book.objects.all()
    context = {
        'title': _('List Books'),
        'books': books,
    }
./locale フォルダを作成し、翻訳対象の辞書ファイルを作成します。
$ python3.6 manage.py makemessages -l ja
./locale/ja/LC_MESSAGES/django.po ファイルが作成されるので、これに、翻訳対象文字列の翻訳を記入します。
#: apps/books/views.py:9 msgid "List Books" msgstr "ブックの一覧" #: templates/books/list_books.html:7 msgid "Book ID" msgstr "ブックID"
下記のコマンドを実行して、メッセージをコンパイルし、./locale/ja/LC_MESSAGES/django.mo ファイルを作成します。
$ python3.6 manage.py compilemessages
http://サーバアドレス/books/ にアクセスし、「List Books」の代わりに「ブックの一覧」と日本語が表示されれば成功です。うまく表示できない場合は、「manage.py runserver」を再起動したり、ブラウザキャッシュをクリアしてみましょう。
言語設定画面を用意する
言語設定画面を用意します。まずは、テンプレートファイルを作成します。
{% extends 'layout.html' %}
{% load i18n %}
{% block content %}
<form action="{% url 'set_language' %}" method="post">
  {% csrf_token %}
  <input name="next" type="hidden" value="{{ redirect_to }}">
  <select name="language">
    {% get_current_language as LANGUAGE_CODE %}
    {% get_available_languages as LANGUAGES %}
    {% get_language_info_list for LANGUAGES as languages %}
    {% for language in languages %}
      <option value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} selected{% endif %}>
        {{ language.name_local }} ({{ language.code }})
      </option>
    {% endfor %}
  </select>
  <button>{% trans 'Set' %}</button>
</form>
{% endblock %}
設定画面のためのアプリケーションを作成します。
$ python3.6 manage.py startapp settings $ mv ./settings ./apps
views.py および urls.py を設定します。
from django.http import HttpResponse
from django.template import loader
from django.utils.translation import gettext_lazy as _
def settings(request):
    context = {
        'title': _('Settings'),
    }
    template = loader.get_template('settings/settings.html')
    return HttpResponse(template.render(context, request))
from django.urls import path
from . import views
urlpatterns = [
    path('', views.settings,  name='settings'),
]
urlpatterns = [
    path('', include('dashboard.urls')),
    path('admin/', admin.site.urls),
    path('books/', include('books.urls')),
    path('i18n/', include('django.conf.urls.i18n')),
    path('settings/', include('settings.urls')),
]
Settings メニューから http://サーバアドレス/settings/ にアクセスして、表示言語を切り替えることができれば成功です。
選択肢として表示する言語を絞るには、./config/settings.py で LANGUAGES を設定してください。
from django.utils.translation import gettext_lazy as _
LANGUAGE_CODE = 'ja'
LANGUAGES = [
    ('ja', _('Japanese')),
    ('en', _('English')),
]