Djangoメモ(12) : get_object_or_404ショートカットとリンクのテスト
A Complete Beginner's Guide to Djangoのチュートリアルを参考にget_object_or_404ショートカットの利用とリンクのテストをしてみる。
get_object_or_404ショートカット
前回作成したTopic一覧を表示する詳細ページのテストをするためにmyproject/boards/tests.py
にBoardTopicsTests
を追加。
from django.urls import reverse, resolve from django.test import TestCase from .views import home, board_topics from .models import Board class HomeTests(TestCase): # 省略 class BoardTopicsTests(TestCase): def setUp(self): Board.objects.create(name='Django', description='Django board.') def test_board_topics_view_success_status_code(self): url = reverse('board_topics', kwargs={'pk': 1}) response = self.client.get(url) self.assertEquals(response.status_code, 200) def test_board_topics_view_not_found_status_code(self): url = reverse('board_topics', kwargs={'pk': 99}) response = self.client.get(url) self.assertEquals(response.status_code, 404) def test_board_topics_url_resolves_board_topics_view(self): view = resolve('/boards/1/') self.assertEquals(view.func, board_topics)
今回はsetUp
メソッドを追加してテストで使用するインスタンスを生成するようにしている。
Djangoのテストは現在のデータベースに対して行うわけではなく、新しいデータベースをその都度生成(マイグレーションおよび破棄)して行っているためテスト用データの準備などはこのsetUp
メソッド内で実施するのが良い。
テストの内容はステータスコードを確認するテストと正しいビュー関数が呼ばれているかの確認だが、テストを実行してみるとtest_board_topics_view_not_found_status_code
がDoesNotExist
例外で失敗する。
$ python manage.py test Creating test database for alias 'default'... System check identified no issues (0 silenced). .E... ====================================================================== ERROR: test_board_topics_view_not_found_status_code (boards.tests.BoardTopicsTests) ---------------------------------------------------------------------- 省略 boards.models.DoesNotExist: Board matching query does not exist. ---------------------------------------------------------------------- Ran 5 tests in 0.100s FAILED (errors=1) Destroying test database for alias 'default'...
実際にページにアクセスしてみると同じメッセージが表示される。
DEBUG = False
にしてみると500 Internal Server Errorになっていることがわかる。
今回は404エラーが返ってくるようにしたいのでboards/views.py
のboard_topics
を次のように編集する。Http404
のインポートを忘れないように注意。
from django.shortcuts import render from django.http import Http404 from .models import Board def home(request): # 省略 def board_topics(request, pk): try: board = Board.objects.get(pk=pk) except Board.DoesNotExist: raise Http404 return render(request, 'topics.html', {'board': board})
テストを実行してみると全部パスする。
$ python manage.py test Creating test database for alias 'default'... System check identified no issues (0 silenced). ..... ---------------------------------------------------------------------- Ran 5 tests in 0.037s OK Destroying test database for alias 'default'...
ページにアクセスしてみると404ページが表示されることが確認できる。 これはDjangoのデフォルトの404ページだが変更する事もできるようだ。
DEBUG = False
の場合の表示は下記。
このget()
を実行し、オブジェクトが存在しない場合にHttp404
を送出することはよく使われるイディオムなので、 Djangoではget_object_or_404()
ショートカットが用意されている。
ということでショートカットで書き換えてみる。import文も変更したので注意。
from django.shortcuts import render, get_object_or_404 from .models import Board def home(request): # 省略 def board_topics(request, pk): board = get_object_or_404(Board, pk=pk) return render(request, 'topics.html', {'board': board})
このように1行で簡潔に書くことができる。
一応、python manage.py test
を実行して結果が変わっていないことを確認しておく。
ナビゲーションリンクの追加とテスト
メインページから詳細ページへのリンクと詳細ページからメインページへのリンクを追加する。 これについては最初にテストを追加してから機能を実装してみる。
メインページの変更
HomeTests
を以下のように変更する。
class HomeTests(TestCase): def setUp(self): self.board = Board.objects.create(name='Django', description='Django board.') url = reverse('home') self.response = self.client.get(url) def test_home_view_status_code(self): self.assertEquals(self.response.status_code, 200) def test_home_url_resolves_home_view(self): view = resolve('/') self.assertEquals(view.func, home) def test_home_view_contains_link_to_topics_page(self): board_topics_url = reverse('board_topics', kwargs={'pk': self.board.pk}) self.assertContains(self.response, 'href="{0}"'.format(board_topics_url))
setUp
メソッドでBoardインスタンスを生成し、home
のレスポンスを再利用できるようにself.response
に設定する。
新しく追加したtest_home_view_contains_link_to_topics_page
テストではassertContains
メソッドを使用してhref="/boards/1/"
が含まれているかを確認する。
テストを実行してみると機能はまだ実装していないので当然失敗する。
$ python manage.py test Creating test database for alias 'default'... System check identified no issues (0 silenced). ....F. ====================================================================== FAIL: test_home_view_contains_link_to_topics_page (boards.tests.HomeTests) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/vagrant/django/myproject/myproject/boards/tests.py", line 21, in test_home_view_contains_link_to_topics_page self.assertContains(self.response, 'href="{0}"'.format(board_topics_url)) File "/home/vagrant/.local/share/virtualenvs/myproject-j-SR1M6H/lib/python3.6/site-packages/django/test/testcases.py", line 369, in assertContains self.assertTrue(real_count != 0, msg_prefix + "Couldn't find %s in response" % text_repr) AssertionError: False is not true : Couldn't find 'href="/boards/1/"' in response ---------------------------------------------------------------------- Ran 6 tests in 0.040s FAILED (failures=1) Destroying test database for alias 'default'...
テストがパスするようにtemplates/home.html
を編集(<tbody>
内のみ表示)。
<tbody> {% for board in boards %} <tr> <td> <a href="{% url 'board_topics' board.pk %}">{{ board.name }}</a> <small class="text-muted d-block">{{ board.description }}</small> </td> <td class="align-middle">0</td> <td class="align-middle">0</td> <td></td> </tr> {% endfor %} </tbody>
テンプレートでURLを記述する場合は、URLの変更に対応できる{% url %}
タグを使用するのが良い。
{% url %}
タグの最初の引数(board_topics
)はURLconfのnameで、残りは<int:pk>
などに対する引数を指定する。
リンクを追加したのでこれでテストが通るようになる。
$ python manage.py test Creating test database for alias 'default'... System check identified no issues (0 silenced). ...... ---------------------------------------------------------------------- Ran 6 tests in 0.055s OK Destroying test database for alias 'default'...
メインページにアクセスしてみるとリンクが追加されている。
詳細ページの変更
次に詳細ページからメインメージへのリンク用のテストを追加する。
class BoardTopicsTests(TestCase): # 省略 def test_board_topics_view_contains_link_back_to_homepage(self): board_topics_url = reverse('board_topics', kwargs={'pk': 1}) response = self.client.get(board_topics_url) homepage_url = reverse('home') self.assertContains(response, 'href="{0}"'.format(homepage_url))
実装していないので同じくテストは失敗する。
$ python manage.py test Creating test database for alias 'default'... System check identified no issues (0 silenced). .F..... ====================================================================== FAIL: test_board_topics_view_contains_link_back_to_homepage (boards.tests.BoardTopicsTests) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/vagrant/django/myproject/myproject/boards/tests.py", line 45, in test_board_topics_view_contains_link_back_to_homepage self.assertContains(response, 'href="{0}"'.format(homepage_url)) File "/home/vagrant/.local/share/virtualenvs/myproject-j-SR1M6H/lib/python3.6/site-packages/django/test/testcases.py", line 369, in assertContains self.assertTrue(real_count != 0, msg_prefix + "Couldn't find %s in response" % text_repr) AssertionError: False is not true : Couldn't find 'href="/"' in response ---------------------------------------------------------------------- Ran 7 tests in 0.059s FAILED (failures=1) Destroying test database for alias 'default'...
templates/topics.html
を編集してリンクを追加。
{% load static %}<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>{{ board.name }}</title> <link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}"> </head> <body> <div class="container"> <ol class="breadcrumb my-4"> <li class="breadcrumb-item"><a href="{% url 'home' %}">Boards</a></li> <li class="breadcrumb-item active">{{ board.name }}</li> </ol> </div> </body> </html>
これでテストが通るようになる。
$ python manage.py test Creating test database for alias 'default'... System check identified no issues (0 silenced). ....... ---------------------------------------------------------------------- Ran 7 tests in 0.047s OK Destroying test database for alias 'default'...
詳細ページにもリンクが付いたのでメインページとの行き来ができるようになった。
まとめ
- テストデータの準備などは
setUp
メソッドで実施 get_object_or_404()
ショートカットが提供されているassertContains
はレスポンスのコンテンツ内に指定文字列が入っているかどうか確認- テンプレートでのURLの記述には
{% url %}
を使用