Python Django で書籍管理アプリを作ってみました

PythonのDijangoを勉強するために、
簡単な書籍管理アプリを作成してみました。

手順については参考サイトに詳しく記載されているので、
ここでは作成手順の概要、参考サイトに書かれていなくて調べたこと、ハマったことを中心に書きました。






Djangoとは



DjangoPythonのWebアプリケーションフレームワークです。

Djangoを使うことで、少ないコードで高い品質のWebアプリケーションを作成することができます。
また、プロトタイプを作ることが簡単なので短い期間で開発をすることができ、
生産性と実用性が高いと言えます。
少人数で効率良く開発するのに最適なフレームワークです。






参考



こちらのサイトを参考にさせて頂きました。
とてもわかりやすく手順が書かれていたので、つまづくことなく進められました。





開発環境構築



qiita.com





書籍管理アプリ作成



qiita.com





公式ドキュメント



はじめての Django アプリ作成、その 1 | Django documentation | Django






作成した画面



管理画面








f:id:mtomitomi:20180123224753p:plain








f:id:mtomitomi:20180123224813p:plain








f:id:mtomitomi:20180123224828p:plain








f:id:mtomitomi:20180123224842p:plain





書籍アプリ








f:id:mtomitomi:20180123224933p:plain








f:id:mtomitomi:20180123225004p:plain








f:id:mtomitomi:20180123225027p:plain








f:id:mtomitomi:20180123225047p:plain








f:id:mtomitomi:20180123225124p:plain









自分の開発環境








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定義の設定


  • wsgi.py -> プロジェクトをサーブするためのWSGI互換Webサーバーとのエントリーポイント








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にアクセスすると画面が表示されます。



http://127.0.0.1:8000/






アプリケーションの作成



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を変更する場合もこの手順を行います。




  1. models.pyを修正

  2. マイグレーションファイルを作成

  3. 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





下記にアクセスして、スーパーユーザーでログインします。



http://127.0.0.1:8000/admin/





追加したBookとImpressionがadmin上で編集できるようになっているはずです。
ここで追加や削除が簡単に行うことができます。



f:id:mtomitomi:20180123104011p:plain






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の使い方は下記に書いてあります。



pypi.python.org






ビューの作成



各ページのコンテンツは、アプリケーションフォルダ配下の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フォルダを新規作成します。
Djangotemplatesフォルダからテンプレートを探しにいくようです。
まず共通で使うためのベースファイルを作成します。





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にあげておきました。



github.com