発掘!あるあるDjango大辞典

プレゼンテーション層編

Django The Web framework for perfectionists with deadlines -

Yutaka Matsubara

http://d.hatena.co.jp/mopemope/

Djangoとは?

Djangoとは

認知度

特徴

Djangoの哲学って?

脱暗黒魔術?

脱暗黒魔術とは

かなり明示的!!!

 1 from django.template import Context, loader
 2 from mysite.polls.models import Poll
 3 from django.http import HttpResponse
 4 
 5 def index(request):
 6     #データベースより値を取得
 7     latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
 8 
 9     #テンプレートをロード
10     t = loader.get_template('polls/index.html')
11     c = Context({
12         'latest_poll_list': latest_poll_list,
13     })
14     #レンダリング→Response
15     return HttpResponse(t.render(c))
16 

ちょっと寄り道

設定ファイルについて

設定ファイル

setting.py

 1 # Django settings for sample project.
 2 
 3 DEBUG = True
 4 TEMPLATE_DEBUG = DEBUG
 5 
 6 ADMINS = (
 7     # ('Your Name', 'your_email@domain.com'),
 8 )
 9 
10 MANAGERS = ADMINS
11 
12 DATABASE_ENGINE = 'sqlite3'           # 'postgresql', 'mysql', 'sqlite3' or 'ado_mssql'.
13 DATABASE_NAME = 'C:/sqlite/sample1.db'             # Or path to database file if using sqlite3.
14 DATABASE_USER = ''             # Not used with sqlite3.

設定ファイル

setting.py

23 # Language code for this installation. All choices can be found here:
24 # http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes
25 # http://blogs.law.harvard.edu/tech/stories/storyReader$15
26 LANGUAGE_CODE = 'ja_JP'
27 
28 SITE_ID = 1
29 
30 # If you set this to False, Django will make some optimizations so as not
31 # to load the internationalization machinery.
32 USE_I18N = True
33 
34 # Absolute path to the directory that holds media.
35 # Example: "/home/media/media.lawrence.com/"
36 MEDIA_ROOT = ''

設定ファイル

setting.py

48 SECRET_KEY = 'x9w#-$q4$p9vu3di4k2)g58mk=p*q=@(w76+j03xf22w@e6u1%'
49 
50 # List of callables that know how to import templates from various sources.
51 TEMPLATE_LOADERS = (
52     'django.template.loaders.filesystem.load_template_source',
53     'django.template.loaders.app_directories.load_template_source',
54 #     'django.template.loaders.eggs.load_template_source',
55 )
56 
57 MIDDLEWARE_CLASSES = (
58     'django.middleware.common.CommonMiddleware',
59     'django.contrib.sessions.middleware.SessionMiddleware',
60     'django.contrib.auth.middleware.AuthenticationMiddleware',
61     'django.middleware.doc.XViewMiddleware',
62 )
63 
64 ROOT_URLCONF = 'sample.urls'
65 
66 TEMPLATE_DIRS = (
67     # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
68     # Always use forward slashes, even on Windows.
69     # Don't forget to use absolute paths, not relative paths.
70 )
71 
72 INSTALLED_APPS = (
73     'django.contrib.auth',
74     'django.contrib.contenttypes',
75     'django.contrib.sessions',
76     'django.contrib.sites',
77     'django.contrib.admin',
78     'sample.wiki'
79 )

本題

Djangoのプレゼンテーション層

これだけしってりゃなんとかなるよ

URLの設計

URLの設計

URLの設計

(正規表現, Pythonのコールバック関数, [, オプションの辞書オブジェクト])

url.py

1 from django.conf.urls.defaults import *
2 
3 urlpatterns = patterns('',
4     (r'^polls/$', 'mysite.polls.views.index'),
5     (r'^polls/(?P<poll_id>\d+)/$', 'mysite.polls.views.detail'),
6     (r'^polls/(?P<poll_id>\d+)/results/$', 'mysite.polls.views.results'),
7     (r'^polls/(?P<poll_id>\d+)/vote/$', 'mysite.polls.views.vote'),
8 )
9 

URLの設計

views.py

1 from django.http import HttpResponse
2 
3 def index(request):
4     return HttpResponse("Hello, world. You're at the poll index.")
5 
6 def detail(request, poll_id):
7     return HttpResponse("You're looking at poll %s." % poll_id)
8 

URLの設計

ビュー関数

ビュー関数

ビュー関数

views.py

 1 from django.template import Context, loader
 2 from mysite.polls.models import Poll
 3 from django.http import HttpResponse
 4 
 5 def index(request):
 6     latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
 7     t = loader.get_template('polls/index.html')
 8     c = Context({
 9         'latest_poll_list': latest_poll_list,
10     })
11     return HttpResponse(t.render(c))
12 

ビュー関数

ショートカットを使うviews.py

1 from django.shortcuts import render_to_response
2 from mysite.polls.models import Poll
3 
4 def index(request):
5     latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
6     return render_to_response('polls/index.html',
7                               {'latest_poll_list': latest_poll_list})
8 

ビュー関数

404の場合 views.py

1 from django.http import Http404
2 # ...
3 def detail(request, poll_id):
4     try:
5         p = Poll.objects.get(pk=poll_id)
6     except Poll.DoesNotExist:
7         raise Http404
8     return render_to_response('polls/detail.html', {'poll': p})
9 

ビュー関数

ショートカットを使うviews.py

1 from django.shortcuts import render_to_response, get_object_or_404
2 # ...
3 def detail(request, poll_id):
4     p = get_object_or_404(Poll, pk=poll_id)
5     return render_to_response('polls/detail.html', {'poll': p})
6 

ビュー関数が呼ばれるまでの流れ

ビュー関数が呼ばれるまでの流れ

base.py

 1     def get_response(self, request):
 2         "Returns an HttpResponse object for the given HttpRequest"
 3         from django.core import exceptions, urlresolvers
 4         from django.core.mail import mail_admins
 5         from django.conf import settings
 6 
 7         # Apply request middleware
 8         for middleware_method in self._request_middleware:
 9             response = middleware_method(request)
10             if response:
11                 return response
12 
13         # Get urlconf from request object, if available.  Otherwise use default.
14         urlconf = getattr(request, "urlconf", settings.ROOT_URLCONF)
15 
16         resolver = urlresolvers.RegexURLResolver(r'^/', urlconf)

ビュー関数が呼ばれるまでの流れ

base.py

17         try:
18             callback, callback_args, callback_kwargs = resolver.resolve(request.path)
19 
20             # Apply view middleware
21             for middleware_method in self._view_middleware:
22                 response = middleware_method(request, callback, callback_args, callback_kwargs)
23                 if response:
24                     return response
25 
26             try:
27                 response = callback(request, *callback_args, **callback_kwargs)
28             except Exception, e:
29                 # If the view raised an exception, run it through exception
30                 # middleware, and if the exception middleware returns a
31                 # response, use that. Otherwise, reraise the exception.
32                 for middleware_method in self._exception_middleware:

ビュー関数が呼ばれるまでの流れ

base.py

33                     response = middleware_method(request, e)
34                     if response:
35                         return response
36                 raise
37 
38             # Complain if the view returned None (a common error).
39             if response is None:
40                 try:
41                     view_name = callback.func_name # If it's a function
42                 except AttributeError:
43                     view_name = callback.__class__.__name__ + '.__call__' # If it's a class
44                 raise ValueError, "The view %s.%s didn't return an HttpResponse object." % (callback.__module__, view_name)
45 
46             return response
47 

ビュー関数が呼ばれるまでの流れ

Formと入力チェック

Formと入力チェック

Formと入力チェック

 1 def create_place(request):
 2     manipulator = Place.AddManipulator()
 3 
 4     if request.POST:
 5         # If data was POSTed, we're trying to create a new Place.
 6         new_data = request.POST.copy()
 7 
 8         # Check for errors.
 9         errors = manipulator.get_validation_errors(new_data)
10 
11         if not errors:
12             # No errors. This means we can save the data!
13             manipulator.do_html2python(new_data)
14             new_place = manipulator.save(new_data)
15 

Formと入力チェック

16             # Redirect to the object's "edit" page. Always use a redirect
17             # after POST data, so that reloads don't accidently create
18             # duplicate entires, and so users don't see the confusing
19             # "Repost POST data?" alert box in their browsers.
20             return HttpResponseRedirect("/places/edit/%i/" % new_place.id)
21     else:
22         # No POST, so we want a brand new form without any data or errors.
23         errors = new_data = {}
24 
25     # Create the FormWrapper, template, context, response.
26     form = forms.FormWrapper(manipulator, new_data, errors)
27     return render_to_response('places/create_form.html', {'form': form})
28 

Formと入力チェック

バリデータ

 1 from django import forms
 2 from django.core import validators
 3 
 4 class ContactManipulator(forms.Manipulator):
 5     def __init__(self):
 6         self.fields = (
 7             # ... snip fields as above ...
 8             forms.EmailField(field_name="to", validator_list=[self.isValidToAddress])
 9         )
10 
11     def isValidToAddress(self, field_data, all_data):
12         if not field_data.endswith("@example.com"):
13             raise validators.ValidationError("You can only send messages to example.com e-mail addresses.")
14 

標準で提供

Formと入力チェック

Formのあるビュー関数

画面遷移

画面遷移

テンプレート

え?HTMLじゃないの?

テンプレート

テンプレート

 1 {% extends "base_generic.html" %}
 2 
 3 {% block title %}{{ section.title }}{% endblock %}
 4 
 5 {% block content %}
 6 <h1>{{ section.title }}</h1>
 7 
 8 {% for story in story_list %}
 9 <h2>
10   <a href="{{ story.get_absolute_url }}">
11     {{ story.headline|upper }}
12   </a>
13 </h2>
14 <p>{{ story.tease|truncatewords:"100" }}</p>
15 {% endfor %}
16 {% endblock %}
17 

テンプレート

Form付きテンプレート

 1 {% extends "base.html" %}
 2 
 3 {% block content %}
 4 <h1>Create a place:</h1>
 5 
 6 <form method="post" action="../do_new/">
 7 <p><label for="id_name">Name:</label> {{ form.name }}</p>
 8 <p><label for="id_address">Address:</label> {{ form.address }}</p>
 9 <p><label for="id_city">City:</label> {{ form.city }}</p>
10 <p><label for="id_state">State:</label> {{ form.state }}</p>
11 <p><label for="id_zip_code">Zip:</label> {{ form.zip_code }}</p>
12 <p><label for="id_place_type">Place type:</label> {{ form.place_type }}</p>
13 <input type="submit" />
14 </form>
15 {% endblock %}
16 

テンプレート

テンプレートについての意識

レイアウト機能

レイアウト機能

base.html

 1 <head>
 2     <link rel="stylesheet" href="style.css" />
 3     <title>{% block title %}My amazing site{% endblock %}</title>
 4 </head>
 5 <body>
 6     <div id="sidebar">{% block sidebar %}
 7     <ul>
 8         <li><a href="/">Home</a></li>
 9         <li><a href="/blog/">Blog</a></li>
10     </ul>{% endblock %}
11     </div>
12     <div id="content">
13         {% block content %}{% endblock %}
14     </div>
15 </body>
16 

レイアウト機能

child.html

 1 {% extends "base.html" %}
 2 
 3 {% block title %}My amazing blog{% endblock %}
 4 
 5 {% block content %}
 6 {% for entry in blog_entries %}
 7     <h2>{{ entry.title }}</h2>
 8     <p>{{ entry.body }}</p>
 9 {% endfor %}
10 {% endblock %}
11 

レイアウト機能

レンダリング結果

 1 <head>
 2     <link rel="stylesheet" href="style.css" />
 3     <title>My amazing blog</title>
 4 </head>
 5 <body>
 6     <div id="sidebar"><ul><li><a href="/">Home</a></li><li><a href="/blog/">Blog</a></li></ul></div>
 7     <div id="content">
 8         <h2>Entry one</h2>
 9         <p>This is my first entry.</p>
10         <h2>Entry two</h2>
11         <p>This is my second entry.</p>
12     </div>
13 </body>
14 

その他

標準で様々な機能を用意

バッテリー積んでます!!

Session

request.session['has_commented'] = True

認証

Ajax

GenericView

GenericView

urls.py

 1 from django.conf.urls.defaults import *
 2 from django_website.apps.blog.models import Entry
 3 
 4 info_dict = {'queryset': Entry.objects.all(),
 5              'date_field': 'pub_date'}
 6 
 7 urlpatterns = patterns('django.views.generic.date_based',
 8    (r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/(?P<slug>[-\w]+)/$',
 9    'object_detail', dict(info_dict, slug_field='slug')),
10    (r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/$',
11    'archive_day', info_dict),
12    (r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/$',
13    'archive_month', info_dict),
14    (r'^(?P<year>\d{4})/$', 'archive_year',  info_dict),
15    (r'^/?$', 'archive_index', info_dict),
16 )

GenericView

GenericView

list_object.py

 1 def object_list(request, queryset, paginate_by=None, page=None,
 2         allow_empty=False, template_name=None, template_loader=loader,
 3         extra_context=None, context_processors=None, template_object_name='object',
 4         mimetype=None):
 5     """
 6     Generic list of objects.
 7 
 8     Templates: ``<app_label>/<model_name>_list.html``
 9     Context:
10         object_list
11             list of objects
12         is_paginated
13             are the results paginated?
14         results_per_page
15             number of objects per page (if paginated)
16         has_next

GenericView

list_object.py

17             is there a next page?
18         has_previous
19             is there a prev page?
20         page
21             the current page
22         next
23             the next page
24         previous
25             the previous page
26         pages
27             number of pages, total
28         hits
29             number of objects, total
30     """
31     if extra_context is None: extra_context = {}
32     queryset = queryset._clone()

GenericView

list_object.py

33     if paginate_by:
34         paginator = ObjectPaginator(queryset, paginate_by)
35         if not page:
36             page = request.GET.get('page', 1)
37         try:
38             page = int(page)
39             object_list = paginator.get_page(page - 1)
40         except (InvalidPage, ValueError):
41             if page == 1 and allow_empty:
42                 object_list = []
43             else:
44                 raise Http404
45         c = RequestContext(request, {
46             '%s_list' % template_object_name: object_list,
47             'is_paginated': paginator.pages > 1,
48             'results_per_page': paginate_by,

GenericView

list_object.py

49             'has_next': paginator.has_next_page(page - 1),
50             'has_previous': paginator.has_previous_page(page - 1),
51             'page': page,
52             'next': page + 1,
53             'previous': page - 1,
54             'pages': paginator.pages,
55             'hits' : paginator.hits,
56         }, context_processors)
57     else:
58         c = RequestContext(request, {
59             '%s_list' % template_object_name: queryset,
60             'is_paginated': False
61         }, context_processors)
62         if not allow_empty and len(queryset) == 0:
63             raise Http404
64     for key, value in extra_context.items():

GenericView

list_object.py

65         if callable(value):
66             c[key] = value()
67         else:
68             c[key] = value
69     if not template_name:
70         model = queryset.model
71         template_name = "%s/%s_list.html" % (model._meta.app_label, model._meta.object_name.lower())
72     t = template_loader.get_template(template_name)
73     return HttpResponse(t.render(c), mimetype=mimetype)
74 

Cache

1 >>> cache.set('my_key', 'hello, world!', 30)
2 >>> cache.get('my_key')
3 'hello, world!'
4 

signal、dispatcher

db/__init__.py

    dispatcher.connect(connection.close, signal=signals.request_finished)

core/handlers/modpython.py

    dispatcher.send(signal=signals.request_finished)

機能拡張

機能拡張

Middleware

SessionMiddleware

 1 class SessionMiddleware(object):
 2     def process_request(self, request):
 3         request.session = SessionWrapper(request.COOKIES.get(settings.SESSION_COOKIE_NAME, None))
 4 
 5     def process_response(self, request, response):
 6         # If request.session was modified, or if response.session was set, save
 7         # those changes and set a session cookie.
 8         patch_vary_headers(response, ('Cookie',))
 9         try:
10             modified = request.session.modified
11         except AttributeError:
12             pass
13         else:
14             if modified or settings.SESSION_SAVE_EVERY_REQUEST:
15                 session_key = request.session.session_key or Session.objects.get_new_session_key()
16                 if settings.SESSION_EXPIRE_AT_BROWSER_CLOSE:
17                     max_age = None
18                     expires = None
19                 else:
20                     max_age = settings.SESSION_COOKIE_AGE
21                     expires = datetime.datetime.strftime(datetime.datetime.utcnow() + datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE), "%a, %d-%b-%Y %H:%M:%S GMT")
22                 new_session = Session.objects.save(session_key, request.session._session,
23                     datetime.datetime.now() + datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE))
24                 response.set_cookie(settings.SESSION_COOKIE_NAME, session_key,
25                     max_age=max_age, expires=expires, domain=settings.SESSION_COOKIE_DOMAIN,
26                     secure=settings.SESSION_COOKIE_SECURE or None)
27         return response

Decorator

Decorator

 1 def user_passes_test(test_func, login_url=LOGIN_URL):
 2     """
 3     Decorator for views that checks that the user passes the given test,
 4     redirecting to the log-in page if necessary. The test should be a callable
 5     that takes the user object and returns True if the user passes.
 6     """
 7     def _dec(view_func):
 8         def _checklogin(request, *args, **kwargs):
 9             if test_func(request.user):
10                 return view_func(request, *args, **kwargs)
11             return HttpResponseRedirect('%s?%s=%s' % (login_url, REDIRECT_FIELD_NAME, quote(request.get_full_path())))
12         _checklogin.__doc__ = view_func.__doc__
13         _checklogin.__dict__ = view_func.__dict__
14 
15         return _checklogin
16     return _dec
17 

運用

運用

パフォーマンス

Djangoの今後

Djangoの今後

まとめ

まとめ

Thank you