djangoのチュートリアルをさくっとやってみた(フロントサイト構築)
前回の記事でdjangoチュートリアルの2までの内容(管理サイトの構築)まで行ったので、チュートリアルの3以降の内容(フロントサイトの構築)を行います。
3.2 フロントサイトの作成
投票一覧(index)・詳細(detail)・投票(vote)・投票結果(result)のページを作成します。
ルーティングの追加
各ページへのurlの設定を行います。
$ vi urls.py 6 urlpatterns = patterns('', 7 url(r'^polls/$', 'polls.views.index'), 8 url(r'^polls/(?P<poll_id>\d+)/$', 'polls.views.detail'), 9 url(r'^polls/(?P<poll_id>\d+)/results/$', 'polls.views.results'), 10 url(r'^polls/(?P<poll_id>\d+)/vote/$', 'polls.views.vote'), 11 url(r'^admin/', include(admin.site.urls)), 12 )
ビューの追加
一旦、各ページのダミーのビューを追加します。
これで「/polls」にアクセスすると表示はされるようになります。
vi polls/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 9 def results(request, poll_id): 10 return HttpResponse("You're looking at the results of poll %s." % poll_id) 11 12 def vote(request, poll_id): 13 return HttpResponse("You're voting on poll %s." % poll_id)
投票一覧画面(index)の作成1
テンプレートを利用しない一覧の実装例
$ vi polls/views.py 1 from polls.models import Poll 2 from django.http import HttpResponse 3 4 def index(request): 5 latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5] 6 output = ', '.join([p.question for p in latest_poll_list]) 7 return HttpResponse(output)
投票一覧画面(index)の作成2
テンプレートを利用した一覧の実装例
$ vi polls/views.py 1 from django.template import Context, loader 2 from 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)) $ mkdir templates/polls $ vi templates/polls/index.html 1 {% if latest_poll_list %} 2 <ul> 3 {% for poll in latest_poll_list %} 4 <li><a href="/polls/{{ poll.id }}/">{{ poll.question }}</a></li> 5 {% endfor %} 6 </ul> 7 {% else %} 8 <p>No polls are available.</p> 9 {% endif %}
投票一覧画面(index)の作成3
ショートカット(render_to_response)を利用した実装例
$ vi polls/views.py 1 from django.shortcuts import render_to_response 2 from 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 return render_to_response('polls/index.html', 8 {'latest_poll_list': latest_poll_list})
投票詳細画面(detail)の作成1
リクエストパラメータのpoll_idが存在しない番号の場合、HTTP STATUS 404を応答するように実装
$ vi polls/views.py 4 from django.http import Http404 ~(略)~ 10 def detail(request, poll_id): 11 try: 12 p = Poll.objects.get(pk=poll_id) 13 except Poll.DoesNotExist: 14 raise Http404 15 return render_to_response('polls/detail.html', {'poll': p}) $ vi templates/polls/detail.html 1 <h1>{{ poll.question }}</h1> 2 <ul> 3 {% for choice in poll.choice_set.all %} 4 <li>{{ choice.choice }}</li> 5 {% endfor %} 6 </ul>
投票詳細画面(detail)の作成2
HTTP STATUS 404の応答をショートカット(get_object_or_404)で実装
$ vi polls/views.py 1 from django.shortcuts import render_to_response, get_object_or_404 ~(略)~ 10 def detail(request, poll_id): 11 p = get_object_or_404(Poll, pk=poll_id) 12 return render_to_response('polls/detail.html', {'poll': p})
URLconfsの記述の簡略化と別ファイル化
urls.pyの記述をpolls/urls.pyに分離して記述も簡略化
(patternsの第一引数にパッケージ名を指定し簡略化)
// 「/polls」のルーティングは「polls/urls.py」に移動 $ vi urls.py 1 from django.conf.urls.defaults import patterns, include, url 2 3 from django.contrib import admin 4 admin.autodiscover() 5 6 urlpatterns = patterns('', 7 url(r'^polls/', include('polls.urls')), 8 url(r'^admin/', include(admin.site.urls)), 9 ) $ vi polls/urls.py 1 from django.conf.urls.defaults import patterns, include, url 2 3 urlpatterns = patterns('polls.views', 4 url(r'^$', 'index'), 5 url(r'^(?P<poll_id>\d+)/$', 'detail'), 6 url(r'^(?P<poll_id>\d+)/results/$', 'results'), 7 url(r'^(?P<poll_id>\d+)/vote/$', 'vote'), 8 )
投票フォーム(vote)と結果(result)の作成
一覧→投票→投票結果表示までの一連の流れを実装します。
// 投票詳細画面(detail)にフォームを組込 $ vi templates/polls/detail.html 1 <h1>{{ poll.question }}</h1> 2 3 {% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %} 4 5 <form action="/polls/{{ poll.id }}/vote/" method="post"> 6 {% csrf_token %} 7 {% for choice in poll.choice_set.all %} 8 <input type="radio" name="choice" id="choice{{ forloop.counter }}" 9 value="{{ choice.id }}" /> 10 <label for="choice{{ forloop.counter }}">{{ choice.choice }}</label><br /> 11 {% endfor %} 12 <input type="submit" value="投票する" /> 13 </form> // 結果画面(result)のテンプレートを作成 1 <h1>{{ poll.question }}</h1> 2 3 <ul> 4 {% for choice in poll.choice_set.all %} 5 <li>{{ choice.choice }} -- {{ choice.votes }} 票</li> 6 {% endfor %} 7 </ul> 8 9 <a href="/polls/{{ poll.id }}/">Vote again?</a> // 各ページのビューの実装 // detailのcontext_instanceはテンプレートの「csrf_token」のために必要 $ vi polls/views.py 1 from django.shortcuts import render_to_response, get_object_or_404 2 from django.template import RequestContext 3 from polls.models import Choice, Poll 4 from django.http import HttpResponseRedirect, HttpResponse 5 from django.core.urlresolvers import reverse 6 7 def index(request): 8 latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5] 9 return render_to_response('polls/index.html', 10 {'latest_poll_list': latest_poll_list}) 11 def detail(request, poll_id): 12 p = get_object_or_404(Poll, pk=poll_id) 13 return render_to_response('polls/detail.html', {'poll': p}, 14 context_instance=RequestContext(request)) 15 16 def results(request, poll_id): 17 p = get_object_or_404(Poll, pk=poll_id) 18 return render_to_response('polls/results.html', {'poll': p}) 19 20 def vote(request, poll_id): 21 p = get_object_or_404(Poll, pk=poll_id) 22 try: 23 selected_choice = p.choice_set.get(pk=request.POST['choice']) 24 except (KeyError, Choice.DoesNotExist): 25 return render_to_response('polls/detail.html', { 26 'poll': p, 27 'error_message': "You didn't select a choice.", 28 }, context_instance=RequestContext(request)) 29 else: 30 selected_choice.votes += 1 31 selected_choice.save() 32 return HttpResponseRedirect(reverse('polls.views.results', args=(p.id,)))
汎用ビューの利用
$ vi polls/urls.py 1 from django.conf.urls.defaults import * 2 from django.views.generic import DetailView, ListView 3 from polls.models import Poll 4 5 urlpatterns = patterns('', 6 url(r'^$', 7 ListView.as_view( 8 queryset=Poll.objects.order_by('-pub_date')[:5], 9 context_object_name='latest_poll_list', 10 template_name='polls/index.html')), 11 url(r'^(?P<pk>\d+)/$', 12 DetailView.as_view( 13 model=Poll, 14 template_name='polls/detail.html')), 15 url(r'^(?P<pk>\d+)/results/$', 16 DetailView.as_view( 17 model=Poll, 18 template_name='polls/results.html'), 19 name='poll_results'), 20 url(r'^(?P<poll_id>\d+)/vote/$', 'polls.views.vote'), 21 ) // 各ビュー(index,detail,result)の削除 $ vi polls/views.py 1 from django.shortcuts import get_object_or_404, render_to_response 2 from django.http import HttpResponseRedirect 3 from django.core.urlresolvers import reverse 4 from django.template import RequestContext 5 from polls.models import Choice, Poll 6 7 def vote(request, poll_id): 8 p = get_object_or_404(Poll, pk=poll_id) 9 try: 10 selected_choice = p.choice_set.get(pk=request.POST['choice']) 11 except (KeyError, Choice.DoesNotExist): 12 return render_to_response('polls/detail.html', { 13 'poll': p, 14 'error_message': "You didn't select a choice.", 15 }, context_instance=RequestContext(request)) 16 else: 17 selected_choice.votes += 1 18 selected_choice.save() 19 return HttpResponseRedirect(reverse('poll_results', args=(p.id,)))
これでチュートリアルは一通り完了です。少しdjangoを解った気分にはなれました。
djangoのチュートリアルをさくっとやってみた(管理サイト構築)
お仕事の事情でpythonエンジニアにスキルチェンジが求められたので
awsにさくっとdjango環境を構築してチュートリアルを行いました。
djangoのチュートリアル
1. サーバ環境準備
awsにマイクロインスタンスでAmazon Linuxを立てました。
対象サーバにログインしてから必要モジュールをインストールします。
// mysql,python2.7 gccをインストール $ sudo yum install mysql-server mysql-devel gcc $ sudo yum install python27 python27-devel python-setuptools $ sudo easy_install pip // mysql起動 $ sudo service mysqld start // virtualenvでテスト環境に必要モジュールをインストール $ sudo pip install virtualenv $ virtualenv -p /usr/bin/python27 --no-site-packages ~/.django13 $ source ~/.django13/bin/activate $ pip install django==1.3.7 flup mysql-python
2. チュートリアル用プロジェクトの準備
mysqlにdjango用ユーザを追加
$ mysql -u root mysql> CREATE DATABASE django; mysql> GRANT ALL PRIVILEGES ON django.* TO django@localhost IDENTIFIED BY 'django'; mysql> FLUSH PRIVILEGES;
プロジェクト用のディレクトリ(/home/django13/taka512)を作成し、mysqlへの接続設定を追加
$ sudo mkdir /home/django13 $ sudo chmod 777 /home/django13 $ cd /home/django13 $ django-admin.py startproject taka512 $ cd /home/django13/taka512 // mysqlへの接続設定を追加 $ vi settings.py 12 DATABASES = { 13 'default': { 14 'ENGINE': 'django.db.backends.mysql', 15 'NAME': 'django', 16 'USER': 'django', 17 'PASSWORD': 'django', 18 'HOST': 'localhost', 19 'PORT': '3306', 20 } 21 }
これで準備は完了です。
3. チュートリアル用アプリの作成
djangoのチュートリアルでは下記機能を備えたウェブアプリを作成します。
(1)投票項目(polls)を追加、変更削除可能な管理(admin)サイト。
(2)人々が投票項目(polls)の参照と投票(vote)が可能な公開サイト
3.1 管理サイトの作成
管理サイトのテーブルとアプリの雛形を作成
// 管理画面用のスキーマをデータベースに作成 // 途中で管理ユーザの作成を聞かれるのでadmin/adminで作成 $ python manage.py syncdb // アプリケーションの雛形を作成 // (pollsディレクトリに雛形が作成されます) $ python manage.py startapp polls
3.1.1 modelの作成
下記2つのmodelを作成します。
・投票項目(Poll):質問と発行日を保持
・投票選択肢(Choice):投票の選択肢と集計を保持、各選択肢は投票項目に関連付
$ vi polls/models.py 1 from django.db import models 2 3 class Poll(models.Model): 4 question = models.CharField(max_length=200) 5 pub_date = models.DateTimeField('date published') 6 7 class Choice(models.Model): 8 poll = models.ForeignKey(Poll) 9 choice = models.CharField(max_length=200) 10 votes = models.IntegerField()
3.1.2 modelの有効化
INSTALLED_APPに「polls」を追加
$ vi settings.py 115 INSTALLED_APPS = ( 116 'django.contrib.auth', 117 'django.contrib.contenttypes', 118 'django.contrib.sessions', 119 'django.contrib.sites', 120 'django.contrib.messages', 121 'django.contrib.staticfiles', 122 'polls',
3.1.3 テーブルにmodelを反映
// 実行されるSQLの確認 $ python manage.py sql polls // テーブル作成 $ python manage.py syncdb
3.1.4 管理サイトの対象にPollを追加
$ vi polls/admin.py 1 from polls.models import Poll 2 from django.contrib import admin 3 4 admin.site.register(Poll)
3.1.5 投票データの追加
shellからオブジェクトを操作してデータの追加が行う。
$ python manage.py shell >>> import datetime >>> from polls.models import Poll, Choice >>> p = Poll(question="What's up?", pub_date=datetime.datetime.now()) >>> p.save() >>> p.choice_set.create(choice='Not much', votes=0) >>> p.choice_set.create(choice='The sky', votes=0) >>> p.choice_set.create(choice='Just hacking again', votes=0)
3.1.6 管理サイトの有効化
INSTALLED_APPSに「django.contrib.admin」を追加し、ルーティングも有効にします。
$ vi settings.py 115 INSTALLED_APPS = ( 116 'django.contrib.auth', 117 'django.contrib.contenttypes', 118 'django.contrib.sessions', 119 'django.contrib.sites', 120 'django.contrib.messages', 121 'django.contrib.staticfiles', 122 'polls', 123 'django.contrib.admin', 124 ) // 管理サイト用のルーティングを追加 $ vi urls.py 1 from django.conf.urls.defaults import patterns, include, url 2 3 from django.contrib import admin 4 admin.autodiscover() 5 6 urlpatterns = patterns('', 7 url(r'^admin/', include(admin.site.urls)), 8 )
3.1.7 ウェブサーバ起動
起動後、自分のawsのurlにアクセスするとログイン画面が表示され先ほど追加したユーザでログインできます。
http://ec2-X-X-X-X.ap-northeast-1.compute.amazonaws.com/admin/
$ sudo /home/ec2-user/.django13/bin/python manage.py runserver 0.0.0.0:80
3.1.8 管理サイトのmodel項目の変更
管理サイトのmodelの項目はadmin.pyを編集する事で変更可能です。
// 質問と日付の表示順序を変更 $ vi polls/admin.py 1 from polls.models import Poll 2 from django.contrib import admin 3 4 class PollAdmin(admin.ModelAdmin): 5 fields = ['pub_date', 'question'] 6 7 admin.site.register(Poll,PollAdmin) // 日付に「Date information」とタイトルバーをつける $ vi polls/admin.py 4 class PollAdmin(admin.ModelAdmin): 5 fieldsets = [ 6 (None, {'fields': ['question']}), 7 ('Date information', {'fields': ['pub_date']}), 8 ] // htmlクラスを指定して日付を折り畳みに変更 $ vi polls/admin.py 4 class PollAdmin(admin.ModelAdmin): 5 fieldsets = [ 6 (None, {'fields': ['question']}), 7 ('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}), 8 ] // 普通にChoiceを編集対象に追加 $ vi polls/admin.py 11 from polls.models import Choice 12 admin.site.register(Choice) // Pollに関連付けてChoiceを表示 $ vi polls/admin.py 1 from django.contrib import admin 2 from polls.models import Poll 3 from polls.models import Choice 4 5 class ChoiceInline(admin.StackedInline): 6 model = Choice 7 extra = 3 8 9 class PollAdmin(admin.ModelAdmin): 10 fieldsets = [ 11 (None, {'fields': ['question']}), 12 ('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}), 13 ] 14 inlines = [ChoiceInline] 15 admin.site.register(Poll,PollAdmin) // Choiceの表示を変更 $ vi polls/admin.py 5 class ChoiceInline(admin.TabularInline): 6 model = Choice 7 extra = 3
一覧の表示を変更する場合は以下のように行います。
// 一覧の表示項目のプロパティを追加 $ vi polls/models.py 1 from django.db import models 2 import datetime 3 4 class Poll(models.Model): 5 question = models.CharField(max_length=200) 6 pub_date = models.DateTimeField('date published') 7 8 def was_published_today(self): 9 return self.pub_date.date() == datetime.date.today() 10 was_published_today.short_description = 'Published today?' // 一覧の表示項目が変更可能 // list_displayで表示項目 // list_filterでフィルター項目 // search_fieldsで検索項目 // date_hierarchyで日付のページめくり項目 $ vi polls/admin.py 9 class PollAdmin(admin.ModelAdmin): 10 list_display = ('question', 'pub_date', 'was_published_today' 11 list_filter = ['pub_date'] 12 search_fields = ['question'] 13 date_hierarchy = 'pub_date'
3.1.9 管理サイトの見た目の変更
サイトのタイトルを書き換えてみます。
タイトル欄が「taka512 administration」と表示されれば成功です。
$ vi settings.py 108 TEMPLATE_DIRS = ( 109 '/home/django13/taka512/templates', 110 ) $ mkdir -p templates/admin $ cp ~/.django13/lib/python2.7/site-packages/django/contrib/admin/templates/admin/base_site.html templates/admin/ $ vi templates/admin/base_site.html 7 <h1 id="site-name">{% trans 'taka512 administration' %}</h1>
以上でチュートリアルの管理サイト構築は完了です。
slimでknp-componentsのpaginatorを使う(データベースアクセス版2)
前回の記事ではページ捲り用のクラスを作ってページ捲り機能を実装してました。
今回はデータの取得/計算をコントローラで行いpagination機能だけを使いたい場合の例を記します。
素のphpの場合、この実装がやりやすいと思います。
Controllerの変更
リミット(limit) , 総件数(count),ページ数(page), データ(items)の取得/計算を行います。
テンプレートは前回から修正の必要はありません。
$ vi src/Taka512/Controllers/ContentsController.php <?php namespace Taka512\Controllers; use Knp\Component\Pager\Pagination\SlidingPagination; class ContentsController { ~省略~ public function pageTest($page) { $limit = 5; $offset = abs($page - 1) * $limit; $sth = $this->dbh->prepare('SELECT COUNT(*) AS count FROM alphabet'); $sth->execute(); $count = 0; while($row = $sth->fetch(\PDO::FETCH_ASSOC)){ $count = $row['count']; } $items = array(); if ($count) { $sth = $this->dbh->prepare('SELECT * FROM alphabet ORDER BY SORT LIMIT ? OFFSET ?'); $sth->execute(array($limit, $offset)); while($row = $sth->fetch(\PDO::FETCH_ASSOC)){ $items[] = $row; } } $pagination = new SlidingPagination(); $pagination->setCurrentPageNumber($page); $pagination->setItemNumberPerPage($limit); $pagination->setTotalItemCount($count); $pagination->setItems($items); $pagination->setCustomParameters(array()); $pagination->setPaginatorOptions(array()); $this->app->render('page_test.html.twig', array('pagination' => $pagination)); }
こんな感じでknp-componentsは理解すると便利に利用できます。
slimでknp-componentsのpaginatorを使う(データベースアクセス版)
前回の記事では最初から全データを用意してページ捲りを実装してました。
ただ、データベースに対象データが存在する場合は全件データを取得するのは現実的ではありません。
データベースからデータの一部を取得する形でページ捲りを実装する方法を記します。
1. データの投入
aからzまでのデータが存在するalphabetテーブルを用意します。
mysql> create table alphabet( str varchar(1), sort int); mysql> insert into alphabet(str, sort) values('a', 1); mysql> insert into alphabet(str, sort) values('b', 2); ~省略~ mysql> select * from alphabet; +------+------+ | str | sort | +------+------+ | a | 1 | | b | 2 | | c | 3 | | d | 4 | | e | 5 | | f | 6 | | g | 7 | ~省略~
2. DoctrineのORMを使う場合の実装例
DoctrineのORM等を利用する場合はpaginateの第1引数にDoctrineのQueryを指定すれば完了です。
$ vi src/Taka512/Controllers/ContentsController.php ~省略~ public function pageTest($page) { $paginator = new Paginator(); $pagination = $paginator->paginate( $this->em->createQuery('SELECT a FROM Entity\Alphabet a'), $page, 5); $this->app->render('page_test.html.twig', array('pagination' => $pagination)); }
3. PDOを使う場合の実装例
knp-componentsのページ捲りはSymfony2のEventDispatcherのcomponentsを使用して実装してます。
DoctrineのORMを使用している場合は既存のSubscriberを使用してページ捲り用の処理を行いますが、
PDOの場合は自分でSubscriberを実装する必要があります。
Subscriberの作成
Subscriberではcountでデータ件数とlimitでデータを取得してEventオブジェクトに設定します。
$ mkdir -p src/Taka512/Events/Subscriber $ vi src/Taka512/Events/Subscriber/PdoQuerySubscriber.php <?php namespace Taka512\Events\Subscriber; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Knp\Component\Pager\Event\ItemsEvent; use Taka512\Models\PdoQuery; class PdoQuerySubscriber implements EventSubscriberInterface { public function items(ItemsEvent $event) { if ($event->target instanceof PdoQuery) { $sth = $event->target->dbh->prepare('SELECT COUNT(*) AS count FROM alphabet'); $sth->execute(); while($row = $sth->fetch(\PDO::FETCH_ASSOC)){ $event->count = $row['count']; } $event->items = array(); if ($event->count) { $sth = $event->target->dbh->prepare('SELECT * FROM alphabet ORDER BY SORT LIMIT ? OFFSET ?'); $sth->execute(array($event->getLimit(),$event->getOffset())); while($row = $sth->fetch(\PDO::FETCH_ASSOC)){ $event->items[] = $row; } } $event->stopPropagation(); } } public static function getSubscribedEvents() { return array( 'knp_pager.items' => array('items', 2) ); } }
Subscriberに渡すデータを保持するクラスを作成します。
本来はここで検索条件などを保持してPdoQuerySubscriberで組み立てますが今回はdbhのみ保持します。
$ mkdir -p src/Taka512/Models $ vi src/Taka512/Models/PdoQuery.php <?php namespace Taka512\Models; class PdoQuery { public $dbh; public function __construct($dbh) { $this->dbh = $dbh; } }
Controllerの変更
Controllerでは明示的にsubscriberを指定し、作成したPdoQueryクラスを指定すると
PdoQuerySubscriberのitemsが動作してpaginationデータがセットされるようになります。
$ vi src/Taka512/Controllers/ContentsController.php ~省略~ public function pageTest($page) { $paginator = new Paginator(); $paginator->subscribe(new PdoQuerySubscriber()); $query = new PdoQuery($this->dbh); $pagination = $paginator->paginate($query, $page, 5); $this->app->render('page_test.html.twig', array('pagination' => $pagination)); }
こんな感じでデータベースを利用したページ捲りが実装できます。
slimでknp-componentsのpaginatorを使う(非データベースアクセス版)
knp-componentsを利用してページ捲りを実装する方法を記す。
今回は[domain]/page/1でアクセスできるページ捲りテスト用のページを作成し、a~zまでのアルファベットを表示するページを作成します。
knp-componentsのインストール
$ vi composer.json "require": { "slim/slim": "2.*", "slim/extras": "2.0.*", "twig/twig": "1.*", "pimple/pimple": "v1.0.2", "phpunit/phpunit": "3.7.21", "fabpot/goutte": "v1.0.1", "knp-components": "1.2.2" $ php composer.phar update
routeを追加
$ vi config/routes.php ~略~ $app->get('/page/:page', function($page = 1) use ($container) { $container['app.controllers.contents_controller']->pageTest($page); });
controllerを実装
a~zの26文字の配列を1ページ5文字で表示するように設定
$ vi src/Taka512/Controllers/ContentsController.php use Knp\Component\Pager\Paginator; class ContentsController { ~略~ public function pageTest($page) { $paginator = new Paginator(); $target = range('a', 'z'); $pagination = $paginator->paginate($target, $page, 5); $this->app->render('page_test.html.twig', array('pagination' => $pagination)); }
テンプレートを実装
ページ捲りは別テンプレートで表示するため、ページ捲り用のデータをpという変数でpagination.html.twigに渡してます。
$ vi src/views/page_test.html.twig <ul> {% for item in pagination %} <li>{{ item }}</li> {% endfor %} </ul> {% include "pagination.html.twig" with { 'p' : pagination.getPaginationData } %}
pagination用のテンプレートを準備
$ vi src/views/pagination.html.twig {% if p.pageCount > 1 %} <div class="pagination"> <ul> {% if p.first is defined and p.current != p.first %} <li class="prev"> <a href="/page/{{ p.first }}"><<</a> </li> {% else %} <li class="prev disabled"><a href="/page/{{ p.first}}"><<</a></li> {% endif %} {% if previous is defined %} <li class="prev"> <a href="/page/{{ p.previous }}"><</a> </li> {% endif %} {% for page in p.pagesInRange %} <li {% if page == p.current %}class="active"{% endif %}><a href="/page/{{ page }}"> {{ page }} </a></li> {% endfor %} {% if p.next is defined %} <li class="next"> <a href="/page/{{ p.next }}">></a> </li> {% endif %} {% if p.last is defined and p.current != p.last %} <li class="next"> <a href="/page/{{ p.last }}">>></a> </li> {% else %} <li class="next disabled"> <a href="/page/{{ p.last }}">>></a> </li> {% endif %} </ul> </div> {% endif %}
これで完了
slimでphpunit使用して結合テスト
今回は、結合テストを行う方法を記す。
goutteはブラウザエミュレートしてくれるphpライブラリです。
今回はgoutteを使ってブラウザのリクエストをエミュレートして正常に表示が行われるかのテストを行います。
goutteのインストール
$ vi composer.json "require": { "slim/slim": "2.*", "slim/extras": "2.0.*", "twig/twig": "1.*", "pimple/pimple": "v1.0.2", "phpunit/phpunit": "3.7.21", "fabpot/goutte": "v1.0.1" }, $ php composer.phar update
結合テストの設定ファイルを作成
tests/integrateディレクトリのテストファイルを結合テストとして読込するよう設定します。
$ vi config/integrate.xml <?xml version="1.0" encoding="UTF-8"?> <phpunit bootstrap = "bootstrap.php" > <testsuites> <testsuite name="integrate test"> <directory>../tests/integrate</directory> </testsuite> </testsuites> </phpunit>
結合テストを作成
hoge.comにリクエストを送信して表示内容に「hello hoge」が含まれるかテストします。
$ mkdir -p tests/integrate/ vi tests/integrate/TopTest.php <?php namespace Taka512; use Goutte\Client; class TopTest extends \PHPUnit_Framework_TestCase { public function testGetName() { $client = new Client(); $crawler = $client->request('GET', 'http://hoge.com/'); $content = $client->getResponse()->getContent(); $this->assertRegExp('/hello hoge/', $content); } }
テスト実行
$ vendor/bin/phpunit -c config/integrate.xml PHPUnit 3.7.21 by Sebastian Bergmann. Configuration read from /home/coh2/config/integrate.xml . Time: 0 seconds, Memory: 6.25Mb OK (1 test, 1 assertion)
slimでphpunit使用してユニットテスト
今回はphpunitをインストールした上で、ユニットテストを行う手順を記します。
ユニットテストはメソッド単体だったりの小さい粒度のテストで
今回はsrc/Taka512/Services/NameService.phpのgetNameメソッドをテストします。
サービスクラスは以下のような感じでコンテナに登録しているとします。
$container['app.services.name_service'] = $container->share(function ($c) { return new \Taka512\Services\NameService($c); });
まずアプリケーション側をテストしやすい形に作り替えます。
具体的にはindex.phpで行っていたコンテナ作成処理等をbootstrap.phpに移動します。
web/index.php
<?php define('ENVIRONMENT', 'prod'); require '../config/bootstrap.php'; $container = createContainer(); $app = $container['app']; require PROJECT_DIR.'/config/routes.php'; $app->run();
config/bootstrap.php
<?php define('PROJECT_DIR', dirname(__FILE__) . '/..'); require PROJECT_DIR . '/vendor/autoload.php'; function createContainer() { require PROJECT_DIR . '/config/config.php'; $container = new \Pimple(); $container['app'] = new \Slim\Slim($config); require PROJECT_DIR . '/config/parameters.php'; require PROJECT_DIR . '/config/databases.php'; require PROJECT_DIR . '/config/services.php'; return $container; }
phpunitのインストール
composerでインストールするとvendor/binの下にphpunitがインストールされます。
$ vi composer.json "require": { "slim/slim": "2.*", "slim/extras": "2.0.*", "twig/twig": "1.*", "pimple/pimple": "v1.0.2", "phpunit/phpunit": "3.7.21", }, $ php composer.phar update $ ls vendor/bin/phpunit vendor/bin/phpunit
テストの設定ファイルを作る
ユニットテスト用の設定ファイルをconfigの下に作成します。
先ほど作成したbootstrapを読み込んで「tests/unit」下のテストを起動する設定となります。
$ vi config/unit.xml <?xml version="1.0" encoding="UTF-8"?> <phpunit bootstrap = "bootstrap.php" > <testsuites> <testsuite name="unit test"> <directory>../tests/unit</directory> </testsuite> </testsuites> </phpunit>
テスト作成
テストディレクトリを掘ってテストファイルを作成します。
$ mkdir -p test/unit/Services $ vi tests/unit/Services/NameServiceTest.php <?php namespace Taka512\Services; class NameServiceTest extends \PHPUnit_Framework_TestCase { public function testGetName() { \Slim\Environment::mock(); $container = createContainer(); $name = $container['app.services.name_service']->getName(); $this->assertEquals($name, 'hoge'); } }
テスト実行
設定ファイルを指定してphpunitを実行するとサービスクラスのテストができましたとさ!
$ vendor/bin/phpunit -c config/unit.xml PHPUnit 3.7.21 by Sebastian Bergmann. Configuration read from /home/coh2/config/unit.xml . Time: 0 seconds, Memory: 5.00Mb OK (1 test, 1 assertion)