PythonのDijangoを勉強するために、
簡単な書籍管理アプリを作成してみました。
手順については参考サイトに詳しく記載されているので、
ここでは作成手順の概要、参考サイトに書かれていなくて調べたこと、ハマったことを中心に書きました。
Djangoとは
DjangoはPythonのWebアプリケーションフレームワークです。
Djangoを使うことで、少ないコードで高い品質のWebアプリケーションを作成することができます。
また、プロトタイプを作ることが簡単なので短い期間で開発をすることができ、
生産性と実用性が高いと言えます。
少人数で効率良く開発するのに最適なフレームワークです。
参考
こちらのサイトを参考にさせて頂きました。
とてもわかりやすく手順が書かれていたので、つまづくことなく進められました。
開発環境構築
書籍管理アプリ作成
公式ドキュメント
はじめての Django アプリ作成、その 1 | Django documentation | Django
作成した画面
管理画面
書籍アプリ
自分の開発環境
PyCharm は有償版でないとDjangoプロジェクトを作成することができません。
30日間お試しで使えます。
プロジェクト作成
参考にした手順通り、仮想環境を作成してその中でこのアプリを作成しました。
仮想環境にはいる
$ workon {仮想環境名}
仮想環境から抜ける
$ deactivate
プロジェクトを作成
$ django-admin startproject {プロジェクト名}
mybookというプロジェクトを作成した場合、
下記ファイルが作成されます。
mybook
├── manage.py
└── mybook
├── __init__.py
├── __pycache__
│ ├── __init__.cpython-36.pyc
│ ├── settings.cpython-36.pyc
│ ├── urls.cpython-36.pyc
│ └── wsgi.cpython-36.pyc
├── settings.py
├── urls.py
└── wsgi.py
このあとプロジェクト配下にアプリケーションを作成し、
そのアプリケーション配下のファイルに処理を書いていくので、
settings.py以外は今回はデフォルトのままです。
manage.py -> プロジェクトに対する様々な操作を行うためのコマンドラインユーティリティ。
DBのマイグレートやサーバーの起動をするときこのファイルを実行します。init.py -> Pythonパッケージであること示すための空ファイル
settings.py -> DBや言語、アプリの設定ファイル
urls.py -> URL定義の設定
DB作成
settings.pyのDATABASES
に設定を追加します。
今回はデフォルトのままSQLiteにしていますが、他のDBにしたい場合はENGINE
の設定を変更します。
��SQLite以外の場合、USERやPASSWORD、HOSTなどの追加設定が必要になります。)
settings.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
# 'ENGINE': 'django.db.backends.postgresql', /postgresql
# 'ENGINE': 'django.db.backends.mysql', /mysql
# 'ENGINE': 'django.db.backends.oracle', /oracle
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
データベースにテーブルを作成します。
下記コマンドを実行すると、db.sqlite3 というファイルが作成されます。
このコマンドは、DBの定義をかく models.py に変更を加えるたびに実行します。
$ python manage.py migrate
開発用サーバーの起動
下記コマンドを実行して開発サーバーを起動します。
$ python manage.py runserver
下記URLにアクセスすると画面が表示されます。
アプリケーションの作成
1つのプロジェクトに複数のアプリケーションを入れることができます。
処理を行うWebアプリケーションの単位で作成します。
今回は参考サイトの手順通りcms
というアプリケーションを作成しました。
上位階層にあるほうのプロジェクトフォルダにいる状態で下記コマンドを実行します。
$ python manage.py startapp {アプリケーション名}
下記ファイルが作成されます。
└── cms
├── __init__.py
├── admin.py
├── apps.py
├── forms.py
├── migrations
│ ├── 0001_initial.py
│ └── __init__.py
├── models.py
├── tests.py
└── views.py
モデルの作成
永続化したいデータのフィー ルドや、そのデータの挙動といったDBの定義をします。
各DB(モデル)は1つのクラスで表現され、 django.db.models.Model
のサブクラスになります。
クラス変数はモデルのデータベースフィールドを定義しています。
フィールドの型はFieldクラスのインスタンスで定義します。
文字 -> CharField(引数でmax_lengthを指定)
数値 -> IntegerField
日時 -> DateTimeField
多量のテキスト -> TextField
DBを関連付けする場合はForeignKey
を使います。
models.py
class Book(models.Model):
"""書籍"""
name = models.CharField('書籍名', max_length=255)
publisher = models.CharField('出版社', max_length=255, blank=True)
page = models.IntegerField('ページ数', blank=True, default=0)
def __str__(self):
return self.name
class Impression(models.Model):
"""感想"""
book = models.ForeignKey(Book, verbose_name='書籍', related_name='impressions')
comment = models.TextField('コメント', blank=True)
def __str__(self):
return self.comment
classの前は2行空けないとうまく動作しません。
このことが原因で画面にうまく出力されなかったり処理が実行できずに、だいぶハマってしまいました。
また、コードの最後が改行で終わっていないと警告がでます。
そういった箇所はエディタ上に灰色の波線が出ているので、よく注意したほうが良いと思います。
アプリケーションを追加したら settings.py にも設定を追加して、モデルを有効にします。INSTALLED_APPS
に作成したアプリケーション名を追加します。
settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'cms', # <-追加
]
言語とタイムゾーンも変更します。
settings.py
# 日本語
LANGUAGE_CODE = 'ja'
# 東京
TIME_ZONE = 'Asia/Tokyo'
models.pyの変更をマイグレーションファイルとして保存します。
$ python manage.py makemigrations {アプリケーション名}
マイグレーションファイルの実行するSQLをみる場合は下記コマンドを実行します。
$ python manage.py sqlmigrate {アプリケーション名} 0001
マイグレーションファイルをDBに反映します。
$ python manage.py migrate
今後、DBを変更する場合もこの手順を行います。
- models.pyを修正
- マイグレーションファイルを作成
- DBに反映
管理サイトへのアクセス
Djangoは管理画面を自動で生成してくれます。
まず、管理画面にアクセスするためのスーパーユーザーを作成します。
メールアドレスは適当でいいので、その他ユーザーとパスワードを設定します。
$ python manage.py createsuperuser
このままだと作成アプリをadmin上で編集できないので、設定を追加します。
cms/admin.py に管理画面に表示したいモデルを追加します。from django.contrib import admin
は決まり文句、
追加したモデルをimportしてadmin.site.register(モデル)
で追加します。
from django.contrib import admin
from cms.models import Book, Impression
admin.site.register(Book)
admin.site.register(Impression)
開発サーバーを起動します。
$ python manage.py runserver
下記にアクセスして、スーパーユーザーでログインします。
追加したBookとImpressionがadmin上で編集できるようになっているはずです。
ここで追加や削除が簡単に行うことができます。
Bootstrapのインストール
Bootstrap、jQueryをダウンロードしてプロジェクト配下のstaticフォルダに配置します。
��mybook/settings.pyのSTATIC_URL
にstaticフォルダを参照するようデフォルトで設定されています)
デフォルトのままだとアプリケーション配下のstaticフォルダを参照してしまうため、
プロジェクト配下のstaticフォルダを参照するよう設定を追加します。
mybook/settings.py
STATICFILES_DIRS = (
os.path.join(BASE_DIR, "static"),
)
Djangoで簡単にBootstrapを使うためのパッケージである、django-bootstrap-formをpipでインストールします。
インストール後、mybook/settings.pyのINSTALLED_APPS
に追加します。
settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'bootstrapform', # <- ここを追加
'cms',
]
django-bootstrap-formの使い方は下記に書いてあります。
ビューの作成
各ページのコンテンツは、アプリケーションフォルダ配下のviews.pyに書きます。
それぞれのビューはviews.pyにPython関数として実装します。
defとdefの間は改行を2つ空けないと警告がでます。
returnにあるrender()は HttpResponseを返却するショートカットです。
第1引数にrequestオブジェクト、第2引数にテンプレート名、第3引数にテンプレートに渡すデータを指定します。
- render(request, template_name, context=None, content_type=None, status=None, using=None)
get_object_or_404()は、リクエストしたオブジェクトが存在しないときにHttp404を送出します。
views.py
from django.shortcuts import render, get_object_or_404, redirect
from django.views.generic.list import ListView
from cms.models import Book, Impression
from cms.forms import BookForm, ImpressionForm
def book_list(request):
"""書籍の一覧"""
books = Book.objects.all().order_by('id')
return render(request, 'cms/book_list.html', {'books': books})
def book_edit(request, book_id=None):
"""書籍の編集"""
if book_id:
book = get_object_or_404(Book, pk=book_id)
else:
book = Book()
if request.method == 'POST':
form = BookForm(request.POST, instance=book)
if form.is_valid():
book = form.save(commit=False)
book.save()
return redirect('cms:book_list')
else:
form = BookForm(instance=book)
return render(request, 'cms/book_edit.html', dict(form=form, book_id=book_id))
def book_del(request, book_id):
"""書籍の削除"""
book = get_object_or_404(Book, pk=book_id)
book.delete()
return redirect('cms:book_list')
class ImpressionList(ListView):
"""感想の一覧"""
context_object_name = 'impressions'
template_name = 'cms/impression_list.html'
paginate_by = 2
def get(self, request, *args, **kwargs):
book = get_object_or_404(Book, pk=kwargs['book_id']) # 親の書籍を読む
impressions = book.impressions.all().order_by('id') # 書籍の子供の、感想を読む
self.object_list = impressions
context = self.get_context_data(object_list=self.object_list, book=book)
return self.render_to_response(context)
def impression_edit(request, book_id, impression_id=None):
"""感想の編集"""
book = get_object_or_404(Book, pk=book_id)
if impression_id:
impression = get_object_or_404(Impression, pk=impression_id)
else:
impression = Impression()
if request.method == 'POST':
form = ImpressionForm(request.POST, instance=impression)
if form.is_valid():
impression = form.save(commit=False)
impression.book = book
impression.save()
return redirect('cms:impression_list', book_id=book_id)
else:
form = ImpressionForm(instance=impression)
return render(request,
'cms/impression_edit.html',
dict(form=form, book_id=book_id, impression_id=impression_id))
def impression_del(request, book_id, impression_id):
"""感想の削除"""
impression = get_object_or_404(Impression, pk=impression_id)
impression.delete()
return redirect('cms:impression_list', book_id=book_id)
アプリケーションフォルダ配下にurls.pyを新しく作成します。
��運用面を考慮して、アプリケーションごとにurls.pyを作成するのが良いそうです)
url() の第2引数にviews.pyの関数名を設定することで、viewとurlが紐づきます。
url()の第1引数にマッチさせたいパターンをは正規表現で書かきます。?P<name>
をカッコで囲むことでnameという名前で参照できるようにしていて、\d+
は半角の0から9を1回以上繰り返す、という意味です。
マッチする正規表現を見つけると、第2引数に指定されたビュー関数を呼び出します。
第3引数のnameは省略可能ですが、設定することでどこからでも参照できるようになります。
- url(regex, view, kwargs=None, name=None)
urls.py
from django.conf.urls import url
from cms import views
urlpatterns = [
# 書籍
url(r'^book/$', views.book_list, name='book_list'), # 一覧
url(r'^book/add/$', views.book_edit, name='book_add'), # 登録
url(r'^book/mod/(?P<book_id>\d+)/$', views.book_edit, name='book_mod'), # 修正
url(r'^book/del/(?P<book_id>\d+)/$', views.book_del, name='book_del'), # 削除
# 感想
url(r'^impression/(?P<book_id>\d+)/$', views.ImpressionList.as_view(), name='impression_list'), # 一覧
url(r'^impression/add/(?P<book_id>\d+)/$', views.impression_edit, name='impression_add'), # 登録
url(r'^impression/mod/(?P<book_id>\d+)/(?P<impression_id>\d+)/$', views.impression_edit, name='impression_mod'), # 修正
url(r'^impression/del/(?P<book_id>\d+)/(?P<impression_id>\d+)/$', views.impression_del, name='impression_del'), # 削除
]
プロジェクトフォルダ配下のurls.pyに、先ほど作成したurls.pyの定義を追加します。
urls.py
from django.conf.urls import url, include
from django.contrib import admin
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^cms/', include('cms.urls', namespace='cms')), # <- 追加
]
ページのテンプレートを作成
ページのレイアウトを変更するたびにPythonコードを編集しなくてすむよう、
ビューから使用できるテンプレートを作成します。
アプリケーションフォルダ配下にtemplates
フォルダを新規作成します。
Djangoはtemplates
フォルダからテンプレートを探しにいくようです。
まず共通で使うためのベースファイルを作成します。
Djangoは特有のテンプレート言語を使っていて、{%
と {{
タグはDjangoのテンプレート言語の一部です。
使い方については下記を参考にしました。
Django テンプレート言語 — Django 1.4 documentation
- {% load %}
静的ファイルやライブラリを読み込みます。
- {% block %}{% endblock %}
ベースのテンプレートで定義したものを、子のテンプレートでオーバーライドできるようにします。
子のテンプレートで値を設定します。
{% extends {ベースのテンプレートファイル名}%} を子のテンプレートに記述する必要があります。
- {% static %}
今回は{% load staticfiles %}
を使っていますが、1.9以降のバージョンでは{% load static %}
で定義してから{% static %}
を使う方法が推奨されているようです。
{% load static %}
<img src="{% static "myapp/image.jpg" %}" alt="My image"/>
- {% for {変数名} in {リスト変数名} %}{% endfor %}
各要素に渡ってループします。
追加、修正のフォーム作成
モデルの追加や修正をするためのforms.pyを新規作成します。
DjangoにはHTMLのフォーム操作用のライブラリが用意されていて、フォームクラスとテンプレートを組み合わせることで
動的なWebフォームを簡単に作成できるようです。
モデルの一部フィールドだけを表示したいので、下記のように実装します。
ModelFormサブクラスで、内部クラスMetaのfields属性を使い、フィールド名のリストを設定します。
そうすることで、指定されたフィールドだけを含むフォームを生成します。
fieldsに指定された名前の順番はフォームが作成される順になっています。
使い方については下記を参考にしました。
モデルからフォームを生成する — Django 1.4 documentation
forms.py
from django.forms import ModelForm
from cms.models import Book, Impression
class BookForm(ModelForm):
"""書籍のフォーム"""
class Meta:
model = Book
fields = ('name', 'publisher', 'page', )
class ImpressionForm(ModelForm):
"""感想のフォーム"""
class Meta:
model = Impression
fields = ('comment', )
追加・修正のテンプレートを作成
{{ form }}
とすると、フォームの内容がHTMLに展開されます。{{ form|bootstrap_horizontal }}
でBootstrapの書式に変換しています。
book_edit.html
{% extends "base.html" %}
{% load bootstrap %}
{% block title %}書籍の編集{% endblock title %}
{% block content %}
<h3 class="page-header">書籍の編集</h3>
{% if book_id %}
<form action="{% url 'cms:book_mod' book_id=book_id %}" method="post" class="form-horizontal" role="form">
{% else %}
<form action="{% url 'cms:book_add' %}" method="post" class="form-horizontal" role="form">
{% endif %}
{% csrf_token %}
{{ form|bootstrap_horizontal }}
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-primary">送信</button>
</div>
</div>
</form>
<a href="{% url 'cms:book_list' %}" class="btn btn-default btn-sm">戻る</a>
{% endblock content %}
子の一覧を表示
view.pyでListViewを使います。
ListViewは一覧ページなどを作るための汎用ビューです。
template_nameを使って、ListViewに既存のcms/impression_list.html
テンプレートを使うよう設定しています。
分割したい場合は、paginate_byという変数に数値を設定します。下記は2件でページングする設定です。
view.py
class ImpressionList(ListView):
"""感想の一覧"""
context_object_name = 'impressions'
template_name = 'cms/impression_list.html'
paginate_by = 2
親の一覧画面から感想一覧へ遷移できるよう、ボタンを追加します。
book_list.html
<td>
<a href="{% url 'cms:book_mod' book_id=book.id %}" class="btn btn-default btn-sm">修正</a>
<a href="{% url 'cms:book_del' book_id=book.id %}" class="btn btn-default btn-sm">削除</a>
<a href="{% url 'cms:impression_list' book_id=book.id %}" class="btn btn-default btn-sm btn-primary">感想の一覧</a>
</td>
これで一通りの一覧、更新、削除の実装が終わりました。
やってみた感想
classとclassの間に2行の改行がないとうまく動作しなかったことが、1番のハマりポイントでした。
また、モーダルダイアログを実装で、設定をmodal
(モーダル)にしなければいけないのに、
スペルを謝ってmodel
(モデル)にしてたことでも少しハマりました。
間違っている箇所探すのに苦労したので、スペルミスには注意したいと思います。
Django特有のテンプレート言語が最初はよくわかりませんでしたが、
1つずつ調べていくうちに理解できるようになりました。
書き方を覚える必要はありますが、管理画面が最初から実装されていたり、
テンプレートを作成することで汎用的にできたりするので、
Webアプリを簡単に作れる仕組みを実感することができました。
今回実装しなかった機能や設定もまだまだあると思うので、
もっとDjangoを使った実装をして理解を深めていこうと思います。
実装したコードはgithubにあげておきました。