Upload
makoto-tsuyuki
View
114
Download
10
Embed Size (px)
Citation preview
スゴイ Django based on Django 1.11 and Python 3.6.n
makoto tsuyuki - UNCOVER TRUTH Inc.
Python&Djangoで始めるWeb開発 in 札幌 #1 2017.11.11
お前、誰よ
tsuyukimakoto a.k.a everes
鎌倉から来ました
django-ja 初代ドメインホルダー
最初に買ったPythonの本
「Pythonテクニカルリファレンス」
株式会社UNCOVER TRUTHで働いてます。エンジニア探してます
https://www.uncovertruth.co.jp/ja/userdive/
djangoとの関わり
2005年7月から触ってます
2006年2月に4人でdjango-jaを始めました
2009年頃までDjango勉強会を開催してた
djangoとは?
Django Reinhardt
Web Application
Framework
Vs Ruby on Rails
Django beats …
Do you know Zope?
Django before 1.0
そもそもは2003年から
Django before 1.0
Django to be The Python Web Framework
by Guido van Rossum @SciPy2006
Full Stack
MVC vs MTV
Full Stack
MVC vs MTV
Model = Model
Full Stack
MVC vs MTV
View ≠ Template
Full Stack
MVC vs MTV
Controller ≠ View
Full Stack
Stable
ドキュメンテーションされたAPIが無くなる時は特定のプロセスを経て消される
7年前のSoozyConのスライドを見返していたけれど、当時と設計思想は変わっていない
Secure
NASA
Mozilla
https://docs.djangoproject.com/en/dev/internals/security/#reporting-security
admin
RoRのScaffoldに対して、djangoにはadminがある
https://youtu.be/pkETpPayyFc
Fundamentals
設計思想
ルースカップリング
コード量の低減
迅速な開発
DRY (Don’t repeat yourself) 則
暗示的より明示的に
一貫性
https://docs.djangoproject.com/en/1.11/misc/design-philosophies/
API stability• ドキュメンテーションされたAPがstable
• remain in the API for
at least two feature
releases.
https://docs.djangoproject.com/en/1.11/misc/api-stability/
HTTP
Requestを受けて
処理をして
Responseを返す
WSGI
Python Web Server Gateway Interface
PEP333, 3333
引数を2つ受け取るCallableを返す
1つ目はRequestに関する情報
2つ目はCallback
django.core.handlers.WSGIHandler あたりから追っていくと全体が見えます
Request to Response
djangoのMiddleware
__call__ (Viewの実行前)
URLconfを使ってHandlerがURLに対応するViewを探す
process_view
Viewが実行される(Responseが生成される)
process_template_response
__call__ (Viewの実行後)
process_exception
>>> class A:
... def __call__(self):
... print('called!')
...
>>> a = A()
>>> a()
called!
>>> def outer():
... def inner():
... print('hello world')
... return inner
...
>>> o = outer()
>>> o()
hello world
デフォルトのMiddlewareMIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
Request
ResponseView
blog/middlewares.py
hokkaido/settings.py
呼び出すとfirst middlewqre before view
second middlewqre before view
django.core.handlers.BaseHandler._get_response: resolver.resolve
first middlewqre process_view
second middlewqre process_view
IndexView: get
second middlewqre process_template_response
first middlewqre process_template_response
second middlewqre after view
first middlewqre after view
Model
Modelに全部書く勢い
DBのテーブル作成に必要な情報も
入力チェックに必要な情報も
関連も書く
ビジネスロジック的なものも書く
blogアプリケーションのEntryモデル
ModelForm
>>> from blog.models import Entry
>>> from django.forms import ModelForm
>>> class EntryForm(ModelForm):
... class Meta:
... model = Entry
... excludes = ['created']
>>>
>>> form = EntryForm(dict(slug='test3', title='MF'))
>>> form.is_valid()
False
>>> form.errors
{'released': ['This field is required.'], 'author': ['This field is
required.’]}
ModelForm
>>> entry = Entry.objects.get(pk=1)
>>> form = EntryForm(instance=entry)
>>> form.as_ul()'<li><label for="id_slug">Slug:</label> <input type="text" name="slug" value="test"
maxlength="50" required id="id_slug" /> <span class="helptext">Part of the URL describing the
content</span></li>\n<li><label for="id_title">Title:</label> <input type="text" name="title"
value="北海道へ来ました!" maxlength="128" required id="id_title" /></li>\n<li><label
for="id_released">Release date / time:</label> <input type="text" name="released"
value="2017-11-05 17:55:34" required id="id_released" /><input type="hidden" name="initial-
released" value="2017-11-05 17:55:34" id="initial-id_released" /></li>\n<li><label
for="id_author">Author:</label> <select name="author" required id="id_author">\n <option
value="">---------</option>\n\n <option value="1" selected>makoto</option>\n\n</select></li>'
manage.py shellで遊ぶ
$ python manage.py shell
>>> from blog.models import Entry
>>> from django.contrib.auth import get_user_model
>>> me =
get_user_model().objects.filter(email='[email protected]')[
0]
>>> me
<User: makoto>
manage.py shellで遊ぶ
>>> entry = Entry(slug='test', title='北海道へ来ました!',
author=me)
>>> entry.save()
/Users/makoto/venv/hokkaido/lib/python3.6/site-
packages/django/db/models/fields/__init__.py:1451:
RuntimeWarning: DateTimeField Entry.released
received a naive datetime (2017-11-05
17:55:34.238327) while time zone support is active.
RuntimeWarning)
manage.py shellで遊ぶ
>>> for entry in Entry.objects.all():
... print(f'{entry.pk} {entry.slug}: {entry.title} by
{entry.author.username}')
...
1 test: 北海道へ来ました! by makoto
manage.py shellで遊ぶ
>>> me.entry_set.all()[0].title
'北海道へ来ました!'
me は django.contrib.auth.models.User
manage.py shellで遊ぶ
>>> me.entry_set.create(slug='test2', title=‘meのEntry
を追加')
<Entry: Entry object>
>>> _.author
<User: makoto>
Queryset
Entry.objects.all()は条件が格納されたQuerysetのインスタンスを返します
🤔
QuerysetLOGGING = {
'version': 1,'handlers': {
'console': {'level': 'DEBUG','class': 'logging.StreamHandler',
},},'loggers': {
'django.db.backends': {'handlers': ['console'],'level': 'DEBUG',
},},
}
Queryset
>>> from blog.models import Entry
>>> qs = Entry.objects.all()
>>> list(qs)
(0.001) SELECT "blog_entry"."id", "blog_entry"."slug",
"blog_entry"."title", "blog_entry"."released",
"blog_entry"."created", "blog_entry"."author_id" FROM
"blog_entry"; args=()
[<Entry: Entry object>, <Entry: Entry object>]
Queryset - filter
>>> qs1 = qs.filter(slug='test')
>>> list(qs)
[<Entry: Entry object>, <Entry: Entry object>]
>>> list(qs1)
(0.000) SELECT "blog_entry"."id", "blog_entry"."slug",
"blog_entry"."title", "blog_entry"."released",
"blog_entry"."created", "blog_entry"."author_id" FROM
"blog_entry" WHERE "blog_entry"."slug" = 'test'; args=('test',)
[<Entry: Entry object>]
Queryset - exclude>>> qs2 = qs.exclude(slug='test')
>>> list(qs2)
(0.000) SELECT "blog_entry"."id", "blog_entry"."slug",
"blog_entry"."title", "blog_entry"."released",
"blog_entry"."created", "blog_entry"."author_id" FROM
"blog_entry" WHERE NOT ("blog_entry"."slug" = 'test');
args=('test',)
[<Entry: Entry object>]
Queryset -関連>>> Entry.objects.filter(author__email='[email protected]')
(0.000) SELECT "blog_entry"."id", "blog_entry"."slug",
"blog_entry"."title", "blog_entry"."released",
"blog_entry"."created", "blog_entry"."author_id" FROM
"blog_entry" INNER JOIN "auth_user" ON
("blog_entry"."author_id" = "auth_user"."id") WHERE
"auth_user"."email" = '[email protected]' LIMIT 21;
args=('[email protected]',)
<QuerySet [<Entry: Entry object>, <Entry: Entry object>]>
Queryset - exact>>> qs.exclude(slug__exact='test')
(0.000) SELECT "blog_entry"."id", "blog_entry"."slug",
"blog_entry"."title", "blog_entry"."released",
"blog_entry"."created", "blog_entry"."author_id" FROM
"blog_entry" WHERE NOT ("blog_entry"."slug" = 'test') LIMIT 21;
args=('test',)
<QuerySet [<Entry: Entry object>]>
Queryset - Q>>> from django.db.models import Q
>>> Entry.objects.filter(Q(slug='test')|Q(slug='test2'))
(0.001) SELECT "blog_entry"."id", "blog_entry"."slug", "blog_entry"."title",
"blog_entry"."released", "blog_entry"."created", "blog_entry"."author_id" FROM
"blog_entry" WHERE ("blog_entry"."slug" = 'test' OR "blog_entry"."slug" = 'test2')
LIMIT 21; args=('test', ‘test2')
<QuerySet [<Entry: Entry object>, <Entry: Entry object>]>
>>> Entry.objects.filter(Q(slug__contains='t')&Q(slug__contains='2'))
(0.000) SELECT "blog_entry"."id", "blog_entry"."slug", "blog_entry"."title",
"blog_entry"."released", "blog_entry"."created", "blog_entry"."author_id" FROM
"blog_entry" WHERE ("blog_entry"."slug" LIKE '%t%' ESCAPE '\' AND
"blog_entry"."slug" LIKE '%2%' ESCAPE '\') LIMIT 21; args=('%t%', '%2%')
<QuerySet [<Entry: Entry object>]>
Q(slug='test')|Q(slug='test2')
Q(slug__contains='t')&Q(slug__contains='2')
Queryset - and>>> Entry.objects.filter(
slug='test', author__email__contains='gmail'
).exclude(
author__email__startswith='spam'
)
(0.000) SELECT "blog_entry"."id", "blog_entry"."slug",
"blog_entry"."title", "blog_entry"."released", "blog_entry"."created",
"blog_entry"."author_id" FROM "blog_entry" INNER JOIN
"auth_user" ON ("blog_entry"."author_id" = "auth_user"."id")
WHERE ("blog_entry"."slug" = 'test' AND "auth_user"."email" LIKE
'%gmail%' ESCAPE '\' AND NOT ("auth_user"."email" LIKE
'spam%' ESCAPE '\')) LIMIT 21; args=('test', '%gmail%', 'spam%')
<QuerySet [<Entry: Entry object>]>
Queryset - slice>>> Entry.objects.all()[1]
>>> Entry.objects.all()[:1]
>>> Entry.objects.all()[1:2]
>>> Entry.objects.all()[::2]
Queryset - others…annotate, order_by, reverse, distinct, values, values_list, dates,
datetimes, none, union, intersection, difference, select_related,
prefetch_related, extra, defer, only, using, select_for_update,
raw, Methods that do not return QuerySets, get, create,
get_or_create, update_or_create, bulk_create, count, in_bulk,
iterator, With server-side cursors, Without server-side cursors,
latest, earliest, first, last, aggregate, exists, update, delete,
as_manager, Field lookups, exact, iexact, contains, icontains, in,
gt, gte, lt, lte, startswith, istartswith, endswith, iendswith, range,
date, year, month, day, week, week_day, time, hour, minute,
second, isnull, search, regex, iregex, Aggregation functions,
expression, output_field, **extra, Avg, Count, Max, Min, StdDev,
Sum, Variance
https://docs.djangoproject.com/en/1.11/ref/models/querysets/
Inheritance - abstract
from django.db import models
class CommonInfo(models.Model):
name = models.CharField(max_length=100)
age = models.PositiveIntegerField()
class Meta:
abstract = True
class Student(CommonInfo):
home_group = models.CharField(max_length=5)
https://docs.djangoproject.com/en/1.11/topics/db/models/#abstract-base-classes
Id
name
age
home_group
Inheritance - separateclass Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
class Restaurant(Place):
serves_hot_dogs =
models.BooleanField(default=False)
serves_pizza = models.BooleanField(default=False)
https://docs.djangoproject.com/en/1.11/topics/db/models/#multi-table-inheritance
id
name
address
place_ptr_id
serves_hot_dogs
serves_pizza
1to1
MultiDB -
DATABASE_ROUTERSclass AuthRouter(object):
def db_for_read(self, model, **hints):
if model._meta.app_label == 'auth':
return 'auth_db'
return None
def db_for_write(self, model, **hints):
return None
def allow_relation(self, obj1, obj2, **hints):
return None
def allow_migrate(self, db, app_label, model_name=None, **hints):
return None
https://docs.djangoproject.com/en/1.11/topics/db/multi-db/#using-routers
Template
継承
プレゼンテーションとロジックの分離
冗長さを防ぐ
XML をテンプレート言語に使わない
プログラミング言語を作り直さない
Template - variables
{{ variable }}
Template - loop
{% for variable in some_list %}
…
{% empty %}
…
{% endfor %}
• forloop.counter
• forloop.counter0
• forloop.first
• forloop.last
• etc…
Template - if
{% if variable %}
…
{% elif variable2 > 0 %}
…
{% else %}
…
{% endif %}
Template - tag
{% for o in some_list %}
<tr class="{% cycle 'row1' 'row2' %}">
...
</tr>
{% endfor %}
https://docs.djangoproject.com/en/1.11/ref/templates/builtins/
Template - filter
{{ value|linebreaksbr }}
https://docs.djangoproject.com/en/1.11/ref/templates/builtins/
Template - inheritance<html>
<head>{% block title %}プロジェクト名:{% endblock title %}</head>
<script type="text/javascript" src="common.js"></script>
{% block custom_js %}{% endblock custom_js %}
<body>
<div id="menu">{% block menu %}{% endblock menu %}</div>
<div id="content">{% block content %}{% endblock content %}</div>
<div id=”copyright”>{% block copyright %}everes{% endblock %}</div>
</body>
</html>
{% extends 'base.html% %}
{% block title %}{{ block.super }}アプリ名とか{% endblock title %}
{% block menu %}アプリレベルのメニューとか{% endblock menu %}
{% extends 'app/base.html' %}
{% block content %}内容内容内容無いよう{% endblock content %}
<head>{% block title %}プロジェクト名:{% endblock title %}</head>
{% block title %}{{ block.super }}アプリ名とか{% endblock title %}
{% block content %}内容内容内容無いよう{% endblock content %}
<head>{% block title %}プロジェクト名:{% endblock title %}</head>
<div id="content">{% block content %}{% endblock content %}</div>
base.html
app/base.html
app/some.html
View
簡潔性
リクエストオブジェクトの利用
ルースカップリング
GET と POST の使い分け
URLconf
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^articles/2003/$', views.special_case_2003),
url(r'^articles/([0-9]{4})/$', views.year_archive),
url(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive),
url(r'^articles/([0-9]{4})/([0-9]{2})/([0-9]+)/$',
views.article_detail),
]
https://docs.djangoproject.com/en/1.11/topics/http/urls/
URLconf
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^articles/2003/$', views.special_case_2003),
url(r'^articles/([0-9]{4})/$', views.year_archive),
url(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive),
url(r'^articles/([0-9]{4})/([0-9]{2})/([0-9]+)/$',
views.article_detail),
]
https://docs.djangoproject.com/en/1.11/topics/http/urls/
views.month_archive(request, ‘2017’, ’11’)
/articles/2017/11/
URLconf
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^articles/2003/$', views.special_case_2003),
url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$',
views.month_archive),
url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-
9]{2})/(?P<day>[0-9]{2})/$', views.article_detail),
]
https://docs.djangoproject.com/en/1.11/topics/http/urls/
views.month_archive(request, year=‘2017’, month=’11’)
/articles/2017/11/
View - function based
import datetime
from django.http import HttpResponse
def current_datetime(request):
now = datetime.datetime.now()
html = "<html><body>It is now %s.</body></html>" % now
return HttpResponse(html)
https://docs.djangoproject.com/en/1.11/topics/http/views/
View - class based
from django.views.generic.base import View
from django.http import HttpResponse
class IndexView(View):
def dispatch(self, request, year=None, month=None):
html = f'<html><body>{year}/{month}</body></html>'
return HttpResponse(html)
https://docs.djangoproject.com/en/1.11/topics/http/views/
View - class based
from django.views.generic.base import View
from django.http import HttpResponse
class IndexView(View):
def dispatch(self, request, year=None, month=None):
html = f'<html><body>{year}/{month}</body></html>'
return HttpResponse(html)
https://docs.djangoproject.com/en/1.11/topics/http/views/
url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', IndexView.as_view()),
View - class based
get
post
put
patch
https://docs.djangoproject.com/en/1.11/ref/class-based-views/base/#view
delete
head
options
trace
View - idiomfrom django.http import HttpResponseRedirect
from django.shortcuts import render
from .forms import MyForm
def myview(request):
if request.method == "POST":
form = MyForm(request.POST)
if form.is_valid():
# <process form cleaned data>
return HttpResponseRedirect('/success/')
else:
form = MyForm(initial={'key': 'value'})
return render(request, 'form_template.html', {'form': form})
https://docs.djangoproject.com/en/1.11/topics/class-based-views/intro/#handling-forms-with-class-based-views
View - idiom
class MyFormView(View):
form_class = MyForm
initial = {'key': 'value'}
template_name = 'form_template.html'
def get(self, request, *args, **kwargs):
form = self.form_class(initial=self.initial)
return render(request, self.template_name, {'form': form})
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_valid():
# <process form cleaned data>
return HttpResponseRedirect('/success/')
return render(request, self.template_name, {'form': form})
https://docs.djangoproject.com/en/1.11/topics/class-based-views/intro/#handling-forms-with-class-based-views
Generic View
TemplateView
from django.views.generic import TemplateView
class AboutView(TemplateView):
template_name = "about.html"
url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', TemplateView.as_view(template_name=‘about.html’)),
Generic display views
DetailView
ListView
from django.views.generic.list import ListView
from articles.models import Article
class ArticleListView(ListView):
model = Article
https://docs.djangoproject.com/en/1.11/ref/class-based-views/generic-display/
Generic editing views
FormView / CreateView / UpdateView / DeleteView
https://docs.djangoproject.com/en/1.11/ref/class-based-views/generic-editing/
from django.views.generic.edit import CreateView
from myapp.models import Author
class AuthorCreate(CreateView):
model = Author
fields = ['name']
Signal
アプリケーション間で連携しつつルースカップリングに保つ
https://docs.djangoproject.com/en/1.11/topics/signals/
Signalをreceiveする
from django.db.models.signals import post_save
from django.dispatch import receiver
from myapp.models import MyModel
@receiver(post_save, sender=MyModel)
def my_handler(sender, **kwargs):
...
https://docs.djangoproject.com/en/1.11/topics/signals/#connecting-to-signals-sent-by-specific-senders
Signalを独自定義する
import django.dispatch
pizza_done = django.dispatch.Signal(
providing_args=["toppings", “size"]
)
https://docs.djangoproject.com/en/1.11/topics/signals/#defining-and-sending-signals
Signalを独自定義する
https://docs.djangoproject.com/en/1.11/topics/signals/#defining-and-sending-signals
class PizzaStore(object):
def send_pizza(self, toppings, size):
pizza_done.send(sender=self.__class__,
toppings=toppings, size=size)
...
–本丸にたどり着いた
“ReUsable Application”
ProjectとApplication
Project
settings.py
urls.py
from django.conf.urls import include, url
urlpatterns = [
url(r'^community/', include(‘app_a.urls')),
url(r'^contact/', include(‘app_b.urls’)),
]
Application
models.py
views.py
admin.py
apps.py
urls.py
templates (フォルダ)
Application - templates
models.py
views.py
admin.py
apps.py
urls.py
templates (フォルダ)
Application - urls.py
app_name = ‘blog'
urlpatterns = [
url(r'^articles/2003/$', views.special_case_2003,
name=‘special_case_2003’),
url(r'^articles/([0-9]{4})/$', views.year_archive,
name=‘year_archive’),
]
{% url ‘blog:year_archive' 2003 %}
Application - auth
from django.contrib.auth impor get_user_model
UserModel = get_user_model()
django.conf.global_settings
DEFAULT_FILE_STORAGE
FILE_UPLOAD_HANDLERS
MIDDLEWARE_CLASSES
SESSION_ENGINE
CACHES
AUTHENTICATION_BACKENDS
DEFAULT_EXCEPTION_REPORTER_FILTER
TEST_RUNNER
STATICFILES_STORAGE
STATICFILES_FINDERS
to see next
https://docs.djangoproject.com/
https://github.com/django/django/
https://sentry.io/https://github.com/getsentry/sentry
http://pinaxproject.com
http://amzn.to/2hQU2Be
Two scoops djangoでググれば出てきますけどね
Any Question?