More Related Content Similar to DjangoでさくっとWeb アプリケーション開発をする話 (20) DjangoでさくっとWeb アプリケーション開発をする話16. 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',
]
17. ディレクトリ構成
.
├── 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
・色付きファイルを中心に作業します
。
・青字は自動生成されます。
・赤字は自分で適宜作成します。
18. 開発用サーバー起動
$ cd myproject
$ python manage.py runserver
System check identified no issues (0 silenced).
March 03, 2017 - 15:23:22
Django version 1.10.6, using settings 'stapydemo.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
19. 開発用サーバーのTips
$ python manage.py runserver IP:ポート番号
$ python manage.py rumserver --settings= myapp.settings
IPとポート指定したい場合
settings.py(環境設定ファイル)を切り替えたい場合
24. • 基本的に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)
27. 認証とセッション関連の
テーブルを作成
$ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, sessions
Running 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上にテーブルを作成する
28. 管理者ユーザ作成
$ python manage.py createsuperuser
Username (leave blank to use 'nakazawa'): admin
Email address: admin@any.com
Password:
Password (again):
Superuser created successfully.
createsuperuser で管理者ユーザを作成する
29. テーブルの中身をのぞいてみる
$ python manage.py dbshell
SQLite version 3.8.10.2 2015-05-20 18:17:19
Enter ".help" for usage hints.
sqlite>
settings.pyに設定してあるDBエンジンに接続(デフォは、sqlite)
sqlite> .tables
auth_group auth_user_user_permissions
auth_group_permissions django_admin_log
auth_permission django_content_type
auth_user django_migrations
auth_user_groups django_session
30. SQLを叩いてみる
sqlite> .header on
sqlite> .mode column
sqlite> select id, username, email from auth_user;
id username email
---------- ---------- -------------
1 admin admin@any.com
31. 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のモデル)
33. 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のサンプル
34. $ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, cms, contenttypes, sessions
Running migrations:
Applying cms.0001_initial... OK
DB migration(DBへ反映)
sqlite> .tables
auth_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
35. 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) # 追加
36. 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のテーブルに反映される
41. 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)
48. 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 のページング部分
50. 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 登録/修正
52. 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'), # 削除
)
55. STATICFILES_DIRS = (
os.path.join(BASE_DIR, "static"),
)
静的ファイルの配置場所を設定
myproject ディレクトリの下に static ディレクトリを作って
配置する場合は、settings.pyに以下のように定義します
• Bootstrap - http://getbootstrap.com/
• jQuery - http://jquery.com/
今回は、以下のサイトからベタにDLして配置します
60. {% 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を記述する
ベースとなるテンプレート
61. {% 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>
book_list.html
Bootstrapの例
↑ 一覧系は Bootstrap の class を使って普通に書く
← base.html を継承
← base.html の title ブロックを置き換え
← base.html の content ブロックを置き換え
62. 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
64. {% 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 形式で展開してくれる
65. {{ 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
を参考にしてみると良いです。
68. 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に設定を書く
69. DBの切り替え
$ pip install dj_database_url
DBをPostgreSQLに切り替える。sqliteが使用できないのと、MySQLは
クレカ登録が必要なため
HerokuのDB設定を使用するため
$ pip install psycopg2
PostgreSQLのアダプタ
70. Settings.pyを両対応させる
from socket import gethostname
if ‘my_hostname' in gethostname():
DEBUG = True
TEMPLATE_DEBUG = True
else:
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()
}
Editor's Notes 先程、辻さんがPythonの初心者向けの話しをして頂いたのと、前回のStapyで阿久津さんがbotoleの話しをしてくれたので、私はDjangoの話しをさせて頂こうと思います。 一緒にPyconで話しをした弊社のエンジニア垣田が書きましたQuitaの記事です。いいね数がハンパないすね。今回、お話しするに当たって何を話そうかなと思った。最初は、Djangoのアップデートで泣いた話しとかしようと思ったのですが、まずが広めることを考えようと思いました。 Django入門と合わせて、Djangoで開発する勘所が分かってもらえればと思います。 Djangoって初めて聞いた人Django使ったことない人、これからやってみようって人
ユニットテストの話しとか、多言語対応とか 初心者向けにあれば、お願いします。 D/JangoじゃなくてDjanhoです。