Post on 10-Apr-2017
Django でさくっとWeb アプリケーション開発をする話
みんなの Python 勉強会 in 長野
Yuichi Nakazawa @y_nakazawa1220
( 株 ) 日本システム技研 所属 Python/Django 案件 ディレクター兼エンジニア
GEEKLAB.NAGANO エバンジェリスト
自己紹介
元ネタ
スライドシェア: https://goo.gl/Hx9zEp Youtube : https://goo.gl/c8y6xy
Django 入門の参考書
• Python で Web アプリケーション開発したい人• これから Django で開発したい人、興味がある人• Django の初級者向け
このセッションの対象
• ベストプラクティス的なこと• Tips 的なこと• その他、細かい Django の機能(テストとか、多言語対応とか・・)
話さないこと
#stapy
お願い• こんな書き方あるよ• こうした方が分かりやすいよ• こんなこと知りたいよ
Django
• フルスタックな Web アプリケーションフレームワーク• MVC でなく、 MVT• 学習コストが低い• admin サイトが秀逸• migration が便利
Django 特徴
チュートリアルが良い
https://docs.djangoproject.com/ja/1.10/intro/
• Django アプリ作成• Model 作成と migration• admin サイト• 簡単な CRUD• Heroku へのデプロイ
レジュメ( 書籍管理アプリの作り方 )
Djanogo アプリ作成
Django インストール
$ pip install django
現状だと 1.10.6 あたりが入るはず$ pip freeze djangoDjango==1.10.6
Django プロジェクト & アプリの生成$ django-admin.py startproject [myproject]
プロジェクトを作る
$ python manage.py startapp [myapp]
アプリ作る
settings.py にアプリ追加INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'myapp',]
ディレクトリ構成.├── cms│ ├── __init__.py│ ├── admin.py│ ├── apps.py│ ├── migrations│ │ └── __init__.py│ ├── models.py │ ├── tests.py│ ├── urls.py│ ├── forms.py│ └── views.py│ └── templates├── db.sqlite3├── manage.py└── stapydemo ├── __init__.py ├── __pycache__ ├── settings.py ├── urls.py └── wsgi.py
・色付きファイルを中心に作業します。・青字は自動生成されます。・赤字は自分で適宜作成します。
開発用サーバー起動$ cd myproject$ python manage.py runserverSystem check identified no issues (0 silenced).
March 03, 2017 - 15:23:22Django version 1.10.6, using settings 'stapydemo.settings'Starting development server at http://127.0.0.1:8000/Quit the server with CONTROL-C.
開発用サーバーの Tips
$ python manage.py runserver IP: ポート番号
$ python manage.py rumserver --settings= myapp.settings
IP とポート指定したい場合
settings.py (環境設定ファイル)を切り替えたい場合
Web アプリケーション開発フロー
1. Model (データ構造)を考える ⇒ Models.py の実装(ビジネスロジックの実装)2. Views.py ⇒ template へ渡すためのデータ加工3. template を作る( HTML/CSS/Javascript )4. URL 設計 ⇒ Urls.py の実装
Model 作成とmigration
Web アプリケーション開発フロー
1. Model (データ構造)を考える ⇒ Models.py の実装(ビジネスロジックの実装)2. Views.py ⇒ template へ渡すためのデータ加工3. template を作る( HTML/CSS/Javascript )4. URL 設計 ⇒ Urls.py の実装
Model とは?
そのアプリケーションが扱う領域のデータと手続き(ビジネスロジック - ショッピングの合計額や送料を計算するなど)を表現する要素である - wikipedia より
DB に定義したいデータ構造を定義する
• 基本的に Django に用意されている ORM のみでDB にアクセスできる。
• SQL は書かなくて済む(個人的に経験なし・・)
def book_list(request): ''' 書籍の一覧 ''' books = Book.objects.all().order_by('id') # 親の書籍を全件読む return render_to_response('cms/book_list.html', # 使用するテンプレート {'books': books}, # テンプレートに渡すデータ context_instance=RequestContext(request))
def impression_list(request, book_id): ''' 感想の一覧 ''' book = get_object_or_404(Book, pk=book_id) # 親の書籍を1件読む impressions = book.impressions.all().order_by('id') # 書籍の子供の、感想を読む : :
親の読み方、子の読み方
ORM (Object Relation Mapping)
モデルの追加や項目の更新・削除を手軽に DB と同期が取れる仕組み
migration とは
• model.py の定義変更を DB に反映させることができる• model を直した場合に、所定のコマンドを叩くと
migrate ファイルを作ってくれる• Django 1.6 までは South
- http://south.aeracode.org
• Django 1.7 からは標準として取り込まれた
migration
認証とセッション関連のテーブルを作成$ python manage.py migrateOperations to perform: Apply all migrations: admin, auth, contenttypes, sessionsRunning migrations: Applying contenttypes.0001_initial... OK Applying auth.0001_initial... OK Applying admin.0001_initial... OK Applying admin.0002_logentry_remove_auto_add... OK Applying contenttypes.0002_remove_content_type_name... OK Applying auth.0002_alter_permission_name_max_length... OK Applying auth.0003_alter_user_email_max_length... OK Applying auth.0004_alter_user_username_opts... OK Applying auth.0005_alter_user_last_login_null... OK Applying auth.0006_require_contenttypes_0002... OK Applying auth.0007_alter_validators_add_error_messages... OK Applying auth.0008_alter_user_username_max_length... OK Applying sessions.0001_initial... OK
まず migrate して、認証とセッション関連を DB 上にテーブルを作成する
管理者ユーザ作成
$ python manage.py createsuperuserUsername (leave blank to use 'nakazawa'): adminEmail address: admin@any.comPassword: Password (again): Superuser created successfully.
createsuperuser で管理者ユーザを作成する
テーブルの中身をのぞいてみる
$ python manage.py dbshellSQLite version 3.8.10.2 2015-05-20 18:17:19Enter ".help" for usage hints.sqlite>
settings.py に設定してある DBエンジンに接続(デフォは、 sqlite )
sqlite> .tablesauth_group auth_user_user_permissionsauth_group_permissions django_admin_log auth_permission django_content_type auth_user django_migrations auth_user_groups django_session
SQL を叩いてみる
sqlite> .header onsqlite> .mode columnsqlite> select id, username, email from auth_user;id username email ---------- ---------- -------------1 admin admin@any.com
from django.db import models
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
Model の実装「書籍」と各書籍の「感想」をモデル化(関連が 1:n のモデル)
model を更新した場合( migrations ディレクトリ配下に 000x_xxxx.py が作成される)$ python manage.py makemigrationsMigrations for 'cms': cms/migrations/0001_initial.py: - Create model Book - Create model Impression
DB migration( 変更の抽出 )
class Migration(migrations.Migration):
initial = True
dependencies = [ ]
operations = [ migrations.CreateModel( name='Book', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=255, verbose_name=' 書籍名 ')), ('publisher', models.CharField(blank=True, max_length=255, verbose_name=' 出版社 ')), ('page', models.IntegerField(blank=True, default=0, verbose_name=' ページ数 ')), ], ), migrations.CreateModel( name='Impression', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('comment', models.TextField(blank=True, verbose_name=' コメント ')), ('book', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='impressions', to='cms.Book', verbose_name=' 書籍 ')), ], ), ]
000x_xxxx.py のサンプル
$ python manage.py migrate Operations to perform: Apply all migrations: admin, auth, cms, contenttypes, sessionsRunning migrations: Applying cms.0001_initial... OK
DB migration(DB へ反映 )
sqlite> .tablesauth_group cms_book auth_group_permissions cms_impression auth_permission django_admin_log auth_user django_content_type auth_user_groups django_migrations auth_user_user_permissions django_session
DB migration (カラム追加)models.py に isbn という項目を追加します。
class Book(models.Model): : page = models.IntegerField(u'ページ数 ', blank=True, default=0) isbn = models.CharField(u'ISBN', max_length=255, blank=True, null=True) # 追加
DB migration
$ python manage.py makemigrations myapp
makemigrations コマンド( models.py の変更を拾う)
makemigrations が作成したマイグレーション ファイルを確認myproj/myapp/migrations/0002_book_isbn.py
migrate コマンドで、変更を DB に反映する$ python manage.py migrate myapp
などといったファイルができているので、エディタで確認する
モデルの項目追加/変更が DB のテーブルに反映される
admin サイト
admin サイトにログインしてみる
http://127.0.0.1:8000/admin
※admin サイト :DB のメンテナンスに使用するサイト
admin サイト ログイン画面
admin サイト
admin サイトへ追加する
admin.site.register(Book)admin.site.register(Impression)
List にカラム表示するのであれば、こんな感じ
カスタマイズするとこんな感じclass BookAdmin(admin.ModelAdmin): list_display = ('id', 'name', 'publisher', 'page',) # 一覧に出したい項目 list_display_links = ('id', 'name',) # 修正リンクでクリックできる項目 search_fields = ['name'] # 検索ボックス を出すadmin.site.register(Book, BookAdmin)
class ImpressionAdmin(admin.ModelAdmin): list_display = ('id', 'comment',) list_display_links = ('id', 'comment',)admin.site.register(Impression, ImpressionAdmin)
admin サイトへ追加する
CRUD
CRUD (クラッド)とは、ほとんど全てのコンピュータソフトウェアが持つ永続性[1] の 4つの基本機能のイニシャルを並べた用語。その 4つとは、 Create (生成)、 Read (読み取り)、 Update (更新)、 Delete(削除)である。ユーザインタフェースが備えるべき機能(情報の参照 /検索 / 更新)を指す用語としても使われる。 - wikipedia より
CRUD とは
Web アプリケーション開発フロー
1. Model (データ構造)を考える ⇒ Models.py の実装(ビジネスロジックの実装)2. Views.py ⇒ template へ渡すためのデータ加工3. URL 設計 ⇒ Urls.py の実装4. template を作る( HTML/CSS/Javascript )
CRUD の書き方書籍の一覧を表示したい場合は、以下のような感じ
def book_list(request): ''' 書籍の一覧 ''' books = Book.objects.all().order_by('id') return render_to_response('cms/book_list.html', # 使用するテンプレート dict(books=books), # テンプレートに渡すデータ context_instance=RequestContext(request))
• django.views.generic.list.ListView を使っておくと、ページネートが簡単
Listページの書き方
class BookList(ListView): """ 書籍の一覧 """ context_object_name = 'books' template_name = 'cms/book_list.html' paginate_by = 2 # 1ページは最大 2 件ずつでページングする def get(self, request, *args, **kw): self.object_list = Book.objects.all().order_by('id') # 書籍を全件取得 context = self.get_context_data(object_list=self.object_list) return self.render_to_response(context)
views.py 一覧
Listページの書き方 {% if is_paginated %} <ul class="pagination"> {% if page_obj.has_previous %} <li><a href="?page={{ page_obj.previous_page_number }}">«</a></li> {% else %} <li class="disabled"><a href="#">«</a></li> {% endif %} {% for linkpage in page_obj.paginator.page_range %} {% ifequal linkpage page_obj.number %} <li class="active"><a href="#">{{ linkpage }}</a></li> {% else %} <li><a href="?page={{ linkpage }}">{{ linkpage }}</a></li> {% endifequal %} {% endfor %} {% if page_obj.has_next %} <li><a href="?page={{ page_obj.next_page_number }}">»</a></li> {% else %} <li class="disabled"><a href="#">»</a></li> {% endif %} </ul> {% endif %}
book_list.html のページング部分
Listページの書き方ページングの表示例
この部分
CRUD の書き方def book_edit(request, book_id=None): """ 書籍の編集 """ if book_id: # book_id が指定されている ( 修正時 ) book = get_object_or_404(Book, pk=book_id) else: # book_id が指定されていない ( 追加時 ) book = Book() if request.method == ‘POST': # POST された request データからフォームを作成 form = BookForm(request.POST, instance=book) if form.is_valid(): # フォームのバリデーション form.save() return redirect('cms:book_list') else: # GET の時 # book インスタンスからフォームを作成 form = BookForm(instance=book)
return render(request, 'cms/book_edit.html', dict(form=form, book_id=book_id))
views.py 登録/修正
CRUD の書き方forms.py
class BookForm(ModelForm): ''' 書籍のフォーム ''' class Meta: model = Book fields = ('name', 'publisher', 'page', )
CRUD の書き方def book_del(request, book_id): ''' 書籍の削除 ''' book = get_object_or_404(Book, pk=book_id) book.delete() return redirect('cms:book_list')
views.py 削除
urls.py
urlpatterns = patterns('', # 書籍 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'), # 削除)
Web アプリケーション開発フロー
1. Model (データ構造)を考える ⇒ Models.py の実装(ビジネスロジックの実装)2. Views.py ⇒ template へ渡すためのデータ加工3. URL 設計 ⇒ Urls.py の実装4. template を作る( HTML/CSS/Javascript )
• CSS フレームワーク http://getbootstrap.com/• とりあえずお手軽に見栄えするサイトを作りたい場合に便利
Bootstrap を使う
STATICFILES_DIRS = ( os.path.join(BASE_DIR, "static"),)
静的ファイルの配置場所を設定myproject ディレクトリの下に static ディレクトリを作って配置する場合は、 settings.py に以下のように定義します
• Bootstrap - http://getbootstrap.com/ • jQuery - http://jquery.com/
今回は、以下のサイトからベタに DL して配置します
myproject/ 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 jquery-3.1.1.min.js
静的ファイルの配置イメージ
• 一覧系のページは、 Bootstrap提供の class を使って書きます• Form系のページは、 django-bootstrap-form
https://github.com/tzangms/django-bootstrap-formを使うと便利です。
Bootstrap
$ pip install django-bootstrap-form
INSTALLED_APPS = ( 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'bootstrapform', # django-bootstrap-form 'cms',)
• Django のテンプレートは継承できるので、以下のような構造にすると流用性が高いです。
Bootstrap
Bootstrap の JS 、 CSSを定義したベース
CMS の各種ページ
base.html
index.html など
Bootstrap の例(一覧)
{% load staticfiles %}<!DOCTYPE html><html lang="{{ LANGUAGE_CODE|default:"en-us" }}"><head><meta charset="UTF-8"><title>{% block title %}My books{% endblock %}</title><meta name="viewport" content="width=device-width, initial-scale=1.0"><link href="{% static 'css/bootstrap.min.css' %}" rel="stylesheet"><link href="{% static 'css/bootstrap-theme.min.css' %}" rel="stylesheet"><script src="{% static 'js/jquery-3.1.1.min.js' %}"></script><script src="{% static 'js/bootstrap.min.js' %}"></script>{% block extrahead %}{% endblock %}</head><body> <div class="container"> {% block content %} {{ content }} {% endblock %} </div></body></html>
base.html
Bootstrap の例(一覧)
Bootstrap の JS 、 CSS を記述するベースとなるテンプレート
{% extends "base.html" %}
{% block title %} 書籍の一覧 {% endblock title %}
{% block extrahead %}<style>table { margin-top: 8px;}</style>{% endblock %}
{% block content %} <h3 class="page-header"> 書籍の一覧 </h3> <a href="{% url 'cms:book_add' %}" class="btn btn-default btn-sm"> 追加 </a> <table class="table table-striped table-bordered"> <thead> <tr> <th>ID</th> <th> 書籍名</th> <th>出版社</th> <th>ページ数</th> <th>操作 </th> </tr> </thead> <tbody> {% for book in books %} <tr> <td>{{ book.id }}</td> <td>{{ book.name }}</td> <td>{{ book.publisher }}</td> <td>{{ book.page }}</td> <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> </td> </tr> {% endfor %} </tbody> </table>{% endblock content %}
book_list.html
Bootstrap の例
↑ 一覧系は Bootstrap の class を使って普通に書く
← base.html を継承← base.html の title ブロックを置き換え← base.html の content ブロックを置き換え
HTML出力例<!DOCTYPE html><html lang="en-us"><head><meta charset="UTF-8"><title> 書籍の一覧 </title><meta name="viewport" content="width=device-width, initial-scale=1.0"><script src="/static/js/jquery-3.1.1.min.js"></script></head><body> <div class="container"> <h3 class="page-header"> 書籍の一覧 </h3> <a href="" class="btn btn-default btn-sm"> 追加 </a> <table class="table table-striped table-bordered"> <thead> </thead> <tbody> </tbody> </table> <ul class="pagination"> <li class="disabled"><a href="#">«</a></li> <li class="active"><a href="#">1</a></li> <li><a href="?page=2">2</a></li> <li><a href="?page=2">»</a></li> </ul> </div></body></html>
青字 :base.html赤字 :book_list.html
Bootstrap の例( Form )
{% extends “base_navi.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 %}
book_edit.html
Bootstrap の例( Form系)
← django-bootstrap-form を使っているので Form の項目を Bootstrap 形式で展開してくれる
{{ form|bootstrap_horizontal }}
form を丸ごと出す
django-bootstrap-form のTips
form を項目単位にバラす(項目を出す/出さない の制御をしたい時){{ form.id|bootstrap_horizontal }}{{ form.name|bootstrap_horizontal }}
HTML レベルにバラす( checkbox 、 radio は微妙に異なるので注意)<div class="form-group{% if form.name.errors %} has-error{% endif %}"> <label class="control-label" for="{{ form.name.auto_id }}">{{ form.name.label }}</label> <input type="text" class=“form-control" name="{{ form.name.html_name }}" value="{{ form.name.value }}"
id="{{ form.name.auto_id }}"> {% for error in form.name.errors %} <span class=“help-block {{ form.error_css_class }}">{{ error }}</span> {% endfor %} {% if form.name.help_text %} <p class="help-block"> {{ form.name.help_text|safe }} </p> {% endif %}</div>
checkbox 、 radio は site-packages/bootstrapform/templates/bootstrapfrom/field.htmlを参考にしてみると良いです。
アプリケーションを公開する
runtime と gunicorn の設定
$ pip install gunicornアプリケーション・サーバーとして「 gunicorn」を入れる
python-3.4.1
PROJECR_ROOT/runtime.txt に使用する Python バージョンを書く
web: gunicorn --env DJANGO_SETTINGS_MODULE=stapydemo.settings stapydemo.wsgi --log-file -
PROJECR_ROOT/Procfile に設定を書く
DB の切り替え
$ pip install dj_database_url
DB を PostgreSQL に切り替える。 sqlite が使用できないのと、 MySQL はクレカ登録が必要なため
Heroku の DB 設定を使用するため$ pip install psycopg2PostgreSQL のアダプタ
Settings.py を両対応させるfrom socket import gethostnameif ‘my_hostname' in gethostname(): DEBUG = True TEMPLATE_DEBUG = Trueelse: DEBUG = False TEMPLATE_DEBUG = False ALLOWED_HOSTS = ['*']
開発環境と Heroku の環境で設定を切り替える。
if 'my_hostname' in gethostname(): DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } }else: import dj_database_url DATABASES = { 'default': dj_database_url.config() }
静的ファイルの扱い静的ファイルを Heroku で扱うため、 whitenoise を入れる。http://whitenoise.evans.io/en/stable/django.html
$ pip install whitenoise
STATICFILES_DIRS = ( os.path.join(BASE_DIR, "static"),)
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
STATICFILES_STORAGE = 'whitenoise.django.GzipManifestStaticFilesStorage'
settings.py にパスを追加する
環境をまとめる
$ pip freeze > requirements.txt
デプロイ
まとめ• Django は、チュートリアルが充実しているので学習コストが比較的低いです。• フルスタックなので、色々やりたいことがあるのであれば Django を選ぶと良いと思います。• Heroku は、お手軽にデプロイ経験できるので是非使ってみてください。
Pythonエンジニア募集中!
https://jsl.co.jp
• 長野で Python/Django をやりたい方• MBP支給します• リモートワーク有り /Slack有り• 勉強会 / セミナー参加支援有り
Q&A