root/docs/everes/20090425_django_hands_on_b/docs/HandsOnB.txt @ 32779

Revision 32779, 20.7 kB (checked in by everes, 5 years ago)

これでいいや

Line 
1=======================================================
2Django Hands on for beginners.
3=======================================================
4
5超基本
6-------------------------------------------------------------------------
7* Webの仕組み
8
9* Modelって何?
10
11
12→ 拙著 `Django×Python`_ 等の入門書を読んでください。
13
14
15.. _`Django×Python`: http://www.amazon.co.jp/dp/477413760X
16
17
18Djangoの超基本(BASE)
19-------------------------------------------------------------------------
20マルチユーザblogっぽいものを作ろう!
21
22ブログのモデルを考える
23^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
24* エントリー → タイトル、本文、タグ、公開日、書いた人
25* タグ → タグ名、持ち主
26
27
28エントリーのモデルサンプル: ForeignKeyやManyToManyに指定するモデルは、
29指定よりも先に定義が読み込まれていなければならない。
30どうしようもない場合には文字列で定義すればよい。
31::
32
33
34  class Tag(models.Model):
35    name = models.CharField(u'タグ名', max_length=50)
36    user = models.ForeignKey(User)
37   
38    class Meta:
39      ordering = ['user', 'name']
40 
41  class Entry(models.Model):
42    title = models.CharField(u'タイトル', max_length=50)
43    body = models.TextField(u'本文')
44    tags = models.ManyToManyField(Tag, blank=True)
45    pub_date = models.DateTimeField(default=datetime.now)
46    author = models.ForeignKey(User)
47 
48    class Meta:
49        ordering = ['pub_date']
50
51
52アプリケーション構成を考える
53^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
54タグを別にしてもよいので、せっかくだから別にする。
55
56
57* Entry → ブログのメインモデル
58* Tag → ブログに限らず使える
59
60
61* 必要なPYTHONPATHを通す
62
63
64  システムのPYTHONPATHか、自分のPYTHONPATHを通す。
65 
66  Windows
67  ::
68 
69    > set PYTHONPATH=%PYTHONPATH%;C:¥¥hackathon_handson
70 
71  bash
72  ::
73 
74    $ export PYTHONPATH=$PYTHONPATH:/home/hoge/hackathon_handson
75
76
77* プロジェクトを作る
78
79
80  ::
81 
82    django-admin.py startproject hackathon
83
84
85* アプリケーションを二つ作る(PYTHONPATHを通したディレクトリ直下に作る)
86
87
88  プロジェクトの直下ではなく、他の場所に作ってみる。名前は他とかぶらないものにした方が良い。とりあえず駄目な名前を付ける。
89  ::
90 
91    django-admin.py startapp blog
92    django-admin.py startapp tags
93
94
95* settings.pyに最低限の設定をする
96
97
98  データベースの設定
99  ::
100 
101    import os
102    BASE_DIR = os.path.dirname(os.path.abspath(__file__))
103    DATABASE_ENGINE = 'sqlite3'
104    DATABASE_NAME = os.path.join(BASE_DIR, 'data.sqlite3')
105
106
107  アプリケーションの設定
108  ::
109 
110    INSTALLED_APPS = (
111    #元から書いてあるもの
112      'blog',
113      'tags',
114    )
115 
116  タイムゾーンを日本にする
117  ::
118 
119    TIME_ZONE = 'Japan'
120
121
122* テーブルを作る
123
124
125  syncdbする。スーパーユーザは作っておく
126  ::
127 
128    $ chmod 755 ./manage.py
129    $ ./manage.py syncdb
130 
131
132
133モデルの扱いに慣れる
134^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
135
136* tags/models.pyにTagクラスを、blog/models.pyにEntryを記述する。
137
138  * ASCII以外の文字をスクリプトファイルに書く場合にはエンコーディングの指定を忘れないこと。ファイルのエンコーディングはutf-8にする。
139    ::
140   
141      # encoding:utf-8
142 
143  * ForeignKeyで指定しているモデルのインポートを忘れずにする。Entryモデルは、公開日のデフォルトとして現在日時を使うのでdatetime.datetimeのインポートも忘れずに。
144    ::
145   
146      #tags/models.py
147      from django.contrib.auth.models import User
148   
149      #blog/models.py
150      from django.contrib.auth.models import User
151      from tags.models import Tag
152      from datetime import datetime
153
154  * プロジェクトの設定を持ったままインタラクティブシェルを起動する。
155    ::
156   
157      $ ./manage.py shell
158      >>>
159
160  * データ操作するためのモデルをインポートする。
161    ::
162   
163      >>> from blog.models import Entry
164      >>> from tags.models import Tag
165      >>> from django.contrib.auth.models import User
166
167  * スーパーユーザを取得しておく
168    ::
169   
170      >>> spruser = User.objects.all()[0]
171
172  * エントリを登録してみる。
173    ::
174   
175      >>> from datetime import datetime
176      >>> ent = Entry(title=u'Test Entry', body=u'Entry Body. Test, Test, Test', pub_date=datetime.now())
177      >>> ent.author = spruser
178      >>> ent.save()
179
180  * データベースに保存されたエントリを取り出してみる
181    ::
182   
183      >>> db_ent = Entry.objects.get(pk=1)
184      >>> print db_ent.title
185      Test Entry
186
187  * タグを登録してみる。ForeignKey(author)に値を設定する方法
188    ::
189   
190      #fkを表すフィールドにUserインスタンスを設定
191      >>> tg1 = Tag(name=u'Test Tag1', user=spruser)
192      >>> tg1.save()
193     
194      #fk_idにUserインスタンスのpkを設定
195      >>> tg2 = Tag(name=u'Test Tag2', user_id=spruser.id)
196      >>> tg2.save()
197     
198      #インスタンスのfkを表すフィールドにUserインスタンスを設定
199      >>> tg3 = Tag(name=u'Test Tag3')
200      >>> tg3.user = spruser
201      >>> tg3.save()
202
203  * エントリにタグを設定してみる。
204    ::
205   
206      >>> ent.tags.add(tg1)
207      >>> ent.tags.all()
208      [<Tag: Tag object>]
209
210  * エントリからタグを取り除く
211    ::
212   
213      >>> ent.tags = []
214      >>> ent.tags.all()
215      []
216
217  * エントリに複数のタグを一度に設定する
218    ::
219   
220      >>> ent.tags.add(tg1, tg2)
221      >>> ent.tags.all()
222      [<Tag: Tag object>, <Tag: Tag object>]
223
224  * エントリに複数のタグを一度に設定する(代入)
225    ::
226   
227      >>> ent.tags.clear() #これでもタグの設定を消せる
228      >>> ent.tags.all()
229      >>> ent.tags = [tg1,tg2]
230      >>> ent.tags.all()
231 
232遅延評価とキャッシュを理解する
233^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
234
235* SQLをデバッグアウトするために、下準備をする
236
237  * eggでインストールしていなければ、django.db.backends.utilのCursorDebugWrapperのexecuteでsqlをprintする。
238  * eggでインストールしている場合には、django.db.connection.queriesを都度デバッグアウトする。
239
240* 一度参照したFKはキャッシュされる
241  ::
242 
243    >>> db_ent.author
244    #SQL
245    <User: hoge>
246    >>> db_ent.author
247    <User: hoge>
248
249* managerを使ってQuerysetを生成・評価すると毎回SQLが発行される。毎回新しいQuerysetが生成されるため。
250  ::
251 
252    >>> db_ent.tags.all()
253    #SQL
254    [<Tag: Tag object>, <Tag: Tag object>]
255    >>> db_ent.tags.all() #
256    #SQL
257    [<Tag: Tag object>, <Tag: Tag object>]
258
259* Querysetは評価されたタイミングでSQLが発行され、一度評価されたQuerysetは再度評価されない
260  ::
261 
262    >>> query = db_ent.tags.all()
263    >>> query #Querysetを評価した時点でSQLが発行される
264    #SQL
265    [<Tag: Tag object>, <Tag: Tag object>]
266    >>> query #一度評価したQuerysetは再度評価されない
267    [<Tag: Tag object>, <Tag: Tag object>]
268
269* 一度評価されたQuerysetも結果がキャッシュされているだけで、通常のQuerysetのまま
270  ::
271 
272    #一度評価したQuerysetのfilterを呼ぶと、きちんと新しいQuerysetが生成される。
273    >>> x = query.filter(pk=1)
274    #つまり、評価すると正しい条件のついたSQLを発行する
275    >>> x
276    #SQL
277    [<Tag: Tag object>]
278    #一度評価したQuerysetのfilterを呼び出す。
279    #再度Queryset評価しても、何もなかったかのように振る舞う。
280    >>> query
281    [<Tag: Tag object>, <Tag: Tag object>]
282
283Djangoの必修(BASE)
284-------------------------------------------------------------------------
285
286Adminを使う
287^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
288
289* URLの設定
290
291  Adminを使うため、hackathon/urls.pyのコメントアウトされている部分を外す。
292  ::
293 
294    # Uncomment the next two lines to enable the admin:
295    from django.contrib import admin
296    admin.autodiscover()
297    ....
298      (r'^admin/(.*)', admin.site.root),
299
300* Adminをインストールする
301
302  hackathon/settings.pyのINSTALLED_APPSに次の行を追加
303  ::
304 
305    'django.contrib.admin',
306 
307  データベースにテーブルを作る
308  ::
309 
310    ./manage.py syncdb
311
312* 開発サーバを起動する
313  ::
314 
315    ./manage.py runserver
316 
317  起動したら、http://localhost:8000/admin/をブラウザで開いてログインする。
318
319* Adminでまずすること
320
321  * Sitesの設定を変更する
322 
323    ドメイン名にlocalhost:8000、表示名にdevelopmentと入力して保存。
324 
325  * グループを作成する
326 
327    Bloggerという名前で、blogとtagに権限を付与する。権限リストは動的に絞り込める。
328   
329    .. image:: images/create_group.png
330 
331  * ユーザを作成する
332 
333    今後のために2つ作成する。スタッフ権限にチェックを入れて、Bloggerグループに所属させる。
334   
335    .. image:: images/user_list.png
336
337* EntryとTagをAdminに表示されるようにする。
338
339  * admin.pyの追加
340 
341    tagsとblogにadmin.pyというファイルを作成して、Adminで使うモデルとしてAdminFormを登録する
342    ::
343   
344      #tags/admin.py
345      from django.contrib import admin
346      from tags.models import Tag
347     
348      admin.site.register(Tag, admin.ModelAdmin)
349     
350      #blog/admin.py
351      from django.contrib import admin
352      from blog.models import Entry
353     
354      admin.site.register(Entry, admin.ModelAdmin)
355 
356  スーパーユーザでログインしているので、一度ログアウトしてBloggerグループのユーザでログインし直す。
357 
358  .. image:: images/blogger_admin_page.png
359
360* モデルのデータ一覧で表示される項目を変更する
361
362  Entryモデルの一覧を表示してみると、悲しい一覧が見えるはず。
363 
364  .. image:: images/sad_entry_list.png
365 
366  * admin.ModelAdminを継承したカスタムフォームを作成する
367    ::
368   
369      #blog/admin.py
370      class EntryModelAdmin(admin.ModelAdmin):
371        list_display = ['title', 'author', 'pub_date']
372     
373      admin.site.register(Entry, EntryModelAdmin)
374   
375    幸せな一覧になった?
376   
377    list_displayにフィールド名をリストで設定すると一覧の表示をカスタマイズできます。
378   
379    .. image:: images/happy_entry_list.png
380 
381  * オブジェクトの自己表現を変更する
382 
383    **しかし!** 編集画面を表示すると、ManyToManyが酷いことになっています。
384   
385    .. image:: images/sad_object_repr.png
386   
387    関連の値は、関連のインスタンスを文字列表現にしたものを表示しています。
388   
389    文字列表現を操るため、Tagモデルに **__unicode__** メソッドを追加します。
390    ::
391   
392      #tags/models.pyのTagクラスに以下を追加
393        def __unicode__(self):
394          return u'%s(%s)' % (self.name, self.user)
395   
396    ちょっと幸せになった?
397   
398    .. image:: images/happy_object_repr.png
399   
400    ついでに、Tagの一覧を表示してみると、同様の表記に変わっているはずです。
401 
402  * 編集画面のフィールド並び順を変更する
403 
404    最近のネットブックは画面の縦方向が異常に短いですよね。
405   
406    編集画面はモデルのフィールドが順番に縦に並んでいるだけなので、少し並べかえてみましょう。
407    また、公開日はデフォルトのママにする可能性が高いので、普段は目に入らないようにしておきましょう。
408   
409    EntryModelAdminのlist_displayに続いてfieldsetsを追加します
410    ::
411   
412      #blog/admin.py
413      fieldsets = (
414          ('Core Information', {
415            'fields': (('title', 'author'), 'body',)
416            }),
417          ('Options', {
418            'classes': ['collapse',],
419            'fields': (('pub_date', 'tags'), )
420            })
421      )
422   
423    fieldsetsはフィールドの表示を制御します。
424   
425    とりあえずすっきりしました。
426   
427    .. image:: images/admin_fieldsets.png
428   
429    オプションは、クリックすると開きます。
430   
431    .. image:: images/admin_fieldsets_open.png
432   
433  * 一覧画面をごっちゃりする
434 
435    一覧といえば絞り込みですよね。
436   
437    .. image:: images/admin_more.png
438   
439    * list_filter
440   
441      list_filterにフィールド名をリストで設定すると一覧の右に絞り込み機能がつきます。
442     
443      指定できるフィールドはBooleanField、ForeignKeyやManyToManyFieldです。
444   
445    * date_hierarchy
446   
447      date_hierarchyに日付型のフィールド名を設定すると、リスト上部に日付での絞り込み機能がつきます。
448     
449      絞り込みは、年→年月→年月日で順に絞れます。また、絞り込みの候補にはデータの存在する対象しか出ません。
450   
451    * search_fields
452   
453      search_fieldsにフィールと名をリストで設定すると文字列検索機能がつきます。
454 
455  * ManyToManyFieldの表示をUserの権限リストのようにしてみる
456 
457    候補の数が増えてくるとどれを選択したのかわからなくなってきてしまいます。
458   
459    そもそも選択自体に困難が伴うこともあります。
460    ::
461   
462      filter_horizontal = ('tags',)
463   
464    .. image:: images/happy_tags.png
465      :scale: 70
466
467  ここまでで、blog/admin.pyは次のようになりました。
468  ::
469 
470    class EntryModelAdmin(admin.ModelAdmin):
471      list_display = ['title', 'author', 'pub_date']
472      fieldsets = (
473          ('Core Information', {
474            'fields': (('title', 'author'), 'body',)
475            }),
476          ('Options', {
477            'classes': ['collapse',],
478            'fields': (('pub_date', 'tags'), )
479            })
480      )
481      list_filter = ['author', 'tags',]
482      date_hierarchy = 'pub_date'
483      search_fields = ('title, body',)
484      filter_horizontal = ('tags',)
485
486* Adminのラベルを日本語に切り替える
487
488  Adminのみではなく、Djangoの国際化されている文字列が全ての日本語で表示されます。
489  ::
490 
491    LANGUAGE_CODE = 'ja'
492
493
494テンプレート
495^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
496opendesignからもってきたテンプレートを使います。
497
498* http://www.opendesigns.org/preview/?template=1182
499
500  .. image:: images/template_sample.png
501    :scale: 50
502
503* 画面の各要素がブロックがどのページに表示されるべきかを検討する
504
505  * 全ての画面に必要なもの
506 
507    .. image:: images/template_everypage.png
508      :scale: 50
509 
510  * トップページにだけ必要なもの
511 
512    .. image:: images/template_toppage.png
513      :scale: 50
514 
515  * 各ページで内容の変わるもの
516 
517    .. image:: images/template_content.png
518      :scale: 50
519
520* Djangoのテンプレートを使う場合の定石に従ってテンプレートを構成する
521
522 * 定石1
523 
524   アプリケーションのモデル一覧・詳細はアプリケーションディレクトリに配備する
525   
526   命名規則: 一覧 → APP/MODEL_list.html 、 詳細 → APP/MODEL_detail.html
527   
528   アプリケーションディレクトリ直下のtemplatesディレクトリがデフォルトでテンプレート格納ディレクトリになる。
529   blogディレクトリ直下にtemplates/blogというディレクトリを作成する(templatesを作ってその中にblogを作る)
530   
531   **Entryモデルの一覧用** blog/templates/blog/entry_list.html
532   
533   **Entryモデルの章採用** blog/templates/blog/entry_detail.html
534 
535 * 定石2
536 
537   各テンプレートは同一階層のbase.htmlを継承し、base.htmlは上位階層のbase.htmlを継承する。
538
539 * 定石3
540 
541   プロジェクトのテンプレートディレクトリを用意し、base.htmlを作成する
542 
543* 実際の階層と、最初の状態は次のようになる
544
545   ::
546   
547     #階層
548     + hackathon
549       + templates
550          - base.html
551     + blog
552       + templates
553         + blog
554            - base.html
555            - entry_list.html
556            - entry_detail.html
557     
558     #blog/base.html
559     {% extends 'base.html' %}
560     
561     #blog/entry_list.html
562     {% extends 'blog/base.html' %}
563     
564     #entry_detail.html
565     {% extends 'blog/entry_detail.html %}
566
567* はやる気持ちを抑えつつ、静的ファイルの配信を準備する
568
569  どこかにstaticという名前のディレクトリを作ってその中にstyle.cssとimagesを放り込む。
570 
571  次に、staticディレクトリ以下へのアクセスをdjango.views.static.serveに処理させる設定をプロジェクトのurls.pyに追加する。
572  ::
573 
574    (r'^static/(?P<path>.*)$', 'django.views.static.serve', {'document_root': '/path/to/static'}),
575 
576* プロジェクトのbase.htmlにindex.htmlテンプレートの中身をコピーする
577
578  * imagesという文字列の前に {{ MEDIA_URL }} という文字列を記述する
579 
580  * styles.cssの前に {{ MEDIA_URL }} という文字列を記述する
581 
582  * settings.pyのMEDIA_URLの設定を '/static/' に変更する
583 
584    ※ MEDIA_URLはContextProcessorsがテンプレートに渡してくれます。
585   
586    現在設定されているContextProcessorsを確認するには
587    ::
588   
589      $ ./manage.py shell
590      >>> from django.conf import settings
591      >>> settings.TEMPLATE_CONTEXT_PROCESSORS
592      ('django.core.context_processors.auth', 'django.core.context_processors.debug',
593       'django.core.context_processors.i18n', 'django.core.context_processors.media')
594   
595    http://code.djangoproject.com/browser/django/trunk/django/core/context_processors.py
596    ::
597   
598      def media(request):
599          """
600          Adds media-related context variables to the context.
601          """
602          return {'MEDIA_URL': settings.MEDIA_URL}
603   
604    どのページでも必要な情報を渡すのに便利。取得のコストが高いものはキャッシュを使っても良いね。
605
606* プロジェクトテンプレートのbase.htmlにブロックを定義してゆく
607
608  * 基本的なブロック
609 
610    * HTMLのheadを囲むように head という名前のブロック
611   
612      * headの中のtitleタグ内に title という名前のブロック
613      * headの終わりに extrahead という名前のブロック
614   
615    * footerが書いてあるdivを囲むように footer という名前のブロック
616    * メインコンテンツが書いてあるdivを囲むように content という名前のブロック
617 
618  * プラスアルファで必要なブロック
619 
620    * トップにのみ表示する画像を置く部分に gallery という名前のブロック
621
622
623実習
624^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
625* トップページの表示
626
627  / へのアクセスに対して、汎用ビューでEntryデータを20件表示する
628 
629  *ヒント* django.views.generic.date_based.archive_index
630
631* ユーザ毎一覧ページの表示
632
633 /USER/ へのアクセスに対して、汎用ビューをラップてユーザのEntryデータを20件表示する
634 
635 *ヒント* django.views.generic.date_based.archive_index か、django.views.generic.list_detail.object_list
636 
637 *ヒント* archive_indexの場合は、テンプレート名はAPP/MODEL_archive.html
638 
639 *ヒント* month_format='%m'
640 
641 ギャラリーのブロックを非表示にしてみる
642
643 余力があったらページングしてみる
644
645* エントリ詳細ページの表示
646
647 /USER/YEAR/MONTH/DAY/ID/ へのアクセスに対して、対象の日付の対象のデータのEntryデータを1件表示する
648 
649 *ヒント* django.views.generic.date_based.object_detail
650 
651 *ヒント* month_format='%m'
652
653発展
654^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
655* 404/500ページを設定して、実際に表示させてみる(表示される条件を学ぶ)
656* Commentフレームワーク(django.contrib.comments)を使ってエントリにコメントできるようにする
657
658  * コメントが追加されたらメールをエントリの作者に送るようにする
659 
660    * コメントにコメントできるようにする
661 
662  * Akismetを使ってコメントスパムを防ぐ
663
664* 写真をアップできるようにする
665
666  * トップページに最新の3枚を表示する
667 
668    * 写真を四角に切り取って、サイズもトップページ用に変換する
669 
670  * IDEFを保存できるようにしてみる
671
672* プロフィールを登録できるようにする
673
674  * アイコンをアップロードできるようにする
675 
676* Google Sitemapsを出力する
677* テストを書く
678* 公開してみる
679
680  * CGI/FastCGI/mod_python/mod_wsgi
681
682あとは好きにしてくれ
Note: See TracBrowser for help on using the browser.