接下来要使用标准的 HTML POST 请求,若想让浏览器发送 POST 请求,要给 <input> 元素指定 name= 属性,然后把它放在 <form> 标签中,并为 <form> 标签指定 method="POST" 属性,这样浏览器才能向服务器发送 POST 请求。调整一下 home.html 中的模板:
<h1>
Your To-Do list
</h1>
<form method="POST">
<input name="item_next" id="id_new_item" placeholder="Enter a to-do item" />
</form>
<table id="id_list_table">
</table>
可以看到,提交表单后新添加的待办事项不见了,页面刷新后又显示了一个空表单。这是因为还没连接服务器让它处理 POST 请求,所以服务器忽略请求,直接显示常规首页。
5.2 在服务器中处理 POST 请求
还没为表单指定 action= 属性,因此提交表单后默认返回之前渲染的页面,即 "/",这个页面由视图函数 home_page 处理。下面修改这个函数,让其能处理 POST 请求。
这意味着要为视图函数 home_page 编写一个新的单元测试。打开 lists/tests.py 文件,在 HomePageTest 类中添加一个新方法。在其中添加 POST 请求,再检查返回的 HTML 中是否有新添加的待办事项文本:
def test_home_page_can_save_a_POST_request(self):
request = HttpRequest()
request.method = "POST"
request.POST["item_text"] = "A new list item"
response = home_page(request)
self.assertIn("A new list item", response.content.decode())
“设置配置-执行代码-编写断言”是单元测试的典型结构
运行测试后,会看到预期的失败:python manage.py test
为了让测试通过,可以故意编写一个符合测试的返回值,更改 lists/views.py:
from django.http import HttpResponse
from django.shortcuts import render
def home_page(request):
if request.method == "POST":
return HttpResponse(request.POST["item_text"])
return render(request, "home.html")
<body>
<h1>
Your To-Do list
</h1>
<form method="POST">
<input name="item_text" id="id_new_item" placeholder="Enter a to-do item" />{% csrf_token %}
</form>
<table id="id_list_table">
<tr><td>{{ new_item_text }}</td></tr>
</table>
</body>
在前一个单元测试中已经用到了 render_to_string 函数,用它手动渲染模板,然后拿它的返回值和视图函数返回的 HTML 比较。下面添加想传入的变量:
self.assertIn("A new list item", response.content.decode())
expected_html = render_to_string(
"home.html",
{"new_item_text": "A new list item"}
)
self.assertEqual(response.content.decode(), expected_html)
可以看出,render_to_string 函数的第二个参数是变量名到值的映射。向模板中传入了一个名为 new_item_text 的变量,其值是期望在 POST 请求中发送的待办事项文本。
运行这个单元测试时,render_to_string 函数会把 <td> 中的{{ new_item_text }} 替换成“A new list item”。视图函数目前还无法做到这一点,因此会看到失败。
# 页面中又显示了一个文本框, 可以输入其他的待办事项
# Y 输入了 Use pen to take notes
# Y 做事很有条理
input_box = self.browser.find_element_by_id("id_new_item")
input_box.send_keys("Use pen to take notes")
input_box.send_keys(Keys.ENTER)
# 页面再次更新, 她的清单中显示了这两个待办事项
table = self.browser.find_element_by_id("id_list_table")
rows = table.find_elements_by_tag_name("tr")
self.assertIn("1: Buy pen", [row.text for row in rows])
self.assertIn("2: Use pen to take notes", [row.text for row in rows])
# Y 想知道这个网站是否会记住她的清单
self.fail("Finish the test!") # 不管怎样, self.fail 都会失败, 生成指定的错误消息。我使用这个方法提醒测试结束了。
运行功能测试,很显然会返回一个错误:
AssertionError: '1: Buy pen' not found in ['1: Use pen to take notes']
# Y 按下回车键后, 页面更新了
# 待办事项表格中显示了 "1: Buy pen"
input_box.send_keys(Keys.ENTER)
self.check_for_low_in_list_table("1: Buy pen")
# 页面中又显示了一个文本框, 可以输入其他的待办事项
# Y 输入了 Use pen to take notes
# Y 做事很有条理
input_box = self.browser.find_element_by_id("id_new_item")
input_box.send_keys("Use pen to take notes")
input_box.send_keys(Keys.ENTER)
# 页面再次更新, 她的清单中显示了这两个待办事项
self.check_for_low_in_list_table("1: Buy pen")
self.check_for_low_in_list_table("2: Use pen to take notes")
# Y 想知道这个网站是否会记住她的清单
self.fail("Finish the test!") # 不管怎样, self.fail 都会失败, 生成指定的错误消息。我使用这个方法提醒测试结束了。
再次运行功能测试,看重构前后的表现是否一致,之后提交这次针对功能测试的重构:
git diff # 查看 functional_tests.py 中的改动
git commit -a
from lists.models import Item
class ItemModelTest(TestCase):
def test_saving_and_retrieving_items(self):
first_item = Item()
first_item.text = "The first (ever) list item"
first_item.save()
second_item = Item()
second_item.text = "Item the second"
second_item.save()
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(second_saved_item.text, "Item the second")