SlideShare une entreprise Scribd logo
1  sur  97
Télécharger pour lire hors ligne
Djangoによる 
スマホアプリ 
バックエンドの実装 
PyCon JP 2014
自己紹介 
Yuichi Nakazawa 
@y_nakazawa1220 
(株)日本システム技研 所属 
スマホアプリのバックエンド開発 
  GEEKLAB.NAGANO 管理人
自己紹介 
Kazuhiko Kakita 
@kaki_k 
(株)日本システム技研 所属 
スマホアプリのバックエンド開発 
  GEEKLAB.NAGANO 管理人
株式会社 日本システム技研 
http://jsl.co.jp
キャスタリア株式会社 
【goocus pro】 
”モバイル&ソーシャル”をコンセプトに設計された、 
”Mobile Native”なラーニングプラットフォーム『goocus 
pro』 
『B2B』 
SaaS型のサービスとして、 
企業様・学校様等にご提供 
『B2B2B』『B2B2C』 
教育ビジネスを展開される企業 
様にプラットフォームとしてご 
提供 
プログラミング学習が必 
修の通信制高等学校「コー 
ドアカデミー高等学校」 
を設立しました 
『「ソーシャルラーニン 
グ」入門 ソーシャルメ 
ディアがもたらす人と組 
織の知識革命』の翻訳を 
手掛けました 
国内外の先端的な教育/学習の最新情 
報をお届けするブログを運営していま 
す 
日本オープンオンライン教育推進 
協議会『JMOOC』に正会員として 
http://www.castalia.co.jp 参加しています
GEEKLAB. NAGANO 
http://geeklab-nagano.com
GEEKLAB. NAGANO 
GEEKLAB. NAGANOとは 
• 地元のエンジニアを集めて 
勉強会・セミナーを開催 
• 知識・ノウハウの集積基地 
• 長野からのITの発信を!!
GEEKLAB. NAGANO 
設備(全部無料です!) 
椅子 
テーブルソファー 
単焦点プロジェクター 
非破壊スキャナ 
IT書籍、雑誌 
ホワイトボード 
インターネット接続(WiFi, 有線) 
Apple TV 
電子工作機器 
自販機 
スライム・・
GEEKLAB. NAGANO 
利用時間・運営 
• 利用可能日時:平日は9-18時頃(勉強会・セミナー以 
外)。土日祝日は問い合わせ要 
• 運営:                          学校法人 信学会 
株式会社日本システム技研(JSL) 
キャスタリア株式会社
長野に来られた際は、 
是非お立ち寄りを!!
はじめに 
• モバイルファースト! 
• スマートフォンと連携した 
バックエンド開発が沢山出てくる時代
Djangoでバックエンドを作ろう!
Djangoのメリット 
• 学習コストが低い 
• フルスタックのフレームワーク 
• scaffoldは無いけど、管理サイトが秀逸
管理サイト ログイン画面
管理サイト Model選択
管理サイト 一覧画面
管理サイト 追加画面
アプリケーションの形態 
• 管理サイト + API < 最低限これだけあればOK 
• 管理サイト + CMS + API < ここまであれば完璧
CMS部分を作る
モデル定義 
• 基本となる親子関係のモデルを作る 
たとえば、このようなモデル 
書籍 
1:多 
感想
models.py の例 
# -*- coding: utf-8 -*- 
from django.db import models 
! 
class Book(models.Model): 
'''書籍''' 
name = models.CharField(u'書籍名', max_length=255) 
publisher = models.CharField(u'出版社', max_length=255, blank=True) 
page = models.IntegerField(u'ページ数', blank=True, default=0) 
def __str__(self): # Python2: def __unicode__(self): 
return self.name 
class Impression(models.Model): 
'''感想''' 
book = models.ForeignKey(Book, verbose_name=u'書籍', related_name='impressions') 
comment = models.TextField(u'コメント', blank=True) 
def __str__(self): # Python2: def __unicode__(self): 
return self.comment 
• models.ForeignKeyがみそ 
• これがDBの定義となり、CREATE TABLE文はDjangoが作ってくれる
ORM (Object Relation Mapping) 
• 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のリレーションで出来ること 
1.多対一のリレーション 
ForeignKey 
Manufacturer 
Car 
1:多 
2.多対多のリレーション 
ManyToManyField 
Topping Pizza 
多:多 
再帰的リレーション 
(自分自身に対する多対一のリレーション) も可 
(中間モデル) 
中間モデルは、DB上に隠しテーブルができるが、 
意識しなくてよい 
再帰的リレーション 
(自分自身に対する多対多のリレーション) も可
ORMのリレーションで出来ること 
3.エクストラフィールドで多対多のリレーション 
ManyToManyField の through 引数 
Person Group 
多:多 
Membership 
中間モデルに項目を持たせて、自分で定義したい場合 
4.一対一のリレーション 
OneToOneField 
Place 
1:1 
Restaurant 
モデルを継承して項目追加する代わりに 
OneToOneField で項目追加したモデルを作る 
! 
継承ができないかというと、そうではない
モデルの継承 
1.抽象ベースクラス 
CommonInfo 
継承 
Student 
親は実体を持たない 
class CommonInfo(models.Model): 
class Meta: 
abstract = True 
2.マルチテーブル継承 
Place 
継承 
Restaurant 
親も子も実体を持つ 
class Place(models.Model): 
class Student(CommonInfo): class Restaurant(Place): 
3.プロキシモデル 
User 
継承 
MyUser 
from django.contrib.auth.models import User 
子は実体を持たない 
子は項目追加できない 
親のメソッドを拡張したい時 
class MyUser(User): 
class Meta: 
proxy = True 
! 
def do_something(self): 
...
一般化リレーション 
1.一般化リレーション 
色々な親モデルにタグを付けたい場合など 
User 
TaggedItem 
class TaggedItem(models.Model): 
tag = models.SlugField() 
content_type = models.ForeignKey(ContentType) 
object_id = models.PositiveIntegerField() 
content_object = generic.GenericForeignKey('content_type', 'object_id') 
逆参照しなければ 
tags = generic.GenericRelation(TaggedItem) 
は不要 
Bookmark 
class Bookmark(models.Model): 
url = models.URLField() 
tags = generic.GenericRelation(TaggedItem) 
一般化リレーション (Generic Relations) または、 
多態性リレーション (Polymorphic Relations) とも呼ばれる 
! 
これだけ、公式ドキュメントで離れた場所にあって、気付きにくいが、ORMでできることの1つ 
en: https://docs.djangoproject.com/en/1.6/ref/contrib/contenttypes/#id1 
jp: http://docs.djangoproject.jp/en/latest/ref/contrib/contenttypes.html#generic-relations
ORMまとめ 
• 様々なリレーションの作り方をご紹介しました。 
• これらを駆使してモデル図を設計すれば、作りたい 
データベースのモデル定義ができると思います。
アグリゲーションを使いこなす 
• アグリゲーション(Aggregation 集約) 
• 意地でもSQLを書かないために、これを極めることが大切 
• パフォーマンスを出す上でも、読んで回すロジックではなく、 
SQL一発に変換されるよう、集約をとことん使う 
# 最も高額な書籍 
>>> from django.db.models import Max 
>>> Book.objects.all().aggregate(Max('price')) 
{'price__max': Decimal('81.20')} 
! 
# 出版社ごとの書籍数を "num_books"属性で 
>>> from django.db.models import Count 
>>> pubs = Publisher.objects.annotate(num_books=Count('book')) 
>>> pubs 
[<Publisher BaloneyPress>, <Publisher SalamiPress>, ...] 
>>> pubs[0].num_books 
73
DB migration 
• Django 1.6まではSouth - http://south.aeracode.org 
• Django 1.7からは標準として取り込まれた 
• モデル変更が楽 
• model.py の定義変更をDBに反映させることができる 
• modelを直すとmigrateファイルを作ってくれる 
(某フレームワークとは逆)
DB migration 
Django 1.7からは半強制になった? 
アプリケーションを作成する 
$ python manage.py startapp myapp 
この時、myapp/migrations/__init__.py ができる 
これがあるとmigation対象、消すと対象外になる 
1.6のチュートリアルを見ると、初回の syncdb はなくなって 
以下の2つのコマンドに分かれた 
$ python manage.py migrate # スーパーユーザーは作られない 
$ python manage.py createsuperuser # 作りたい場合は、任意で実行
DB migration 
新たなアプリケーションを作って、models.pyを書いた初回 
class Book(models.Model): 
: 
page = models.IntegerField(u'ページ数', blank=True, default=0)
DB migration 
makemigrationsコマンド(models.pyの変更を拾う) 
$ python manage.py makemigrations myapp 
makemigrationsが作成したマイグレーション ファイルを確認 
myproj/myapp/migrations/0001_initial.py 
などといったファイルができているので、エディタで確認する 
migrateコマンドで、変更をDBに反映する 
$ python manage.py migrate myapp 
新たなモデルがテーブルとしてDBに作成される
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 
makemigrationsコマンド(models.pyの変更を拾う) 
$ python manage.py makemigrations myapp 
makemigrationsが作成したマイグレーション ファイルを確認 
myproj/myapp/migrations/0002_book_isbn.py 
などといったファイルができているので、エディタで確認する 
migrateコマンドで、変更をDBに反映する 
$ python manage.py migrate myapp 
モデルの項目追加/変更がDBのテーブルに反映される
Bootstrapを使う 
• CSSフレームワーク http://getbootstrap.com/ 
• エンジニアだけで作っても見栄えを良くする
Bootstrap 
• Djangoのテンプレートは継承できるので、以下のように 
BootstrapのJS、CSS 
を定義したベース 
Navbar 
ヘッダーのナビバー 
Navbarを使わないもの 
ログイン など 
Navbarを使うもの 
CMSの各種ページ 
base.html 
base_navi.html 
login.html などindex.html など
Bootstrap 
使い方としては 
• 一覧系のページは、Bootstrapのclassを使って普通に書く 
• フォーム系のページは、django-bootstrap-form 
https://github.com/tzangms/django-bootstrap-form 
を使う 
$ pip install django-bootstrap-form
base.html 
Bootstrapの例 
{% load staticfiles %} 
<!DOCTYPE html> 
<html lang="{{ LANGUAGE_CODE|default:"en-us" }}"> 
<head> 
<meta charset="UTF-8"> 
<title>{% block title %}Title{% 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-1.11.1.min.js' %}"></script> 
<script src="{% static 'js/bootstrap.min.js' %}"></script> 
{% block extrahead %}{% endblock %} 
</head> 
<body> 
{% block navbar %}{% endblock %} 
<div class="container"> 
{% block content %} 
{{ content }} 
{% endblock %} 
</div> 
</body> 
</html> 
Bootstrap の JS、CSSを記述する 
ベースとなるテンプレート
base_navi.html 
Bootstrapの例 
{% extends "base.html" %} 
! 
{% block navbar %} 
<nav class="navbar navbar-default" role="navigation"> 
<div class="container-fluid"> 
← base.html を継承 
← base.html の navbar ブロックを置き換え 
<!-- Brand and toggle get grouped for better mobile display --> 
<div class="navbar-header"> 
<button type="button" class="navbar-toggle" data-toggle=“collapse” data-target="#bs-example-navbar-collapse- 
1"> 
<span class="sr-only">Toggle navigation</span> 
<span class="icon-bar"></span> 
<span class="icon-bar"></span> 
<span class="icon-bar"></span> 
</button> 
<a class="navbar-brand" href="{% url 'mock:index' %}”>Brand name</a> 
</div> 
<!-- Collect the nav links, forms, and other content for toggling --> 
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> 
<ul class="nav navbar-nav navbar-right"> 
<li class="dropdown"> 
<a href="#" class="dropdown-toggle" data-toggle="dropdown">{{ user.username }} <span 
class="caret"></span></a> 
<ul class="dropdown-menu" role="menu"> 
<li><a href="{% url 'logout' %}">Log out</a></li> 
</ul> 
</li> 
</ul> 
</div> 
</div> 
</nav> 
{% endblock %} 
Bootstrap の Navbar のみを定義
index.html 
{% extends “base_navi.html" %} 
! 
{% block title %}書籍の一覧{% endblock title %} 
! 
{% block content %} 
← base_navi.html を継承 
← base.html の title ブロックを置き換え 
← base.html の 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> 
</tr> 
</thead> 
<tbody> 
{% for book in books %} 
<tr> 
<td>{{ book.id }}</td> 
<td>{{ book.name }}</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 %} 
Bootstrapの例 
↑ 一覧系は Bootstrap の class を使って普通に書く
book_edit.html 
{% 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 %} 
Bootstrapの例 
← django-bootstrap-form を使っているので 
  Form の項目を Bootstrap 形式で展開してくれる
django-bootstrap-formのテクニック 
form を丸ごと出す 
{{ form|bootstrap_horizontal }} 
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は 
bootstrapform/templates/bootstrapfrom/field.html 
でやっていることを真似ること
CRUDの書き方 
scaffold はないので、手で書くが、それほど大変ではない。 
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)) 
views.py 一覧
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': 
form = BookForm(request.POST, instance=book) # POST された request データからフォームを作成 
if form.is_valid(): # フォームのバリデーション 
form.save() 
return redirect('cms:book_list') 
else: # GET の時 
form = BookForm(instance=book) # book インスタンスからフォームを作成 
return render_to_response('cms/book_edit.html', 
dict(form=form, book_id=book_id), 
context_instance=RequestContext(request)) 
views.py 登録/修正 
forms.py 
class BookForm(ModelForm): 
'''書籍のフォーム''' 
class Meta: 
model = Book 
fields = ('name', 'publisher', 'page', )
CRUDの書き方 
views.py 削除 
def book_del(request, book_id): 
'''書籍の削除''' 
book = get_object_or_404(Book, pk=book_id) 
book.delete() 
return redirect('cms:book_list') 
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'), # 削除 
)
Listページの書き方 
• django.views.generic.list.ListView を使っておくと、ページネートが簡単 
• Bootstrapのページネート部品とも相性がいい 
views.py 一覧 
class ImpressionList(ListView): 
'''感想の一覧''' 
context_object_name='impressions' 
template_name='cms/impression_list.html' 
paginate_by = 2 # 1ページは最大2件ずつでページングする 
! 
def get(self, request, *args, **kwargs): 
book = get_object_or_404(Book, pk=kwargs['book_id']) # 親の書籍を読む 
impressions = book.impressions.all().order_by('id') # 書籍の子供の、感想を読む 
self.object_list = impressions 
context = self.get_context_data(object_list=self.object_list, book=book) 
return self.render_to_response(context)
Listページの書き方 
impression_list.html のページング部分 
{% if is_paginated %} 
<ul class="pagination"> 
{% if page_obj.has_previous %} 
<li><a href="?page={{ page_obj.previous_page_number }}">&laquo;</a></li> 
{% else %} 
<li class="disabled"><a href="#">&laquo;</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 }}">&raquo;</a></li> 
{% else %} 
<li class="disabled"><a href="#">&raquo;</a></li> 
{% endif %} 
</ul> 
{% endif %}
Listページの書き方 
ページングの表示例 
この部分
APIの実装
Django REST framework 
とかもあるけど・・    
レスポンスを自前で 
JSONで書く
辞書をsimplejson.dumps()で返した場合 
def org_list(request): 
'''会社の一覧''' 
orgs = [] 
for org in Organization.objects.all(): 
orgs.append(dict(id=org.id, name=org.name)) 
data = dict(status='ok', response_code= '000', message='Success', org_list=orgs) 
json = simplejson.dumps(data, ensure_ascii=False, indent=2) 
return HttpResponse(json, mimetype='application/json; charset=UTF-8')
項目が順不同になる・・ 
{ 
"org_list": [ 
{ 
"id": 1, 
"name": "Japan System Laboratory" 
}, 
{ 
"id": 2, 
"name": "GEEKLAB.NAGANO" 
} 
], 
"status": "ok", 
"message": "Success", 
"response_code": "000" 
}
アプリ開発者から文句 
を言われるw・・
なので・・
OrderedDict()で順序付き辞書にする 
順序付き辞書を使いましょう!(Python 2.7~) 
from collections import OrderedDict 
def org_list(request): 
''' 会社の一覧を返す ''' 
orgs = [] 
for org in Organization.objects.all(): 
orgs.append(dict(id=org.id, name=org.name)) 
data = OrderedDict([('status', 'ok'), ('response_code', '000'), ('message', 'Success'), 
('org_list',orgs)]) 
json = simplejson.dumps(data, ensure_ascii=False, indent=2) 
return HttpResponse(json, mimetype='application/json; charset=UTF-8')
ちゃんとコード通りSortされる 
{ 
"status": "ok", 
"response_code": "000", 
"message": "Success", 
"org_list": [ 
{ 
"name": "Japan System Laboratory", 
"id": 1 
}, 
{ 
"name": "GEEKLAB.NAGANO", 
"id": 2 
} 
] 
}
Postの場合 
• 簡単なデータの場合は、CMSのForm受信と同じものを 
書いて、スマホ側では http form post を模倣してもら 
う。 
◦ こうすることによって、データのエラーチェックは、 
フォームのバリデーションの仕組みが使えます。 
◦ 正常かエラーかは、JSONで結果を返すようにしま 
す。 
! 
• 繰り返しがある複雑なデータは、スマホ側からJSONを 
POSTしてもらい、json.loads() でデコードする
簡単なデータの場合 
formの作成 
#ログインフォーム 
class MemberLoginForm(forms.Form): 
email = forms.CharField(label='email', max_length=255) 
password = forms.CharField(label='password', max_length=255) 
ファンクションの作成 
@csrf_exempt 
def user_login(request): 
if request.method == 'POST': 
form = MemberLoginForm(request.POST) 
if not form.is_valid(): 
email = form.cleaned_data['email'] 
password = form.cleaned_data['password'] 
data = OrderedDict([ ('status', 'ng'), ('response_code', '001'), ('message', form.errors) ]) 
return render_json_response(request, data) 
return render_json_response(request, data) 
else: 
form = MemberLoginForm() 
return render_to_response('api/user_login.html', dict(form=form), 
context_instance=RequestContext(request))
簡単なデータの場合
複雑なデータの場合 
formの作成(JSONのテンプレをplaceholderで表示してあげると親切) 
JSON_QUIZ_RESPONSE = ''' 
{ 
"quiz_questions":[ 
{ 
"quiz_question_id":2, 
"checked_quiz_options": [ 
{"quiz_option_id":6} 
] 
} 
] 
} 
''' 
# 4択クイズ回答フォーム 
class ModuleQuizResponseForm(forms.Form): 
user_id = forms.IntegerField(label='user_id') # ユーザID 
json_string = forms.CharField(label='json_string', widget=forms.Textarea, initial=JSON_QUIZ_RESPONSE)
複雑なデータの場合 
この部分
複雑なデータの場合 
ファンクションの作成(json.loadsでJSONを解析) 
@csrf_exempt 
def module_quiz_response(request, module_id): 
if request.method == 'POST': 
form = ModuleQuizResponseForm(request.POST) 
if form.is_valid(): 
'''省略''' 
! 
# JSON文字列の取り出し 
json_string = form.cleaned_data['json_string'] 
json_obj = json.loads(json_string) 
analyze_quiz_questions = [] 
# クイズ回答ログ、初回回答の更新 
for json_question in json_obj['quiz_questions']: 
quiz_question_id = json_question['quiz_question_id'] 
'''省略''' 
data = OrderedDict([ ('status', 'ok'), ('response_code', '000'), ('message', 'Success'), ]) 
return render_json_response(request, data) 
else: 
form = ModuleQuizResponseForm() 
return render_to_response('api/module_quiz_response.html', dict(form=form, 
module_id=module_id), context_instance=RequestContext(request))
PUSH通知
to iOS
APNs 
(Apple Notification 
Service)
PyAPNs 
• 最新バージョン 1.1.2(今のところPython3は未対応) 
https://github.com/djacobs/PyAPNs 
• 事前に証明書ファイル・キーファイルを作成しておく 
$ pip install git+git://github.com/djacobs/PyAPNs.git 
※普通に入れると、期待したものが入らない可能性があるので、 
 GitHubからインストール
1デバイスへのPUSH通知 
from apns import APNs, Frame, Payload 
def send_push_message(token_hex): 
apns = APNs(use_sandbox=True, cert_file='xxx.pem', key_file='xxx_key-noenc. 
pem') 
  payload = Payload(alert="Hello World!", sound="default", badge=1) 
# Send a notification 
apns.gateway_server.send_notification(token_hex, payload)
複数デバイスへのPUSH通知 
最新の形式(frame形式?) 
from apns import APNs, Payload 
def send_push_message(token_hex): 
apns = APNs(use_sandbox=False, cert_file='xxx.pem', key_file='xxx-noenc.pem') 
! 
# 対象のデバイスのトークンをまとめる 
tokens = ['xxxxxxxxxxxxxxxxxx','xxxxxxxxxxxxxxxxxx'] 
! 
payload = Payload(alert="Hello World!", sound="default", badge=1) 
# 一括でPUSH 
frame = Frame() 
identifier = 1 
expiry = time.time()+3600 
priority = 10 # 即座に通知 
for token in tokens: 
frame.add_item(token, payload, identifier, expiry, priority) 
apns.gateway_server.send_notification_multiple(frame)
feedbackで返された 
トークンは、削除する 
for (token_hex, fail_time) in apns.feedback_server.items(): 
#未使用のデバイストークンを削除 
for token in DeviceToken.objects.filter(token=token_hex): 
token.delete() 
!
実装上のポイント 
• デバイストークンを収集する仕組み > API 
*ユーザの複数端末持ちを考慮 
• ペイロードのサイズ制限は256バイト > 冗長した 
メッセージは「・・・」等で調整
to Android
GCM 
(Google Cloud Message)
python-gcm 
• 最新バージョンは 0.1.5 
https://github.com/geeknam/python-gcm 
• APIキーを事前にGoogle API Consoleから取得 
$ pip install python-gcm
python-gcmの使用例 
# APIキーを渡して、GCMオブジェクトを作成 
gcm = GCM('XXXXXXXXXXXXXXXXXXXXXXXXX') 
! 
# registration idを指定する 
reg_ids = ['XXXXXXXXXXXXX','XXXXXXXXXXXXX','XXXXXXXXXXXXX'] 
data = {'alert': 'テスト!!' } 
! 
# PUSH 
response = gcm.json_request(registration_ids=reg_ids, data=data) 
if 'canonical' in response: 
#GCMサーバーがcanonical idを返したきた場合、現状のデバイストークン(register id)をこちらに置き換える 
for canonical_id, reg_id in response['canonical'].items(): 
for token in DeviceToken.objects.filter(device_token=reg_id): 
token.device_token = canonical_id 
token.save()
実装上のポイント 
• デバイストークンを収集する仕組み > API 
*ユーザの複数端末持ちを考慮(APNsと同様) 
• ペイロードのサイズ制限は4096バイト > 気にし 
なくて良いレベル
ログイン パスワードの暗号化 
• サーバ/スマホ間のパスワード通信を暗号化したい 
• iOS/Android/Python で共通で暗号化/復号化できる 
ベストなプロトコルは何か 
• AESがよい(AES ECBモード) 
AESの暗号化はバイナリ値になるのでBASE64に変換 
• pycrypto を使う 
https://www.dlitz.net/software/pycrypto/ 
$ pip install pycrypto
ログイン パスワードの暗号化 
AES 復号化の部分 
from Crypto.Cipher import AES 
from Crypto import Random 
! 
def aes_decrypt(string, key=None): 
''' AESで復号化 ''' 
if not key or len(key) not in (16, 128, 192, 256): 
raise ValueError('Key size must be 16, 128, 192, 256') 
bs = AES.block_size 
iv = Random.new().read(bs) 
cipher = AES.new(key.encode(), AES.MODE_ECB, iv) 
! 
plaintext = cipher.decrypt(string) 
return plaintext.decode().rstrip('0')
ログイン パスワードの暗号化 
BASE64のデコード 
import base64 
! 
def base64url_decode(input): 
''' BASE64のデコード ''' 
rem = len(input) % 4 
if rem > 0: 
input += '=' * (4 - rem) 
try: 
return base64.urlsafe_b64decode(input.encode()).decode() # return str 
except UnicodeDecodeError: 
return base64.urlsafe_b64decode(input.encode()) # return byte 
ログイン処理のパスワード復号化 
AES_KEY = getattr(settings, 'AES_KEY', 'SomeAesKey16byte') 
password_decrypt = aes_decrypt(base64url_decode(password), AES_KEY)
ログイン連携 
• Twitter/Facebook などの OAuth 2.0 連携は、 
python-social-authで用意されている 
- https://github.com/omab/python-social-auth 
• OpenID Connect でログイン連携したい 
- 今後多くなると思われ 
• Yahoo Janan! の OpenID Connect (YConnect) 
の胸を借りる 
- 公開してくれている Yahoo Janan! に感謝を!
ログイン連携 
• python-social-auth の拡張モジュールを書く 
- 本家でもOpenID Connectは未対応? 
- 自分で書くことにした 
‣ ベースはOAuth2.0で行ける 
‣ OpenIDっぽいnonceの処理がある 
‣ JWT (JSON Web Token)のデコードを追加 
• サンプルはGitHub Gistを参照(長いので割愛) 
https://gist.github.com/kakky/6809432
アプリケーションを公開する
AWSで公開する 
iOS/Android 
アプリAmazon EC2 
Mobile 
Client 
DBサーバー 
(MySQL) 
Amazon RDS 
画像、音声、映像 
Amazon S3 
Email Amazon SES 
AWS SDK for Python 
(boto) 
普通に 
SMTPサーバー 
として指定 
IPアドレス指定 
量が少ない場合は 
GMail、Google Apps 
で済ませてしまう場合もあり
botoによるAmazon S3連携 
画像ファイル等をS3に追い出すために、まずはこれ 
$ pip install boto 
S3 の bucket と key の関係 
• URLに変換する際に、bucketはホスト名の一部になるため、全世界で 
一意にする 
• keyの部分は、/を使って任意にフォルダ的なものを作ることができる 
• bucket=my-bucket-name、key=path/to/image.jpg とすると、 
以下のようなURLを生成できる 
https://my-bucket-name.s3.amazonaws.com/path/to/image.jpg
botoによるAmazon S3連携 
S3へのアップロードと、パブリックなURLの取得 
import boto, mimetypes, os 
from boto.s3.key import Key 
! 
def s3_upload_media(file_path, s3_bucket, s3_key, do_delete=True): 
'''S3へのアップロードと、URLの取得''' 
conn = boto.connect_s3() 
b = conn.get_bucket(s3_bucket) 
k = Key(b) 
k.key = s3_key 
k.set_metadata("Content-Type", mimetypes.guess_type(k.key)[0]) 
k.set_contents_from_filename(file_path) # アップロード 
k.set_acl('public-read') # アクセス権を設定し、URLで見れるようにする 
s3_url = k.generate_url(3600, query_auth=False) #バケットとキーからURLを生成 
if do_delete: 
os.remove(file_path) # 元ファイルの削除 
return s3_url # DBには、このURL(と削除のためにs3_key)を格納する 
※S3へのaccess_key、secret_access_keyなどのCredentialは、 
~/.boto に置いてあると仮定
• Djangoアプリケーションのデプロイは以下を使用 
- nginx : Webサーバ 
- uWSGI : アプリケーション コンテナ サーバ 
‣ 姉妹サービスを同一ホストで公開することも踏まえ 
‣ emperor/vassals(皇帝/家臣)モードを使用 
uWSGI 
vassal 
uWSGI 
vassal 
nginx 
nginx + uWSGI 
uWSGI 
emperor 
皇帝 
家臣/家来? 
サービス1 
仮想ホスト1 
サービス2 
仮想ホスト2 
upstream 起動
nginxの設定 
upstream django-myservice { 
server unix:/tmp/uwsgi-myservice.sock; 
} 
server { 
listen 80; 
server_name www.myservice.com; 
uwsgi_buffer_size 4k; 
uwsgi_buffers 32 4k; 
: 
location /static/admin { 
alias /usr/lib/python2.7/site-packages/django/contrib/admin/static/admin; 
} 
location /static { 
alias /var/www/django/myservice/static; 
} 
location /media { 
alias /var/www/django/myservice/media; 
} 
location / { 
include uwsgi_params; 
uwsgi_pass django-myservice; 
} 
}
uWSGI emperorの設定 
# /etc/uwsgi.yaml 
uwsgi: 
emperor: /etc/uwsgi/vassals 
uid: nginx 
gid: nginx 
logfile-chmod: 644 
daemonize: /var/log/uwsgi/emperor.log 
touch-logreopen: /tmp/uwsgi-log-reopen.txt 
emperor側は、/etc/uwsgi/vassels/ の下にある 
vasselsの設定ファイルを起動せよ、と書いてあるだけ 
この /etc/uwsgi.yaml は、 
/etc/rc.d/init.d/uwsgi にスクリプトを書いて 
  $ sudo service uwsgi start 
にて起動できるようにしているが、長いので割愛(すみません)
uWSGI emperorの設定 
とはいえ、後からスライドを見て、コピペしたい人用に 
/etc/rc.d/init.d/uwsgi のスクリプトを貼っておきます 
#!/bin/sh 
# 
# /etc/rc.d/init.d/uwsgi 
# 
# uwsgi - this script starts and stops the uwsgi daemon 
# 
# chkconfig: - 85 15 
# processname: uwsgi 
# config: /etc/uwsgi.yaml 
# config: /etc/sysconfig/uwsgi 
# pidfile: /var/run/uwsgi.pid 
# description: uwsgi is a WSGI server 
#! # Source function library. 
.! /etc/rc.d/init.d/functions C!ONFFILE="/etc/uwsgi.yaml" if [ -f /etc/sysconfig/uwsgi ]; then 
. /etc/sysconfig/uwsgi 
f!i prog=uwsgi 
uwsgi=${NGINX-/usr/bin/uwsgi} 
conffile=${CONFFILE-/etc/uwsgi.yaml} 
lockfile=${LOCKFILE-/var/lock/subsys/uwsgi} 
pidfile=${!PIDFILE-/var/run/uwsgi.pid} 
RETVAL=0 start() ! { 
echo -n $"Starting $prog: " #daemon --pidfile=${pidfile} ${uwsgi} --yaml ${conffile} 
daemon ${uwsgi} --yaml ${conffile} --pidfile ${pidfile} 
RETVAL=$? 
echo 
[ $RETVAL = 0 ] && touch ${lockfile} 
return $RETVAL 
}! stop() { 
echo -n $"Stopping $prog: " 
killproc -p ${pidfile} ${prog} -INT 
RETVAL=$? 
echo 
[ $RETVAL = 0 ] && rm -f ${lockfile} ${pidfile} 
}! # See how we were called. 
case "$1" in 
start) 
start 
;; 
stop) 
stop 
;; 
status) 
status -p ${pidfile} ${uwsgi} 
RETVAL=$? 
;; 
restart) 
stop 
start 
;; 
*) 
echo $"Usage: $prog {start|stop|restart|status}" 
RETVAL=2 
e!sac exit $RETVAL 
今、見えなくても怒らないで (́・ω・`)
uWSGI vassalsの設定 
# /etc/uwsgi/vassals/myservice_uwsgi.yaml 
uwsgi: 
socket: /tmp/uwsgi-myservice.sock 
chmod-socket: 666 
chdir: /var/www/django/myservice/ 
wsgi-file: myservice/uwsgi.py 
master: true 
enable-threads: true 
pidfile: /tmp/uwsgi-myservice-master.pid 
processes: 2 
threads: 30 
stats: 127.0.0.1:9191 
no-orphans: true 
touch-reload: /tmp/uwsgi-myservice-reload.txt 
uid: nginx 
gid: nginx 
vacuum: true 
import: uwsgi_autoreload 
logfile-chmod: 644 
log-date: [%%a %%b %%d %%H:%%M:%%S %%Y] 
daemonize: /var/log/uwsgi/myservice.log 
disable-logging: true 
touch-logreopen: /tmp/uwsgi-log-reopen.txt 
listen: 4096 
正直、パラメータ大杉 
! 
性能が出る/出ない 
エラー吐く/吐かない 
はパラメータ次第 
Apache+mod_wsgi 
の方が、よろしくやっ 
てくれた感がある
uWSGIでオートリロード 
• 開発サーバと同じく、コードをデプロイしたら、 
自動的に再起動して反映してほしい 
• 果たして、プロダクションでそれをやっていいかは 
議論の余地があるが、便利なので設定する
uWSGIでオートリロード 
プロジェクトのディレクトリ直下に、uwsgi_autoreload.py 
というコードを置く 
# -*- coding: utf-8 -*- 
# nginx + uWSGI で実行した時、ソースコードの変更を検知して、uWSGIを再起動する 
# 
# 注) import uwsgi は uWSGI 配下で実行した時のみ参照できる 
# from uwsgidecorators も同様 
# どちらもローカル開発時は Unresolved import のままでよい 
! 
import uwsgi 
from uwsgidecorators import timer 
from django.utils import autoreload 
! 
@timer(3) # 3秒ごとに呼ばれる 
def change_code_gracefull_reload(sig): 
if autoreload.code_changed(): 
print(‘code change detected. autoreload ——————————————————————‘) 
uwsgi.reload()
uWSGIでオートリロード 
uWSGI vassals の設定ファイルで指定する 
# /etc/uwsgi/vassals/myservice_uwsgi.yaml 
uwsgi: 
: 
: 
import: uwsgi_autoreload 
: 
:
Qiitaにチュートリアル書きました 
http://qiita.com/kaki_k/items/511611cadac1d0c69c54
Qiitaにチュートリアル書きました 
• 「Django入門」でググると、 
一番上に出てきてビビリます 
• Djangoを使う人の裾野を広げたいと思い 
書きました。 
• 公式チュートリアルと合わせて、 
新しい人材の育成にご活用下さい。
まとめ 
• コード部分は小さい字が多くてすみません。 
• スライドは後ほど公開しますので、小さくて見えな 
かった部分は、後で見返して下さい。 
• ということで、 
スマートフォンとの連携案件を、 
Djangoを使ってどんどん作りましょう!
ご清聴ありがとうございました

Contenu connexe

Tendances

高速!Clojure Web 開発入門
高速!Clojure Web 開発入門高速!Clojure Web 開発入門
高速!Clojure Web 開発入門Kazuki Tsutsumi
 
ソーシャルゲーム案件におけるDB分割のPHP実装
ソーシャルゲーム案件におけるDB分割のPHP実装ソーシャルゲーム案件におけるDB分割のPHP実装
ソーシャルゲーム案件におけるDB分割のPHP実装infinite_loop
 
ブラック企業から学ぶMVCモデル
ブラック企業から学ぶMVCモデルブラック企業から学ぶMVCモデル
ブラック企業から学ぶMVCモデルYuta Hiroto
 
MvcのFatモデルに立ち向かう
MvcのFatモデルに立ち向かうMvcのFatモデルに立ち向かう
MvcのFatモデルに立ち向かうShun Hikita
 
Prometheus入門から運用まで徹底解説
Prometheus入門から運用まで徹底解説Prometheus入門から運用まで徹底解説
Prometheus入門から運用まで徹底解説貴仁 大和屋
 
入社1年目のプログラミング初心者がSpringを学ぶための手引き
入社1年目のプログラミング初心者がSpringを学ぶための手引き入社1年目のプログラミング初心者がSpringを学ぶための手引き
入社1年目のプログラミング初心者がSpringを学ぶための手引き土岐 孝平
 
SPAセキュリティ入門~PHP Conference Japan 2021
SPAセキュリティ入門~PHP Conference Japan 2021SPAセキュリティ入門~PHP Conference Japan 2021
SPAセキュリティ入門~PHP Conference Japan 2021Hiroshi Tokumaru
 
やはりお前らのMVCは間違っている
やはりお前らのMVCは間違っているやはりお前らのMVCは間違っている
やはりお前らのMVCは間違っているKoichi Tanaka
 
なかったらINSERTしたいし、あるならロック取りたいやん?
なかったらINSERTしたいし、あるならロック取りたいやん?なかったらINSERTしたいし、あるならロック取りたいやん?
なかったらINSERTしたいし、あるならロック取りたいやん?ichirin2501
 
外部キー制約に伴うロックの小話
外部キー制約に伴うロックの小話外部キー制約に伴うロックの小話
外部キー制約に伴うロックの小話ichirin2501
 
Ormとの付き合い方
Ormとの付き合い方Ormとの付き合い方
Ormとの付き合い方豊明 尾古
 
Microsoft Intune を用いたパッチ管理
Microsoft Intune を用いたパッチ管理Microsoft Intune を用いたパッチ管理
Microsoft Intune を用いたパッチ管理Yutaro Tamai
 
イミュータブルデータモデル(入門編)
イミュータブルデータモデル(入門編)イミュータブルデータモデル(入門編)
イミュータブルデータモデル(入門編)Yoshitaka Kawashima
 
Multipeer connectivityを使った 動画のリアルタイム端末間共有
Multipeer connectivityを使った 動画のリアルタイム端末間共有Multipeer connectivityを使った 動画のリアルタイム端末間共有
Multipeer connectivityを使った 動画のリアルタイム端末間共有Imajin Kawabe
 
マルチスレッド問題の特定と再現に頑張った話
マルチスレッド問題の特定と再現に頑張った話マルチスレッド問題の特定と再現に頑張った話
マルチスレッド問題の特定と再現に頑張った話LINE Corporation
 
Java ORマッパー選定のポイント #jsug
Java ORマッパー選定のポイント #jsugJava ORマッパー選定のポイント #jsug
Java ORマッパー選定のポイント #jsugMasatoshi Tada
 
世界一わかりやすいClean Architecture
世界一わかりやすいClean Architecture世界一わかりやすいClean Architecture
世界一わかりやすいClean ArchitectureAtsushi Nakamura
 
ソーシャルゲームのためのデータベース設計
ソーシャルゲームのためのデータベース設計ソーシャルゲームのためのデータベース設計
ソーシャルゲームのためのデータベース設計Yoshinori Matsunobu
 
BigQuery Query Optimization クエリ高速化編
BigQuery Query Optimization クエリ高速化編BigQuery Query Optimization クエリ高速化編
BigQuery Query Optimization クエリ高速化編sutepoi
 
やってはいけない空振りDelete
やってはいけない空振りDeleteやってはいけない空振りDelete
やってはいけない空振りDeleteYu Yamada
 

Tendances (20)

高速!Clojure Web 開発入門
高速!Clojure Web 開発入門高速!Clojure Web 開発入門
高速!Clojure Web 開発入門
 
ソーシャルゲーム案件におけるDB分割のPHP実装
ソーシャルゲーム案件におけるDB分割のPHP実装ソーシャルゲーム案件におけるDB分割のPHP実装
ソーシャルゲーム案件におけるDB分割のPHP実装
 
ブラック企業から学ぶMVCモデル
ブラック企業から学ぶMVCモデルブラック企業から学ぶMVCモデル
ブラック企業から学ぶMVCモデル
 
MvcのFatモデルに立ち向かう
MvcのFatモデルに立ち向かうMvcのFatモデルに立ち向かう
MvcのFatモデルに立ち向かう
 
Prometheus入門から運用まで徹底解説
Prometheus入門から運用まで徹底解説Prometheus入門から運用まで徹底解説
Prometheus入門から運用まで徹底解説
 
入社1年目のプログラミング初心者がSpringを学ぶための手引き
入社1年目のプログラミング初心者がSpringを学ぶための手引き入社1年目のプログラミング初心者がSpringを学ぶための手引き
入社1年目のプログラミング初心者がSpringを学ぶための手引き
 
SPAセキュリティ入門~PHP Conference Japan 2021
SPAセキュリティ入門~PHP Conference Japan 2021SPAセキュリティ入門~PHP Conference Japan 2021
SPAセキュリティ入門~PHP Conference Japan 2021
 
やはりお前らのMVCは間違っている
やはりお前らのMVCは間違っているやはりお前らのMVCは間違っている
やはりお前らのMVCは間違っている
 
なかったらINSERTしたいし、あるならロック取りたいやん?
なかったらINSERTしたいし、あるならロック取りたいやん?なかったらINSERTしたいし、あるならロック取りたいやん?
なかったらINSERTしたいし、あるならロック取りたいやん?
 
外部キー制約に伴うロックの小話
外部キー制約に伴うロックの小話外部キー制約に伴うロックの小話
外部キー制約に伴うロックの小話
 
Ormとの付き合い方
Ormとの付き合い方Ormとの付き合い方
Ormとの付き合い方
 
Microsoft Intune を用いたパッチ管理
Microsoft Intune を用いたパッチ管理Microsoft Intune を用いたパッチ管理
Microsoft Intune を用いたパッチ管理
 
イミュータブルデータモデル(入門編)
イミュータブルデータモデル(入門編)イミュータブルデータモデル(入門編)
イミュータブルデータモデル(入門編)
 
Multipeer connectivityを使った 動画のリアルタイム端末間共有
Multipeer connectivityを使った 動画のリアルタイム端末間共有Multipeer connectivityを使った 動画のリアルタイム端末間共有
Multipeer connectivityを使った 動画のリアルタイム端末間共有
 
マルチスレッド問題の特定と再現に頑張った話
マルチスレッド問題の特定と再現に頑張った話マルチスレッド問題の特定と再現に頑張った話
マルチスレッド問題の特定と再現に頑張った話
 
Java ORマッパー選定のポイント #jsug
Java ORマッパー選定のポイント #jsugJava ORマッパー選定のポイント #jsug
Java ORマッパー選定のポイント #jsug
 
世界一わかりやすいClean Architecture
世界一わかりやすいClean Architecture世界一わかりやすいClean Architecture
世界一わかりやすいClean Architecture
 
ソーシャルゲームのためのデータベース設計
ソーシャルゲームのためのデータベース設計ソーシャルゲームのためのデータベース設計
ソーシャルゲームのためのデータベース設計
 
BigQuery Query Optimization クエリ高速化編
BigQuery Query Optimization クエリ高速化編BigQuery Query Optimization クエリ高速化編
BigQuery Query Optimization クエリ高速化編
 
やってはいけない空振りDelete
やってはいけない空振りDeleteやってはいけない空振りDelete
やってはいけない空振りDelete
 

En vedette

AWS クラウドで構築するスマホアプリ バックエンド
AWS クラウドで構築するスマホアプリ バックエンドAWS クラウドで構築するスマホアプリ バックエンド
AWS クラウドで構築するスマホアプリ バックエンドkaki_k
 
通信対戦ゲームを作った話
通信対戦ゲームを作った話通信対戦ゲームを作った話
通信対戦ゲームを作った話mipsparc
 
スマートフォン向けサービスにおけるサーバサイド設計入門
スマートフォン向けサービスにおけるサーバサイド設計入門スマートフォン向けサービスにおけるサーバサイド設計入門
スマートフォン向けサービスにおけるサーバサイド設計入門Hisashi HATAKEYAMA
 
日曜プログラマーが
1週間くらいで通信対戦ゲームを作ってみた
日曜プログラマーが
1週間くらいで通信対戦ゲームを作ってみた日曜プログラマーが
1週間くらいで通信対戦ゲームを作ってみた
日曜プログラマーが
1週間くらいで通信対戦ゲームを作ってみたYuusuke Takeuchi
 
Djangoフレームワークの紹介
Djangoフレームワークの紹介Djangoフレームワークの紹介
Djangoフレームワークの紹介Shinya Okano
 
新入社員のための大規模ゲーム開発入門 サーバサイド編
新入社員のための大規模ゲーム開発入門 サーバサイド編新入社員のための大規模ゲーム開発入門 サーバサイド編
新入社員のための大規模ゲーム開発入門 サーバサイド編infinite_loop
 
自宅で出来る!ゲームサーバの作り方
自宅で出来る!ゲームサーバの作り方自宅で出来る!ゲームサーバの作り方
自宅で出来る!ゲームサーバの作り方光晶 上原
 
ゲームサーバ開発現場の考え方
ゲームサーバ開発現場の考え方ゲームサーバ開発現場の考え方
ゲームサーバ開発現場の考え方Daisaku Mochizuki
 
年の瀬!リアルタイム通信ゲームサーバ勉強会
年の瀬!リアルタイム通信ゲームサーバ勉強会年の瀬!リアルタイム通信ゲームサーバ勉強会
年の瀬!リアルタイム通信ゲームサーバ勉強会monobit
 
 〜デザイン初心者向け〜
 デザイン時に気をつけると幸せになれる事
 〜デザイン初心者向け〜
 デザイン時に気をつけると幸せになれる事 〜デザイン初心者向け〜
 デザイン時に気をつけると幸せになれる事
 〜デザイン初心者向け〜
 デザイン時に気をつけると幸せになれる事kenji goto
 
Webデザインのセオリーを学ぼう
Webデザインのセオリーを学ぼうWebデザインのセオリーを学ぼう
Webデザインのセオリーを学ぼうToshiaki Sasaki
 
色彩センスのいらない配色講座
色彩センスのいらない配色講座色彩センスのいらない配色講座
色彩センスのいらない配色講座Mariko Yamaguchi
 

En vedette (12)

AWS クラウドで構築するスマホアプリ バックエンド
AWS クラウドで構築するスマホアプリ バックエンドAWS クラウドで構築するスマホアプリ バックエンド
AWS クラウドで構築するスマホアプリ バックエンド
 
通信対戦ゲームを作った話
通信対戦ゲームを作った話通信対戦ゲームを作った話
通信対戦ゲームを作った話
 
スマートフォン向けサービスにおけるサーバサイド設計入門
スマートフォン向けサービスにおけるサーバサイド設計入門スマートフォン向けサービスにおけるサーバサイド設計入門
スマートフォン向けサービスにおけるサーバサイド設計入門
 
日曜プログラマーが
1週間くらいで通信対戦ゲームを作ってみた
日曜プログラマーが
1週間くらいで通信対戦ゲームを作ってみた日曜プログラマーが
1週間くらいで通信対戦ゲームを作ってみた
日曜プログラマーが
1週間くらいで通信対戦ゲームを作ってみた
 
Djangoフレームワークの紹介
Djangoフレームワークの紹介Djangoフレームワークの紹介
Djangoフレームワークの紹介
 
新入社員のための大規模ゲーム開発入門 サーバサイド編
新入社員のための大規模ゲーム開発入門 サーバサイド編新入社員のための大規模ゲーム開発入門 サーバサイド編
新入社員のための大規模ゲーム開発入門 サーバサイド編
 
自宅で出来る!ゲームサーバの作り方
自宅で出来る!ゲームサーバの作り方自宅で出来る!ゲームサーバの作り方
自宅で出来る!ゲームサーバの作り方
 
ゲームサーバ開発現場の考え方
ゲームサーバ開発現場の考え方ゲームサーバ開発現場の考え方
ゲームサーバ開発現場の考え方
 
年の瀬!リアルタイム通信ゲームサーバ勉強会
年の瀬!リアルタイム通信ゲームサーバ勉強会年の瀬!リアルタイム通信ゲームサーバ勉強会
年の瀬!リアルタイム通信ゲームサーバ勉強会
 
 〜デザイン初心者向け〜
 デザイン時に気をつけると幸せになれる事
 〜デザイン初心者向け〜
 デザイン時に気をつけると幸せになれる事 〜デザイン初心者向け〜
 デザイン時に気をつけると幸せになれる事
 〜デザイン初心者向け〜
 デザイン時に気をつけると幸せになれる事
 
Webデザインのセオリーを学ぼう
Webデザインのセオリーを学ぼうWebデザインのセオリーを学ぼう
Webデザインのセオリーを学ぼう
 
色彩センスのいらない配色講座
色彩センスのいらない配色講座色彩センスのいらない配色講座
色彩センスのいらない配色講座
 

Similaire à Djangoによるスマホアプリバックエンドの実装

Rails初心者レッスン lesson3 3edition
Rails初心者レッスン lesson3 3editionRails初心者レッスン lesson3 3edition
Rails初心者レッスン lesson3 3editionSatomi Tsujita
 
XPagesDay2014 A-4 XPages with jQueryMobile BADプラクティスガイド
XPagesDay2014 A-4 XPages with jQueryMobile BADプラクティスガイドXPagesDay2014 A-4 XPages with jQueryMobile BADプラクティスガイド
XPagesDay2014 A-4 XPages with jQueryMobile BADプラクティスガイドTetsuji Hayashi
 
Scalaでプログラムを作りました
Scalaでプログラムを作りましたScalaでプログラムを作りました
Scalaでプログラムを作りましたTomoharu ASAMI
 
2012 05-19第44回cocoa勉強会発表資料
2012 05-19第44回cocoa勉強会発表資料2012 05-19第44回cocoa勉強会発表資料
2012 05-19第44回cocoa勉強会発表資料OCHI Shuji
 
Jqm20120210
Jqm20120210Jqm20120210
Jqm20120210cmtomoda
 
Mashup Caravan in オープンソースカンファレンス2011 Hiroshima: infoScoop OpenSource
Mashup Caravan in オープンソースカンファレンス2011 Hiroshima: infoScoop OpenSourceMashup Caravan in オープンソースカンファレンス2011 Hiroshima: infoScoop OpenSource
Mashup Caravan in オープンソースカンファレンス2011 Hiroshima: infoScoop OpenSourcecmutoh
 
Webフロントエンド開発の最新トレンド - HTML5, モバイル, オフライン
Webフロントエンド開発の最新トレンド - HTML5, モバイル, オフラインWebフロントエンド開発の最新トレンド - HTML5, モバイル, オフライン
Webフロントエンド開発の最新トレンド - HTML5, モバイル, オフラインShumpei Shiraishi
 
Magento meet up Tokyo#1 for Design
Magento meet up Tokyo#1 for DesignMagento meet up Tokyo#1 for Design
Magento meet up Tokyo#1 for DesignMiho Nakano
 
データマイニング+WEB勉強会資料第6回
データマイニング+WEB勉強会資料第6回データマイニング+WEB勉強会資料第6回
データマイニング+WEB勉強会資料第6回Naoyuki Yamada
 
scala+liftで遊ぼう
scala+liftで遊ぼうscala+liftで遊ぼう
scala+liftで遊ぼうyouku
 
jQuery Mobile(開発編)勉強会資料
jQuery Mobile(開発編)勉強会資料jQuery Mobile(開発編)勉強会資料
jQuery Mobile(開発編)勉強会資料Nobumasa Ura
 
空のテンプレートから始めるWindows 8 ストアアプリ
空のテンプレートから始めるWindows 8 ストアアプリ空のテンプレートから始めるWindows 8 ストアアプリ
空のテンプレートから始めるWindows 8 ストアアプリAkira Hatsune
 
ASP.NET MVC 2 ~新機能の紹介~
ASP.NET MVC 2 ~新機能の紹介~ASP.NET MVC 2 ~新機能の紹介~
ASP.NET MVC 2 ~新機能の紹介~Yoshitaka Seo
 
jQuery Mobile 最新情報 & Tips
jQuery Mobile 最新情報 & TipsjQuery Mobile 最新情報 & Tips
jQuery Mobile 最新情報 & Tipsyoshikawa_t
 
Ec cube開発合宿 プラグインセミナー
Ec cube開発合宿 プラグインセミナーEc cube開発合宿 プラグインセミナー
Ec cube開発合宿 プラグインセミナーAyumu Kawaguchi
 
初めてのPadrino
初めてのPadrino初めてのPadrino
初めてのPadrinoTakeshi Yabe
 

Similaire à Djangoによるスマホアプリバックエンドの実装 (20)

Rails初心者レッスン lesson3 3edition
Rails初心者レッスン lesson3 3editionRails初心者レッスン lesson3 3edition
Rails初心者レッスン lesson3 3edition
 
XPagesDay2014 A-4 XPages with jQueryMobile BADプラクティスガイド
XPagesDay2014 A-4 XPages with jQueryMobile BADプラクティスガイドXPagesDay2014 A-4 XPages with jQueryMobile BADプラクティスガイド
XPagesDay2014 A-4 XPages with jQueryMobile BADプラクティスガイド
 
jQuery Mobileの基礎
jQuery Mobileの基礎jQuery Mobileの基礎
jQuery Mobileの基礎
 
jQuery Mobile入門
jQuery Mobile入門jQuery Mobile入門
jQuery Mobile入門
 
UnicastWS vol.2
UnicastWS vol.2UnicastWS vol.2
UnicastWS vol.2
 
Scalaでプログラムを作りました
Scalaでプログラムを作りましたScalaでプログラムを作りました
Scalaでプログラムを作りました
 
2012 05-19第44回cocoa勉強会発表資料
2012 05-19第44回cocoa勉強会発表資料2012 05-19第44回cocoa勉強会発表資料
2012 05-19第44回cocoa勉強会発表資料
 
Jqm20120210
Jqm20120210Jqm20120210
Jqm20120210
 
Mashup Caravan in オープンソースカンファレンス2011 Hiroshima: infoScoop OpenSource
Mashup Caravan in オープンソースカンファレンス2011 Hiroshima: infoScoop OpenSourceMashup Caravan in オープンソースカンファレンス2011 Hiroshima: infoScoop OpenSource
Mashup Caravan in オープンソースカンファレンス2011 Hiroshima: infoScoop OpenSource
 
Webフロントエンド開発の最新トレンド - HTML5, モバイル, オフライン
Webフロントエンド開発の最新トレンド - HTML5, モバイル, オフラインWebフロントエンド開発の最新トレンド - HTML5, モバイル, オフライン
Webフロントエンド開発の最新トレンド - HTML5, モバイル, オフライン
 
Magento meet up Tokyo#1 for Design
Magento meet up Tokyo#1 for DesignMagento meet up Tokyo#1 for Design
Magento meet up Tokyo#1 for Design
 
データマイニング+WEB勉強会資料第6回
データマイニング+WEB勉強会資料第6回データマイニング+WEB勉強会資料第6回
データマイニング+WEB勉強会資料第6回
 
scala+liftで遊ぼう
scala+liftで遊ぼうscala+liftで遊ぼう
scala+liftで遊ぼう
 
Webteko 20090925
Webteko 20090925Webteko 20090925
Webteko 20090925
 
jQuery Mobile(開発編)勉強会資料
jQuery Mobile(開発編)勉強会資料jQuery Mobile(開発編)勉強会資料
jQuery Mobile(開発編)勉強会資料
 
空のテンプレートから始めるWindows 8 ストアアプリ
空のテンプレートから始めるWindows 8 ストアアプリ空のテンプレートから始めるWindows 8 ストアアプリ
空のテンプレートから始めるWindows 8 ストアアプリ
 
ASP.NET MVC 2 ~新機能の紹介~
ASP.NET MVC 2 ~新機能の紹介~ASP.NET MVC 2 ~新機能の紹介~
ASP.NET MVC 2 ~新機能の紹介~
 
jQuery Mobile 最新情報 & Tips
jQuery Mobile 最新情報 & TipsjQuery Mobile 最新情報 & Tips
jQuery Mobile 最新情報 & Tips
 
Ec cube開発合宿 プラグインセミナー
Ec cube開発合宿 プラグインセミナーEc cube開発合宿 プラグインセミナー
Ec cube開発合宿 プラグインセミナー
 
初めてのPadrino
初めてのPadrino初めてのPadrino
初めてのPadrino
 

Djangoによるスマホアプリバックエンドの実装

  • 2. 自己紹介 Yuichi Nakazawa @y_nakazawa1220 (株)日本システム技研 所属 スマホアプリのバックエンド開発   GEEKLAB.NAGANO 管理人
  • 3. 自己紹介 Kazuhiko Kakita @kaki_k (株)日本システム技研 所属 スマホアプリのバックエンド開発   GEEKLAB.NAGANO 管理人
  • 5. キャスタリア株式会社 【goocus pro】 ”モバイル&ソーシャル”をコンセプトに設計された、 ”Mobile Native”なラーニングプラットフォーム『goocus pro』 『B2B』 SaaS型のサービスとして、 企業様・学校様等にご提供 『B2B2B』『B2B2C』 教育ビジネスを展開される企業 様にプラットフォームとしてご 提供 プログラミング学習が必 修の通信制高等学校「コー ドアカデミー高等学校」 を設立しました 『「ソーシャルラーニン グ」入門 ソーシャルメ ディアがもたらす人と組 織の知識革命』の翻訳を 手掛けました 国内外の先端的な教育/学習の最新情 報をお届けするブログを運営していま す 日本オープンオンライン教育推進 協議会『JMOOC』に正会員として http://www.castalia.co.jp 参加しています
  • 7. GEEKLAB. NAGANO GEEKLAB. NAGANOとは • 地元のエンジニアを集めて 勉強会・セミナーを開催 • 知識・ノウハウの集積基地 • 長野からのITの発信を!!
  • 8. GEEKLAB. NAGANO 設備(全部無料です!) 椅子 テーブルソファー 単焦点プロジェクター 非破壊スキャナ IT書籍、雑誌 ホワイトボード インターネット接続(WiFi, 有線) Apple TV 電子工作機器 自販機 スライム・・
  • 9. GEEKLAB. NAGANO 利用時間・運営 • 利用可能日時:平日は9-18時頃(勉強会・セミナー以 外)。土日祝日は問い合わせ要 • 運営:                          学校法人 信学会 株式会社日本システム技研(JSL) キャスタリア株式会社
  • 11. はじめに • モバイルファースト! • スマートフォンと連携した バックエンド開発が沢山出てくる時代
  • 13. Djangoのメリット • 学習コストが低い • フルスタックのフレームワーク • scaffoldは無いけど、管理サイトが秀逸
  • 18. アプリケーションの形態 • 管理サイト + API < 最低限これだけあればOK • 管理サイト + CMS + API < ここまであれば完璧
  • 20. モデル定義 • 基本となる親子関係のモデルを作る たとえば、このようなモデル 書籍 1:多 感想
  • 21. models.py の例 # -*- coding: utf-8 -*- from django.db import models ! class Book(models.Model): '''書籍''' name = models.CharField(u'書籍名', max_length=255) publisher = models.CharField(u'出版社', max_length=255, blank=True) page = models.IntegerField(u'ページ数', blank=True, default=0) def __str__(self): # Python2: def __unicode__(self): return self.name class Impression(models.Model): '''感想''' book = models.ForeignKey(Book, verbose_name=u'書籍', related_name='impressions') comment = models.TextField(u'コメント', blank=True) def __str__(self): # Python2: def __unicode__(self): return self.comment • models.ForeignKeyがみそ • これがDBの定義となり、CREATE TABLE文はDjangoが作ってくれる
  • 22. ORM (Object Relation Mapping) • 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') # 書籍の子供の、感想を読む : : !
  • 23. ORMのリレーションで出来ること 1.多対一のリレーション ForeignKey Manufacturer Car 1:多 2.多対多のリレーション ManyToManyField Topping Pizza 多:多 再帰的リレーション (自分自身に対する多対一のリレーション) も可 (中間モデル) 中間モデルは、DB上に隠しテーブルができるが、 意識しなくてよい 再帰的リレーション (自分自身に対する多対多のリレーション) も可
  • 24. ORMのリレーションで出来ること 3.エクストラフィールドで多対多のリレーション ManyToManyField の through 引数 Person Group 多:多 Membership 中間モデルに項目を持たせて、自分で定義したい場合 4.一対一のリレーション OneToOneField Place 1:1 Restaurant モデルを継承して項目追加する代わりに OneToOneField で項目追加したモデルを作る ! 継承ができないかというと、そうではない
  • 25. モデルの継承 1.抽象ベースクラス CommonInfo 継承 Student 親は実体を持たない class CommonInfo(models.Model): class Meta: abstract = True 2.マルチテーブル継承 Place 継承 Restaurant 親も子も実体を持つ class Place(models.Model): class Student(CommonInfo): class Restaurant(Place): 3.プロキシモデル User 継承 MyUser from django.contrib.auth.models import User 子は実体を持たない 子は項目追加できない 親のメソッドを拡張したい時 class MyUser(User): class Meta: proxy = True ! def do_something(self): ...
  • 26. 一般化リレーション 1.一般化リレーション 色々な親モデルにタグを付けたい場合など User TaggedItem class TaggedItem(models.Model): tag = models.SlugField() content_type = models.ForeignKey(ContentType) object_id = models.PositiveIntegerField() content_object = generic.GenericForeignKey('content_type', 'object_id') 逆参照しなければ tags = generic.GenericRelation(TaggedItem) は不要 Bookmark class Bookmark(models.Model): url = models.URLField() tags = generic.GenericRelation(TaggedItem) 一般化リレーション (Generic Relations) または、 多態性リレーション (Polymorphic Relations) とも呼ばれる ! これだけ、公式ドキュメントで離れた場所にあって、気付きにくいが、ORMでできることの1つ en: https://docs.djangoproject.com/en/1.6/ref/contrib/contenttypes/#id1 jp: http://docs.djangoproject.jp/en/latest/ref/contrib/contenttypes.html#generic-relations
  • 27. ORMまとめ • 様々なリレーションの作り方をご紹介しました。 • これらを駆使してモデル図を設計すれば、作りたい データベースのモデル定義ができると思います。
  • 28. アグリゲーションを使いこなす • アグリゲーション(Aggregation 集約) • 意地でもSQLを書かないために、これを極めることが大切 • パフォーマンスを出す上でも、読んで回すロジックではなく、 SQL一発に変換されるよう、集約をとことん使う # 最も高額な書籍 >>> from django.db.models import Max >>> Book.objects.all().aggregate(Max('price')) {'price__max': Decimal('81.20')} ! # 出版社ごとの書籍数を "num_books"属性で >>> from django.db.models import Count >>> pubs = Publisher.objects.annotate(num_books=Count('book')) >>> pubs [<Publisher BaloneyPress>, <Publisher SalamiPress>, ...] >>> pubs[0].num_books 73
  • 29. DB migration • Django 1.6まではSouth - http://south.aeracode.org • Django 1.7からは標準として取り込まれた • モデル変更が楽 • model.py の定義変更をDBに反映させることができる • modelを直すとmigrateファイルを作ってくれる (某フレームワークとは逆)
  • 30. DB migration Django 1.7からは半強制になった? アプリケーションを作成する $ python manage.py startapp myapp この時、myapp/migrations/__init__.py ができる これがあるとmigation対象、消すと対象外になる 1.6のチュートリアルを見ると、初回の syncdb はなくなって 以下の2つのコマンドに分かれた $ python manage.py migrate # スーパーユーザーは作られない $ python manage.py createsuperuser # 作りたい場合は、任意で実行
  • 31. DB migration 新たなアプリケーションを作って、models.pyを書いた初回 class Book(models.Model): : page = models.IntegerField(u'ページ数', blank=True, default=0)
  • 32. DB migration makemigrationsコマンド(models.pyの変更を拾う) $ python manage.py makemigrations myapp makemigrationsが作成したマイグレーション ファイルを確認 myproj/myapp/migrations/0001_initial.py などといったファイルができているので、エディタで確認する migrateコマンドで、変更をDBに反映する $ python manage.py migrate myapp 新たなモデルがテーブルとしてDBに作成される
  • 33. 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) # 追加
  • 34. DB migration makemigrationsコマンド(models.pyの変更を拾う) $ python manage.py makemigrations myapp makemigrationsが作成したマイグレーション ファイルを確認 myproj/myapp/migrations/0002_book_isbn.py などといったファイルができているので、エディタで確認する migrateコマンドで、変更をDBに反映する $ python manage.py migrate myapp モデルの項目追加/変更がDBのテーブルに反映される
  • 35. Bootstrapを使う • CSSフレームワーク http://getbootstrap.com/ • エンジニアだけで作っても見栄えを良くする
  • 36. Bootstrap • Djangoのテンプレートは継承できるので、以下のように BootstrapのJS、CSS を定義したベース Navbar ヘッダーのナビバー Navbarを使わないもの ログイン など Navbarを使うもの CMSの各種ページ base.html base_navi.html login.html などindex.html など
  • 37. Bootstrap 使い方としては • 一覧系のページは、Bootstrapのclassを使って普通に書く • フォーム系のページは、django-bootstrap-form https://github.com/tzangms/django-bootstrap-form を使う $ pip install django-bootstrap-form
  • 38. base.html Bootstrapの例 {% load staticfiles %} <!DOCTYPE html> <html lang="{{ LANGUAGE_CODE|default:"en-us" }}"> <head> <meta charset="UTF-8"> <title>{% block title %}Title{% 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-1.11.1.min.js' %}"></script> <script src="{% static 'js/bootstrap.min.js' %}"></script> {% block extrahead %}{% endblock %} </head> <body> {% block navbar %}{% endblock %} <div class="container"> {% block content %} {{ content }} {% endblock %} </div> </body> </html> Bootstrap の JS、CSSを記述する ベースとなるテンプレート
  • 39. base_navi.html Bootstrapの例 {% extends "base.html" %} ! {% block navbar %} <nav class="navbar navbar-default" role="navigation"> <div class="container-fluid"> ← base.html を継承 ← base.html の navbar ブロックを置き換え <!-- Brand and toggle get grouped for better mobile display --> <div class="navbar-header"> <button type="button" class="navbar-toggle" data-toggle=“collapse” data-target="#bs-example-navbar-collapse- 1"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="{% url 'mock:index' %}”>Brand name</a> </div> <!-- Collect the nav links, forms, and other content for toggling --> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <ul class="nav navbar-nav navbar-right"> <li class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown">{{ user.username }} <span class="caret"></span></a> <ul class="dropdown-menu" role="menu"> <li><a href="{% url 'logout' %}">Log out</a></li> </ul> </li> </ul> </div> </div> </nav> {% endblock %} Bootstrap の Navbar のみを定義
  • 40. index.html {% extends “base_navi.html" %} ! {% block title %}書籍の一覧{% endblock title %} ! {% block content %} ← base_navi.html を継承 ← base.html の title ブロックを置き換え ← base.html の 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> </tr> </thead> <tbody> {% for book in books %} <tr> <td>{{ book.id }}</td> <td>{{ book.name }}</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 %} Bootstrapの例 ↑ 一覧系は Bootstrap の class を使って普通に書く
  • 41. book_edit.html {% 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 %} Bootstrapの例 ← django-bootstrap-form を使っているので   Form の項目を Bootstrap 形式で展開してくれる
  • 42. django-bootstrap-formのテクニック form を丸ごと出す {{ form|bootstrap_horizontal }} 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は bootstrapform/templates/bootstrapfrom/field.html でやっていることを真似ること
  • 43. CRUDの書き方 scaffold はないので、手で書くが、それほど大変ではない。 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)) views.py 一覧
  • 44. 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': form = BookForm(request.POST, instance=book) # POST された request データからフォームを作成 if form.is_valid(): # フォームのバリデーション form.save() return redirect('cms:book_list') else: # GET の時 form = BookForm(instance=book) # book インスタンスからフォームを作成 return render_to_response('cms/book_edit.html', dict(form=form, book_id=book_id), context_instance=RequestContext(request)) views.py 登録/修正 forms.py class BookForm(ModelForm): '''書籍のフォーム''' class Meta: model = Book fields = ('name', 'publisher', 'page', )
  • 45. CRUDの書き方 views.py 削除 def book_del(request, book_id): '''書籍の削除''' book = get_object_or_404(Book, pk=book_id) book.delete() return redirect('cms:book_list') 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'), # 削除 )
  • 46. Listページの書き方 • django.views.generic.list.ListView を使っておくと、ページネートが簡単 • Bootstrapのページネート部品とも相性がいい views.py 一覧 class ImpressionList(ListView): '''感想の一覧''' context_object_name='impressions' template_name='cms/impression_list.html' paginate_by = 2 # 1ページは最大2件ずつでページングする ! def get(self, request, *args, **kwargs): book = get_object_or_404(Book, pk=kwargs['book_id']) # 親の書籍を読む impressions = book.impressions.all().order_by('id') # 書籍の子供の、感想を読む self.object_list = impressions context = self.get_context_data(object_list=self.object_list, book=book) return self.render_to_response(context)
  • 47. Listページの書き方 impression_list.html のページング部分 {% if is_paginated %} <ul class="pagination"> {% if page_obj.has_previous %} <li><a href="?page={{ page_obj.previous_page_number }}">&laquo;</a></li> {% else %} <li class="disabled"><a href="#">&laquo;</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 }}">&raquo;</a></li> {% else %} <li class="disabled"><a href="#">&raquo;</a></li> {% endif %} </ul> {% endif %}
  • 50. Django REST framework とかもあるけど・・    
  • 52. 辞書をsimplejson.dumps()で返した場合 def org_list(request): '''会社の一覧''' orgs = [] for org in Organization.objects.all(): orgs.append(dict(id=org.id, name=org.name)) data = dict(status='ok', response_code= '000', message='Success', org_list=orgs) json = simplejson.dumps(data, ensure_ascii=False, indent=2) return HttpResponse(json, mimetype='application/json; charset=UTF-8')
  • 53. 項目が順不同になる・・ { "org_list": [ { "id": 1, "name": "Japan System Laboratory" }, { "id": 2, "name": "GEEKLAB.NAGANO" } ], "status": "ok", "message": "Success", "response_code": "000" }
  • 56. OrderedDict()で順序付き辞書にする 順序付き辞書を使いましょう!(Python 2.7~) from collections import OrderedDict def org_list(request): ''' 会社の一覧を返す ''' orgs = [] for org in Organization.objects.all(): orgs.append(dict(id=org.id, name=org.name)) data = OrderedDict([('status', 'ok'), ('response_code', '000'), ('message', 'Success'), ('org_list',orgs)]) json = simplejson.dumps(data, ensure_ascii=False, indent=2) return HttpResponse(json, mimetype='application/json; charset=UTF-8')
  • 57. ちゃんとコード通りSortされる { "status": "ok", "response_code": "000", "message": "Success", "org_list": [ { "name": "Japan System Laboratory", "id": 1 }, { "name": "GEEKLAB.NAGANO", "id": 2 } ] }
  • 58. Postの場合 • 簡単なデータの場合は、CMSのForm受信と同じものを 書いて、スマホ側では http form post を模倣してもら う。 ◦ こうすることによって、データのエラーチェックは、 フォームのバリデーションの仕組みが使えます。 ◦ 正常かエラーかは、JSONで結果を返すようにしま す。 ! • 繰り返しがある複雑なデータは、スマホ側からJSONを POSTしてもらい、json.loads() でデコードする
  • 59. 簡単なデータの場合 formの作成 #ログインフォーム class MemberLoginForm(forms.Form): email = forms.CharField(label='email', max_length=255) password = forms.CharField(label='password', max_length=255) ファンクションの作成 @csrf_exempt def user_login(request): if request.method == 'POST': form = MemberLoginForm(request.POST) if not form.is_valid(): email = form.cleaned_data['email'] password = form.cleaned_data['password'] data = OrderedDict([ ('status', 'ng'), ('response_code', '001'), ('message', form.errors) ]) return render_json_response(request, data) return render_json_response(request, data) else: form = MemberLoginForm() return render_to_response('api/user_login.html', dict(form=form), context_instance=RequestContext(request))
  • 61. 複雑なデータの場合 formの作成(JSONのテンプレをplaceholderで表示してあげると親切) JSON_QUIZ_RESPONSE = ''' { "quiz_questions":[ { "quiz_question_id":2, "checked_quiz_options": [ {"quiz_option_id":6} ] } ] } ''' # 4択クイズ回答フォーム class ModuleQuizResponseForm(forms.Form): user_id = forms.IntegerField(label='user_id') # ユーザID json_string = forms.CharField(label='json_string', widget=forms.Textarea, initial=JSON_QUIZ_RESPONSE)
  • 63. 複雑なデータの場合 ファンクションの作成(json.loadsでJSONを解析) @csrf_exempt def module_quiz_response(request, module_id): if request.method == 'POST': form = ModuleQuizResponseForm(request.POST) if form.is_valid(): '''省略''' ! # JSON文字列の取り出し json_string = form.cleaned_data['json_string'] json_obj = json.loads(json_string) analyze_quiz_questions = [] # クイズ回答ログ、初回回答の更新 for json_question in json_obj['quiz_questions']: quiz_question_id = json_question['quiz_question_id'] '''省略''' data = OrderedDict([ ('status', 'ok'), ('response_code', '000'), ('message', 'Success'), ]) return render_json_response(request, data) else: form = ModuleQuizResponseForm() return render_to_response('api/module_quiz_response.html', dict(form=form, module_id=module_id), context_instance=RequestContext(request))
  • 67. PyAPNs • 最新バージョン 1.1.2(今のところPython3は未対応) https://github.com/djacobs/PyAPNs • 事前に証明書ファイル・キーファイルを作成しておく $ pip install git+git://github.com/djacobs/PyAPNs.git ※普通に入れると、期待したものが入らない可能性があるので、  GitHubからインストール
  • 68. 1デバイスへのPUSH通知 from apns import APNs, Frame, Payload def send_push_message(token_hex): apns = APNs(use_sandbox=True, cert_file='xxx.pem', key_file='xxx_key-noenc. pem')   payload = Payload(alert="Hello World!", sound="default", badge=1) # Send a notification apns.gateway_server.send_notification(token_hex, payload)
  • 69. 複数デバイスへのPUSH通知 最新の形式(frame形式?) from apns import APNs, Payload def send_push_message(token_hex): apns = APNs(use_sandbox=False, cert_file='xxx.pem', key_file='xxx-noenc.pem') ! # 対象のデバイスのトークンをまとめる tokens = ['xxxxxxxxxxxxxxxxxx','xxxxxxxxxxxxxxxxxx'] ! payload = Payload(alert="Hello World!", sound="default", badge=1) # 一括でPUSH frame = Frame() identifier = 1 expiry = time.time()+3600 priority = 10 # 即座に通知 for token in tokens: frame.add_item(token, payload, identifier, expiry, priority) apns.gateway_server.send_notification_multiple(frame)
  • 70. feedbackで返された トークンは、削除する for (token_hex, fail_time) in apns.feedback_server.items(): #未使用のデバイストークンを削除 for token in DeviceToken.objects.filter(token=token_hex): token.delete() !
  • 71. 実装上のポイント • デバイストークンを収集する仕組み > API  *ユーザの複数端末持ちを考慮 • ペイロードのサイズ制限は256バイト > 冗長した メッセージは「・・・」等で調整
  • 73. GCM (Google Cloud Message)
  • 74. python-gcm • 最新バージョンは 0.1.5 https://github.com/geeknam/python-gcm • APIキーを事前にGoogle API Consoleから取得 $ pip install python-gcm
  • 75. python-gcmの使用例 # APIキーを渡して、GCMオブジェクトを作成 gcm = GCM('XXXXXXXXXXXXXXXXXXXXXXXXX') ! # registration idを指定する reg_ids = ['XXXXXXXXXXXXX','XXXXXXXXXXXXX','XXXXXXXXXXXXX'] data = {'alert': 'テスト!!' } ! # PUSH response = gcm.json_request(registration_ids=reg_ids, data=data) if 'canonical' in response: #GCMサーバーがcanonical idを返したきた場合、現状のデバイストークン(register id)をこちらに置き換える for canonical_id, reg_id in response['canonical'].items(): for token in DeviceToken.objects.filter(device_token=reg_id): token.device_token = canonical_id token.save()
  • 76. 実装上のポイント • デバイストークンを収集する仕組み > API  *ユーザの複数端末持ちを考慮(APNsと同様) • ペイロードのサイズ制限は4096バイト > 気にし なくて良いレベル
  • 77. ログイン パスワードの暗号化 • サーバ/スマホ間のパスワード通信を暗号化したい • iOS/Android/Python で共通で暗号化/復号化できる ベストなプロトコルは何か • AESがよい(AES ECBモード) AESの暗号化はバイナリ値になるのでBASE64に変換 • pycrypto を使う https://www.dlitz.net/software/pycrypto/ $ pip install pycrypto
  • 78. ログイン パスワードの暗号化 AES 復号化の部分 from Crypto.Cipher import AES from Crypto import Random ! def aes_decrypt(string, key=None): ''' AESで復号化 ''' if not key or len(key) not in (16, 128, 192, 256): raise ValueError('Key size must be 16, 128, 192, 256') bs = AES.block_size iv = Random.new().read(bs) cipher = AES.new(key.encode(), AES.MODE_ECB, iv) ! plaintext = cipher.decrypt(string) return plaintext.decode().rstrip('0')
  • 79. ログイン パスワードの暗号化 BASE64のデコード import base64 ! def base64url_decode(input): ''' BASE64のデコード ''' rem = len(input) % 4 if rem > 0: input += '=' * (4 - rem) try: return base64.urlsafe_b64decode(input.encode()).decode() # return str except UnicodeDecodeError: return base64.urlsafe_b64decode(input.encode()) # return byte ログイン処理のパスワード復号化 AES_KEY = getattr(settings, 'AES_KEY', 'SomeAesKey16byte') password_decrypt = aes_decrypt(base64url_decode(password), AES_KEY)
  • 80. ログイン連携 • Twitter/Facebook などの OAuth 2.0 連携は、 python-social-authで用意されている - https://github.com/omab/python-social-auth • OpenID Connect でログイン連携したい - 今後多くなると思われ • Yahoo Janan! の OpenID Connect (YConnect) の胸を借りる - 公開してくれている Yahoo Janan! に感謝を!
  • 81. ログイン連携 • python-social-auth の拡張モジュールを書く - 本家でもOpenID Connectは未対応? - 自分で書くことにした ‣ ベースはOAuth2.0で行ける ‣ OpenIDっぽいnonceの処理がある ‣ JWT (JSON Web Token)のデコードを追加 • サンプルはGitHub Gistを参照(長いので割愛) https://gist.github.com/kakky/6809432
  • 83. AWSで公開する iOS/Android アプリAmazon EC2 Mobile Client DBサーバー (MySQL) Amazon RDS 画像、音声、映像 Amazon S3 Email Amazon SES AWS SDK for Python (boto) 普通に SMTPサーバー として指定 IPアドレス指定 量が少ない場合は GMail、Google Apps で済ませてしまう場合もあり
  • 84. botoによるAmazon S3連携 画像ファイル等をS3に追い出すために、まずはこれ $ pip install boto S3 の bucket と key の関係 • URLに変換する際に、bucketはホスト名の一部になるため、全世界で 一意にする • keyの部分は、/を使って任意にフォルダ的なものを作ることができる • bucket=my-bucket-name、key=path/to/image.jpg とすると、 以下のようなURLを生成できる https://my-bucket-name.s3.amazonaws.com/path/to/image.jpg
  • 85. botoによるAmazon S3連携 S3へのアップロードと、パブリックなURLの取得 import boto, mimetypes, os from boto.s3.key import Key ! def s3_upload_media(file_path, s3_bucket, s3_key, do_delete=True): '''S3へのアップロードと、URLの取得''' conn = boto.connect_s3() b = conn.get_bucket(s3_bucket) k = Key(b) k.key = s3_key k.set_metadata("Content-Type", mimetypes.guess_type(k.key)[0]) k.set_contents_from_filename(file_path) # アップロード k.set_acl('public-read') # アクセス権を設定し、URLで見れるようにする s3_url = k.generate_url(3600, query_auth=False) #バケットとキーからURLを生成 if do_delete: os.remove(file_path) # 元ファイルの削除 return s3_url # DBには、このURL(と削除のためにs3_key)を格納する ※S3へのaccess_key、secret_access_keyなどのCredentialは、 ~/.boto に置いてあると仮定
  • 86. • Djangoアプリケーションのデプロイは以下を使用 - nginx : Webサーバ - uWSGI : アプリケーション コンテナ サーバ ‣ 姉妹サービスを同一ホストで公開することも踏まえ ‣ emperor/vassals(皇帝/家臣)モードを使用 uWSGI vassal uWSGI vassal nginx nginx + uWSGI uWSGI emperor 皇帝 家臣/家来? サービス1 仮想ホスト1 サービス2 仮想ホスト2 upstream 起動
  • 87. nginxの設定 upstream django-myservice { server unix:/tmp/uwsgi-myservice.sock; } server { listen 80; server_name www.myservice.com; uwsgi_buffer_size 4k; uwsgi_buffers 32 4k; : location /static/admin { alias /usr/lib/python2.7/site-packages/django/contrib/admin/static/admin; } location /static { alias /var/www/django/myservice/static; } location /media { alias /var/www/django/myservice/media; } location / { include uwsgi_params; uwsgi_pass django-myservice; } }
  • 88. uWSGI emperorの設定 # /etc/uwsgi.yaml uwsgi: emperor: /etc/uwsgi/vassals uid: nginx gid: nginx logfile-chmod: 644 daemonize: /var/log/uwsgi/emperor.log touch-logreopen: /tmp/uwsgi-log-reopen.txt emperor側は、/etc/uwsgi/vassels/ の下にある vasselsの設定ファイルを起動せよ、と書いてあるだけ この /etc/uwsgi.yaml は、 /etc/rc.d/init.d/uwsgi にスクリプトを書いて   $ sudo service uwsgi start にて起動できるようにしているが、長いので割愛(すみません)
  • 89. uWSGI emperorの設定 とはいえ、後からスライドを見て、コピペしたい人用に /etc/rc.d/init.d/uwsgi のスクリプトを貼っておきます #!/bin/sh # # /etc/rc.d/init.d/uwsgi # # uwsgi - this script starts and stops the uwsgi daemon # # chkconfig: - 85 15 # processname: uwsgi # config: /etc/uwsgi.yaml # config: /etc/sysconfig/uwsgi # pidfile: /var/run/uwsgi.pid # description: uwsgi is a WSGI server #! # Source function library. .! /etc/rc.d/init.d/functions C!ONFFILE="/etc/uwsgi.yaml" if [ -f /etc/sysconfig/uwsgi ]; then . /etc/sysconfig/uwsgi f!i prog=uwsgi uwsgi=${NGINX-/usr/bin/uwsgi} conffile=${CONFFILE-/etc/uwsgi.yaml} lockfile=${LOCKFILE-/var/lock/subsys/uwsgi} pidfile=${!PIDFILE-/var/run/uwsgi.pid} RETVAL=0 start() ! { echo -n $"Starting $prog: " #daemon --pidfile=${pidfile} ${uwsgi} --yaml ${conffile} daemon ${uwsgi} --yaml ${conffile} --pidfile ${pidfile} RETVAL=$? echo [ $RETVAL = 0 ] && touch ${lockfile} return $RETVAL }! stop() { echo -n $"Stopping $prog: " killproc -p ${pidfile} ${prog} -INT RETVAL=$? echo [ $RETVAL = 0 ] && rm -f ${lockfile} ${pidfile} }! # See how we were called. case "$1" in start) start ;; stop) stop ;; status) status -p ${pidfile} ${uwsgi} RETVAL=$? ;; restart) stop start ;; *) echo $"Usage: $prog {start|stop|restart|status}" RETVAL=2 e!sac exit $RETVAL 今、見えなくても怒らないで (́・ω・`)
  • 90. uWSGI vassalsの設定 # /etc/uwsgi/vassals/myservice_uwsgi.yaml uwsgi: socket: /tmp/uwsgi-myservice.sock chmod-socket: 666 chdir: /var/www/django/myservice/ wsgi-file: myservice/uwsgi.py master: true enable-threads: true pidfile: /tmp/uwsgi-myservice-master.pid processes: 2 threads: 30 stats: 127.0.0.1:9191 no-orphans: true touch-reload: /tmp/uwsgi-myservice-reload.txt uid: nginx gid: nginx vacuum: true import: uwsgi_autoreload logfile-chmod: 644 log-date: [%%a %%b %%d %%H:%%M:%%S %%Y] daemonize: /var/log/uwsgi/myservice.log disable-logging: true touch-logreopen: /tmp/uwsgi-log-reopen.txt listen: 4096 正直、パラメータ大杉 ! 性能が出る/出ない エラー吐く/吐かない はパラメータ次第 Apache+mod_wsgi の方が、よろしくやっ てくれた感がある
  • 91. uWSGIでオートリロード • 開発サーバと同じく、コードをデプロイしたら、 自動的に再起動して反映してほしい • 果たして、プロダクションでそれをやっていいかは 議論の余地があるが、便利なので設定する
  • 92. uWSGIでオートリロード プロジェクトのディレクトリ直下に、uwsgi_autoreload.py というコードを置く # -*- coding: utf-8 -*- # nginx + uWSGI で実行した時、ソースコードの変更を検知して、uWSGIを再起動する # # 注) import uwsgi は uWSGI 配下で実行した時のみ参照できる # from uwsgidecorators も同様 # どちらもローカル開発時は Unresolved import のままでよい ! import uwsgi from uwsgidecorators import timer from django.utils import autoreload ! @timer(3) # 3秒ごとに呼ばれる def change_code_gracefull_reload(sig): if autoreload.code_changed(): print(‘code change detected. autoreload ——————————————————————‘) uwsgi.reload()
  • 93. uWSGIでオートリロード uWSGI vassals の設定ファイルで指定する # /etc/uwsgi/vassals/myservice_uwsgi.yaml uwsgi: : : import: uwsgi_autoreload : :
  • 95. Qiitaにチュートリアル書きました • 「Django入門」でググると、 一番上に出てきてビビリます • Djangoを使う人の裾野を広げたいと思い 書きました。 • 公式チュートリアルと合わせて、 新しい人材の育成にご活用下さい。
  • 96. まとめ • コード部分は小さい字が多くてすみません。 • スライドは後ほど公開しますので、小さくて見えな かった部分は、後で見返して下さい。 • ということで、 スマートフォンとの連携案件を、 Djangoを使ってどんどん作りましょう!