Ensuring Test Isolation in Functional Tests
$ mkdir functional_tests
$ touch functional_tests/__init__.py
สร้าง Folder functional_tests และข้างในมีไฟล์ __init__.py
$ git mv functional_tests.py functional_tests/tests.py
ย้าย functional_tests.py ไปอยู่ใน functional_tests และเปลี่ยนชื่อเป็น test.py
├── db.sqlite3
├── functional_tests
│ ├── __init__.py
│ └── tests.py
├── lists
│ ├── admin.py
│ ├── __init__.py
│ ├── migrations
│ │ ├── 0001_initial.py
│ │ ├── 0002_item_text.py
│ │ ├── __init__.py
│ │ └── __pycache__
│ ├── models.py
│ ├── __pycache__
│ ├── templates
│ │ └── home.html
│ ├── tests.py
│ └── views.py
├── manage.py
└── superlists
├── __init__.py
├── __pycache__
├── settings.py
├── urls.py
└── wsgi.py
functional_tests/tests.py
from django.test import LiveServerTestCase
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
class NewVisitorTest(LiveServerTestCase):
def setUp(self):
[...]
แก้ตรง
def test_can_start_a_list_and_retrieve_it_later(self):
# Edith has heard about a cool new online to-do app. She goes
# to check out its homepage
self.browser.get(self.live_server_url)
git status
git add functional_tests
git diff --staged -M
git commit
Running Just the Unit Tests
ตอนนี้ถ้าเรา run manage.py test Django จะ run ทั้ง functional และ unit tests ทั้งคู่เลย
แต่ถ้าอยากรันแค่ unit tests ให้ใช้คำสั่ง python3 manage.py test lists
คำสั่งในการรัน FT จะเปลี่ยนเป็น
python3 manage.py test functional_tests
คำสั่งในการรัน Unit Test จะเปลี่ยนเป็น
python3 manage.py test lists
Small Design When Necessary
เราต้องการสร้าง list ใหม่ เพื่อใส่itemเข้าไป1อย่าง และใช้ url สำหรับ list นี้
functional_tests/tests.py
inputbox.send_keys('Buy peacock feathers')
# When she hits enter, she is taken to a new URL, # and now the page lists "1: Buy peacock feathers" as an item in a # to-do list table inputbox.send_keys(Keys.ENTER) edith_list_url = self.browser.current_url self.assertRegex(edith_list_url, '/lists/.+') #1 self.check_for_row_in_list_table('1: Buy peacock feathers') # There is still a text box inviting her to add another item. She [...]
เมื่อมีผู้ใช้รายใหม่เข้ามาใช้เราต้องตรวจสอบว่า พวกเขาจะไม่เห็นรายการใดๆของ Edith’s items เมื่อเข้ามาแล้วต้องได้รับ url ที่เป็นเอกลักษณ์ของตนเอง
[...]
# The page updates again, and now shows both items on her list
self.check_for_row_in_list_table('2: Use peacock feathers to make a fly')
self.check_for_row_in_list_table('1: Buy peacock feathers')
# Now a new user, Francis, comes along to the site.
## We use a new browser session to make sure that no information
## of Edith's is coming through from cookies etc #1
self.browser.quit()
self.browser = webdriver.Firefox()
# Francis visits the home page. There is no sign of Edith's
# list
self.browser.get(self.live_server_url)
page_text = self.browser.find_element_by_tag_name('body').text
self.assertNotIn('Buy peacock feathers', page_text)
self.assertNotIn('make a fly', page_text)
# Francis starts a new list by entering a new item. He
# is less interesting than Edith...
inputbox = self.browser.find_element_by_id('id_new_item')
inputbox.send_keys('Buy milk')
inputbox.send_keys(Keys.ENTER)
# Francis gets his own unique URL
francis_list_url = self.browser.current_url
self.assertRegex(francis_list_url, '/lists/.+')
self.assertNotEqual(francis_list_url, edith_list_url)
# Again, there is no trace of Edith's list
page_text = self.browser.find_element_by_tag_name('body').text
self.assertNotIn('Buy peacock feathers', page_text)
self.assertIn('Buy milk', page_text)
# Satisfied, they both go back to sleep
# The page updates again, and now shows both items on her list
self.check_for_row_in_list_table('2: Use peacock feathers to make a fly')
self.check_for_row_in_list_table('1: Buy peacock feathers')
# Now a new user, Francis, comes along to the site.
## We use a new browser session to make sure that no information
## of Edith's is coming through from cookies etc #1
self.browser.quit()
self.browser = webdriver.Firefox()
# Francis visits the home page. There is no sign of Edith's
# list
self.browser.get(self.live_server_url)
page_text = self.browser.find_element_by_tag_name('body').text
self.assertNotIn('Buy peacock feathers', page_text)
self.assertNotIn('make a fly', page_text)
# Francis starts a new list by entering a new item. He
# is less interesting than Edith...
inputbox = self.browser.find_element_by_id('id_new_item')
inputbox.send_keys('Buy milk')
inputbox.send_keys(Keys.ENTER)
# Francis gets his own unique URL
francis_list_url = self.browser.current_url
self.assertRegex(francis_list_url, '/lists/.+')
self.assertNotEqual(francis_list_url, edith_list_url)
# Again, there is no trace of Edith's list
page_text = self.browser.find_element_by_tag_name('body').text
self.assertNotIn('Buy peacock feathers', page_text)
self.assertIn('Buy milk', page_text)
# Satisfied, they both go back to sleep
Iterating Towards the New Design
สร้าง test ให้ redirect location หลังจากได้รับ POST
lists/tests.py
self.assertEqual(response.status_code, 302)
self.assertEqual(response['location'], '/lists/the-only-list-in-the-world/')
self.assertEqual(response.status_code, 302)
self.assertEqual(response['location'], '/lists/the-only-list-in-the-world/')
และแก้ใน lists/views.py
def home_page(request):
if request.method == 'POST':
Item.objects.create(text=request.POST['item_text'])
return redirect('/lists/the-only-list-in-the-world/')
items = Item.objects.all()
return render(request, 'home.html', {'items': items})
จะ error ว่า เนื่องจากยังไม่มี list item ชื่อว่า the-only-list-in-the-world
Testing Views, Templates, and URLs Together with the Django Test Client
ในบทนี้เราจะใช้ unit tests เช็คความละเอียดของ URL และเช็ค views render templates ถูกต้องหรือไม่
A New Test Class
ไปที่ lists/tests.py เพิ่มคลาสใหม่ ListViewTest หลังจากนั้น copy method ที่ชื่อว่า test_home_page_displays_all_ list_items จาก HomePageTest มาใส่ไว้ในคลาสใหม่แทนlists/tests.py
class ListViewTest(TestCase):
def test_displays_all_items(self):
Item.objects.create(text='itemey 1')
Item.objects.create(text='itemey 2')
response = self.client.get('/lists/the-only-list-in-the-world/') #1
self.assertContains(response, 'itemey 1') #2
self.assertContains(response, 'itemey 2') #3
A New URL
URL ของเรายังไม่ได้สร้างจึงไปแก้ไขใน superlists/urls.py
superlists/urls.py
urlpatterns = patterns('',
url(r'^$', 'lists.views.home_page', name='home'),
url(r'^lists/the-only-list-in-the-world/$', 'lists.views.view_list',
name='view_list'
),
# url(r'^admin/', include(admin.site.urls)),
)
urlpatterns = patterns('',
url(r'^$', 'lists.views.home_page', name='home'),
url(r'^lists/the-only-list-in-the-world/$', 'lists.views.view_list',
name='view_list'
),
# url(r'^admin/', include(admin.site.urls)),
)
A New View Function
lists/views.py
def view_list(request):
pass
def view_list(request):
pass
lists/views.py
def view_list(request):
items = Item.objects.all()
return render(request, 'home.html', {'items': items})
def view_list(request):
items = Item.objects.all()
return render(request, 'home.html', {'items': items})
Green? Refactor
เราสามารถลบ test_home_page_displays_all_list_items method เพราะมันไม่จำเป็น
test_home_page_displays_all_list_items method เพราะมันไม่จำเป็นA Separate Template for Viewing Lists
lists/tests.py
class ListViewTest(TestCase):def test_uses_list_template(self):
response = self.client.get('/lists/the-only-list-in-the-world/')
self.assertTemplateUsed(response, 'list.html')
def test_displays_all_items(self):
[...]
lists/views.py
def view_list(request):
items = Item.objects.all()
return render(request, 'list.html', {'items': items})
สร้างไฟล์ใหม่ที่ lists/templates/list.html ใช้คำสั่ง $ touch lists/templates/list.html
$ cp lists/templates/home.html lists/templates/list.html
home pageไม่จำเป็นต้องแสดงlist items มันเพียงต้องการ list inputใหม่ เพื่อให้เราสามารถลบบรรทัดจาก lists/templates/home.html และอาจปรับแต่งh1 ที่จะบอกว่า "Start a new To-Do list"
lists/templates/home.html
<body>
<h1>Start a new To-Do list</h1>
<form method="POST">
<input name="item_text" id="id_new_item" placeholder="Enter a to-do item" />
{% csrf_token %}
</form>
</body>
lists/views.py
def home_page(request):
if request.method == 'POST':
Item.objects.create(text=request.POST['item_text'])
return redirect('/lists/the-only-list-in-the-world/')
return render(request, 'home.html')
การรับ input item ตัวที่สองยังผิดอยู่ เพราะว่ารูปแบบใหม่ action= attribute หายไป เลยเป็นค่า default หมายความว่ามันจะส่งไปยัง url เดียวกัน
lists/templates/list.html
<form method="POST" action="/">
$ git status
$ git add lists/templates/list.html
$ git diff
$ git commit -a
Another URL and View for Adding List Items
เปิดไฟล์ lists/tests.py ให้นำ methods test_home_page_can_save_a_POST_request และ test_home_page_redirects_after_POST ไปใส่ในคลาสใหม่และเปลี่ยนชื่อตามข้างล่างA Test Class for New List Creation
lists/tests.py
class NewListTest(TestCase):
def test_saving_a_POST_request(self):
request = HttpRequest()
request.method = 'POST'
[...]
def test_redirects_after_POST(self):
[...]
class NewListTest(TestCase):
def test_saving_a_POST_request(self):
request = HttpRequest()
request.method = 'POST'
[...]
def test_redirects_after_POST(self):
[...]
แล้วเปลี่ยนcode ตามหนังสือ
class NewListTest(TestCase):
def test_saving_a_POST_request(self):
self.client.post(
'/lists/new',
data={'item_text': 'A new list item'}
)
self.assertEqual(Item.objects.count(), 1)
new_item = Item.objects.first()
self.assertEqual(new_item.text, 'A new list item')
def test_redirects_after_POST(self):
response = self.client.post(
'/lists/new',
data={'item_text': 'A new list item'}
)
self.assertEqual(response.status_code, 302)
self.assertEqual(response['location'], '/lists/the-only-list-in-the-world/')
A URL and View for New List Creation
urlpatterns = patterns('',
url(r'^$', 'lists.views.home_page', name='home'),
url(r'^lists/the-only-list-in-the-world/$', 'lists.views.view_list',
name='view_list'
),
url(r'^lists/new$', 'lists.views.new_list', name='new_list'),
# url(r'^admin/', include(admin.site.urls)),
)
url(r'^$', 'lists.views.home_page', name='home'),
url(r'^lists/the-only-list-in-the-world/$', 'lists.views.view_list',
name='view_list'
),
url(r'^lists/new$', 'lists.views.new_list', name='new_list'),
# url(r'^admin/', include(admin.site.urls)),
)
lists/views.py
def new_list(request):
pass
Then we get "The view lists.views.new_list didn’t return an HttpResponse object". (This is getting rather familiar!) We could return a raw HttpResponse, but since we know we’ll need a redirect, let’s borrow a line from home_page:
lists/views.py
def new_list(request):
return redirect('/lists/the-only-list-in-the-world/')
def new_list(request):
pass
Then we get "The view lists.views.new_list didn’t return an HttpResponse object". (This is getting rather familiar!) We could return a raw HttpResponse, but since we know we’ll need a redirect, let’s borrow a line from home_page:
lists/views.py
def new_list(request):
return redirect('/lists/the-only-list-in-the-world/')
lists/views.py
def new_list(request):
Item.objects.create(text=request.POST['item_text'])
return redirect('/lists/the-only-list-in-the-world/')
lists/tests.py
def test_redirects_after_POST(self):
response = self.client.post(
'/lists/new',
data={'item_text': 'A new list item'}
)
self.assertRedirects(response, '/lists/the-only-list-in-the-world/')
def test_redirects_after_POST(self):
response = self.client.post(
'/lists/new',
data={'item_text': 'A new list item'}
)
self.assertRedirects(response, '/lists/the-only-list-in-the-world/')
Removing Now-Redundant Code and Tests
viewของเราตอนนี้ส่วนใหญ่ทำงานที่ home_page เราควรจะที่จะลดความซับซ้อนของมัน ลองเอาส่วนของ if request.method == 'POST' ออกแล้วดูการเปลี่ยนแปลง
lists/views.py
def home_page(request):
return render(request, 'home.html')
เมื่อเราเอาการทดสอบ test_home_page_only_saves_ items_when_necessary ออกไปแล้วลองรัน UT
ผลคือ OK
Pointing Our Forms at the New URL
เรามี 2 urlที่จะต้องแก้ไขใน home.html และ lists.htmllists/templates/home.html, lists/templates/list.html.
<form method="POST" action="/lists/new">
Adjusting Our Models
lists/tests.py เพิ่มลด ตาม code ด้านล่าง
[...]
from django.template.loader import render_to_string
from django.test import TestCase
-from lists.models import Item
+from lists.models import Item, List
from lists.views import home_page
class HomePageTest(TestCase):
[...]
-class ItemModelTest(TestCase):
+class ListAndItemModelsTest(TestCase):
def test_saving_and_retrieving_items(self):
+ list_ = List()
+ list_.save()
+
first_item = Item()
first_item.text = 'The first (ever) list item'
+ first_item.list = list_
first_item.save()
second_item = Item()
second_item.text = 'Item the second'
+ second_item.list = list_
second_item.save()
+ saved_list = List.objects.first()
+ self.assertEqual(saved_list, list_)
+
saved_items = Item.objects.all()
self.assertEqual(saved_items.count(), 2)
first_saved_item = saved_items[0]
second_saved_item = saved_items[1]
self.assertEqual(first_saved_item.text, 'The first (ever) list item')
+ self.assertEqual(first_saved_item.list, list_)
self.assertEqual(second_saved_item.text, 'Item the second')
+ self.assertEqual(second_saved_item.list, list_)
from django.template.loader import render_to_string
from django.test import TestCase
-from lists.models import Item
+from lists.models import Item, List
from lists.views import home_page
class HomePageTest(TestCase):
[...]
-class ItemModelTest(TestCase):
+class ListAndItemModelsTest(TestCase):
def test_saving_and_retrieving_items(self):
+ list_ = List()
+ list_.save()
+
first_item = Item()
first_item.text = 'The first (ever) list item'
+ first_item.list = list_
first_item.save()
second_item = Item()
second_item.text = 'Item the second'
+ second_item.list = list_
second_item.save()
+ saved_list = List.objects.first()
+ self.assertEqual(saved_list, list_)
+
saved_items = Item.objects.all()
self.assertEqual(saved_items.count(), 2)
first_saved_item = saved_items[0]
second_saved_item = saved_items[1]
self.assertEqual(first_saved_item.text, 'The first (ever) list item')
+ self.assertEqual(first_saved_item.list, list_)
self.assertEqual(second_saved_item.text, 'Item the second')
+ self.assertEqual(second_saved_item.list, list_)
เมื่อเราไปเพิ่ม lists/models.py
class List(object):
pass
pass
หลังจากนั้นแก้เป็น
class List(models.Model):
pass
pass
A Foreign Key Relationship
lists/models.py
from django.db import models
class List(models.Model):
pass
class Item(models.Model):
text = models.TextField(default='')
list = models.TextField(default='')
from django.db import models
class List(models.Model):
pass
class Item(models.Model):
text = models.TextField(default='')
list = models.TextField(default='')
lists/models.py
from django.db import models
class List(models.Model):
pass
class Item(models.Model):
text = models.TextField(default='')
list = models.ForeignKey(List, default=None)
from django.db import models
class List(models.Model):
pass
class Item(models.Model):
text = models.TextField(default='')
list = models.ForeignKey(List, default=None)
Adjusting the Rest of the World to Our New Models
เมื่อลองรัน
$ python3 manage.py test lists
[...]
ERROR: test_displays_all_items (lists.tests.ListViewTest)
django.db.utils.IntegrityError: NOT NULL constraint failed: lists_item.list_id
[...]
ERROR: test_redirects_after_POST (lists.tests.NewListTest)
django.db.utils.IntegrityError: NOT NULL constraint failed: lists_item.list_id
[...]
ERROR: test_saving_a_POST_request (lists.tests.NewListTest)
django.db.utils.IntegrityError: NOT NULL constraint failed: lists_item.list_id
Ran 7 tests in 0.021s
FAILED (errors=3)
[...]
ERROR: test_displays_all_items (lists.tests.ListViewTest)
django.db.utils.IntegrityError: NOT NULL constraint failed: lists_item.list_id
[...]
ERROR: test_redirects_after_POST (lists.tests.NewListTest)
django.db.utils.IntegrityError: NOT NULL constraint failed: lists_item.list_id
[...]
ERROR: test_saving_a_POST_request (lists.tests.NewListTest)
django.db.utils.IntegrityError: NOT NULL constraint failed: lists_item.list_id
Ran 7 tests in 0.021s
FAILED (errors=3)
แก้ไฟล์ lists/tests.py
class ListViewTest(TestCase):
def test_displays_all_items(self):
list_ = List.objects.create()
Item.objects.create(text='itemey 1', list=list_)
Item.objects.create(text='itemey 2', list=list_)
class ListViewTest(TestCase):
def test_displays_all_items(self):
list_ = List.objects.create()
Item.objects.create(text='itemey 1', list=list_)
Item.objects.create(text='itemey 2', list=list_)
ที่lists/views.py
from lists.models import Item, List
[...]
def new_list(request):
list_ = List.objects.create()
Item.objects.create(text=request.POST['item_text'], list=list_)
return redirect('/lists/the-only-list-in-the-world/')
from lists.models import Item, List
[...]
def new_list(request):
list_ = List.objects.create()
Item.objects.create(text=request.POST['item_text'], list=list_)
return redirect('/lists/the-only-list-in-the-world/')
$ git status # 3 changed files, plus 2 migrations
$ git add lists
$ git diff --staged
$ git commit
$ git add lists
$ git diff --staged
$ git commit
Each List Should Have Its Own URL
lists/tests.py
class ListViewTest(TestCase):
def test_uses_list_template(self):
list_ = List.objects.create()
response = self.client.get('/lists/%d/' % (list_.id,))
self.assertTemplateUsed(response, 'list.html')
def test_displays_only_items_for_that_list(self):
correct_list = List.objects.create()
Item.objects.create(text='itemey 1', list=correct_list)
Item.objects.create(text='itemey 2', list=correct_list)
other_list = List.objects.create()
Item.objects.create(text='other list item 1', list=other_list)
Item.objects.create(text='other list item 2', list=other_list)
response = self.client.get('/lists/%d/' % (correct_list.id,))
self.assertContains(response, 'itemey 1')
self.assertContains(response, 'itemey 2')
self.assertNotContains(response, 'other list item 1')
self.assertNotContains(response, 'other list item 2')
Capturing Parameters from URLs
superlists/urls.py
urlpatterns = patterns('',
url(r'^$', 'lists.views.home_page', name='home'),
url(r'^lists/(.+)/$', 'lists.views.view_list', name='view_list'),
url(r'^lists/new$', 'lists.views.new_list', name='new_list'),
# url(r'^admin/', include(admin.site.urls)),
)
urlpatterns = patterns('',
url(r'^$', 'lists.views.home_page', name='home'),
url(r'^lists/(.+)/$', 'lists.views.view_list', name='view_list'),
url(r'^lists/new$', 'lists.views.new_list', name='new_list'),
# url(r'^admin/', include(admin.site.urls)),
)
เมื่อลองรัน UT จะพบว่า
ERROR: test_displays_only_items_for_that_list (lists.tests.ListViewTest)
ERROR: test_uses_list_template (lists.tests.ListViewTest)
ERROR: test_redirects_after_POST (lists.tests.NewListTest)
[...]
TypeError: view_list() takes 1 positional argument but 2 were given
ERROR: test_displays_only_items_for_that_list (lists.tests.ListViewTest)
ERROR: test_uses_list_template (lists.tests.ListViewTest)
ERROR: test_redirects_after_POST (lists.tests.NewListTest)
[...]
TypeError: view_list() takes 1 positional argument but 2 were given
แก้ไข parameterใน lists/views.py
def view_list(request, list_id):
[...]
def view_list(request, list_id):
[...]
lists/views.py
def view_list(request, list_id):
list_ = List.objects.get(id=list_id)
items = Item.objects.filter(list=list_)
return render(request, 'list.html', {'items': items})
def view_list(request, list_id):
list_ = List.objects.get(id=list_id)
items = Item.objects.filter(list=list_)
return render(request, 'list.html', {'items': items})
Adjusting new_list to the New World
เมื่อลองรัน UT
ERROR: test_redirects_after_POST (lists.tests.NewListTest)
ValueError: invalid literal for int() with base 10:'the-only-list-in-the-world'
ValueError: invalid literal for int() with base 10:'the-only-list-in-the-world'
lists/tests.py แก้ code ใน function ใหม่เป็น
class NewListTest(TestCase):
[...]
def test_redirects_after_POST(self):
response = self.client.post(
'/lists/new',
data={'item_text': 'A new list item'}
)
new_list = List.objects.first()
self.assertRedirects(response, '/lists/%d/' % (new_list.id,))
class NewListTest(TestCase):
[...]
def test_redirects_after_POST(self):
response = self.client.post(
'/lists/new',
data={'item_text': 'A new list item'}
)
new_list = List.objects.first()
self.assertRedirects(response, '/lists/%d/' % (new_list.id,))
lists/views.py
def new_list(request):
list_ = List.objects.create()
Item.objects.create(text=request.POST['item_text'], list=list_)
return redirect('/lists/%d/' % (list_.id,))
def new_list(request):
list_ = List.objects.create()
Item.objects.create(text=request.POST['item_text'], list=list_)
return redirect('/lists/%d/' % (list_.id,))
One More View to Handle Adding Items to an Existing List
lists/tests.py
class NewItemTest(TestCase):
def test_can_save_a_POST_request_to_an_existing_list(self):
other_list = List.objects.create()
correct_list = List.objects.create()
self.client.post(
'/lists/%d/add_item' % (correct_list.id,),
data={'item_text': 'A new item for an existing list'}
)
self.assertEqual(Item.objects.count(), 1)
new_item = Item.objects.first()
self.assertEqual(new_item.text, 'A new item for an existing list')
self.assertEqual(new_item.list, correct_list)
def test_redirects_to_list_view(self):
other_list = List.objects.create()
correct_list = List.objects.create()
response = self.client.post(
'/lists/%d/add_item' % (correct_list.id,),
data={'item_text': 'A new item for an existing list'}
)
self.assertRedirects(response, '/lists/%d/' % (correct_list.id,))
class NewItemTest(TestCase):
def test_can_save_a_POST_request_to_an_existing_list(self):
other_list = List.objects.create()
correct_list = List.objects.create()
self.client.post(
'/lists/%d/add_item' % (correct_list.id,),
data={'item_text': 'A new item for an existing list'}
)
self.assertEqual(Item.objects.count(), 1)
new_item = Item.objects.first()
self.assertEqual(new_item.text, 'A new item for an existing list')
self.assertEqual(new_item.list, correct_list)
def test_redirects_to_list_view(self):
other_list = List.objects.create()
correct_list = List.objects.create()
response = self.client.post(
'/lists/%d/add_item' % (correct_list.id,),
data={'item_text': 'A new item for an existing list'}
)
self.assertRedirects(response, '/lists/%d/' % (correct_list.id,))
Beware of Greedy Regular Expressions!
superlists/urls.py
url(r'^lists/(\d+)/$', 'lists.views.view_list', name='view_list'),
url(r'^lists/(\d+)/$', 'lists.views.view_list', name='view_list'),
The Last New URL
superlists/urls.py
urlpatterns = patterns('',
url(r'^$', 'lists.views.home_page', name='home'),
url(r'^lists/(\d+)/$', 'lists.views.view_list', name='view_list'),
url(r'^lists/(\d+)/add_item$', 'lists.views.add_item', name='add_item'),
url(r'^lists/new$', 'lists.views.new_list', name='new_list'),
# url(r'^admin/', include(admin.site.urls)),
)
urlpatterns = patterns('',
url(r'^$', 'lists.views.home_page', name='home'),
url(r'^lists/(\d+)/$', 'lists.views.view_list', name='view_list'),
url(r'^lists/(\d+)/add_item$', 'lists.views.add_item', name='add_item'),
url(r'^lists/new$', 'lists.views.new_list', name='new_list'),
# url(r'^admin/', include(admin.site.urls)),
)
The Last New View
lists/views.py
def add_item(request):
pass
def add_item(request):
pass
รัน UT
TypeError: add_item() takes 1 positional argument but 2 were given
lists/views.py
def add_item(request, list_id):
pass
def add_item(request, list_id):
pass
รัน UT
ValueError: The view lists.views.add_item didn't return an HttpResponse object.
It returned None instead.
lists/views.py
def add_item(request, list_id):
list_ = List.objects.get(id=list_id)
return redirect('/lists/%d/' % (list_.id,))
รัน UT
self.assertEqual(Item.objects.count(), 1)
AssertionError: 0 != 1
lists/views.py
def add_item(request, list_id):
list_ = List.objects.get(id=list_id)
Item.objects.create(text=request.POST['item_text'], list=list_)
return redirect('/lists/%d/' % (list_.id,))
รัน UT
Ran 9 tests in 0.022s
OK
But How to Use That URL in the Form?
lists/templates/list.html
<form method="POST" action="/lists/{{ list.id }}/add_item">
For that to work, the view will have to pass the list to the template. Let’s create a new unit test in ListViewTest:
lists/tests.py
def test_passes_correct_list_to_template(self):
other_list = List.objects.create()
correct_list = List.objects.create()
response = self.client.get('/lists/%d/' % (correct_list.id,))
self.assertEqual(response.context['list'], correct_list)
lists/views.py
def view_list(request, list_id):
list_ = List.objects.get(id=list_id)
return render(request, 'list.html', {'list': list_})
lists/templates/list.html
<form method="POST" action="/lists/{{ list.id }}/add_item">
[...]
{% for item in list.item_set.all %}
<tr><td>{{ forloop.counter }}: {{ item.text }}</td></tr>
{% endfor %}
A Final Refactor Using URL includes
$ cp superlists/urls.py lists/
superlists/urls.py
urlpatterns = patterns('',
url(r'^$', 'lists.views.home_page', name='home'),
url(r'^lists/', include('lists.urls')),
# url(r'^admin/', include(admin.site.urls)),
)
urlpatterns = patterns('',
url(r'^$', 'lists.views.home_page', name='home'),
url(r'^lists/', include('lists.urls')),
# url(r'^admin/', include(admin.site.urls)),
)
lists/urls.py
from django.conf.urls import patterns, url
urlpatterns = patterns('',
url(r'^(\d+)/$', 'lists.views.view_list', name='view_list'),
url(r'^(\d+)/add_item$', 'lists.views.add_item', name='add_item'),
url(r'^new$', 'lists.views.new_list', name='new_list'),
)












































ไม่มีความคิดเห็น:
แสดงความคิดเห็น