第13章 数据库层验证
13.1 模型层验证
服务端的验证分为:模型层和表单层。
13.1.1 self.assertRaises上下文管理器
lists/tests/test_models.py
def test_cannot_save_empty_list_items(self):
list_ = List.objects.create()
item = Item(list= list_,text='')
with self.assertRaises(ValidationError):
item.save()
python manage.py test lists
AssertionError: ValidationError not raised
13.1.2 Django怪异的表现:保存时不验证数据
TextField默认设置是blank=False,文本字段应该拒绝空值。SQLite不支持字段的强制空值约束,所以save()方法直接通过了验证。
Django提供了一个用于运行全部验证的方法:full_clean.
with self.assertRaises(ValidationError):
item.save()
item.full_clean()
通过了测试
13.2 在视图中年显示模型验证错误
lists/templates/base.html
<form method="POST" action="{% block form_action %}{% endblock %}">
{% csrf_token %}
<input name="item_text" id="id_new_item" placeholder="Enter a to-do item"
class="form-control input-lg">
{% if error %}
<div class="form-group has-error">
<span class="help-block">{{ error }}</span>
</div>
{% endif %}
</form>
lists/tests/test_views.py
class NewListTest(TestCase):
def test_validation_errors_are_sesnt_back_to_home_page_template(self):
response = self.client.post('/lists/new',data={'item_text':''})
self.assertEqual(response.status_code,200)
self.assertTemplateUsed(response,'home.html')
expected_error = "You can't have an empty list item"
self.assertContains(response,expected_error)
AssertionError: 302 != 200
lists/views.py
def new_list(request):
list_ = List.objects.create()
item = Item.objects.create(text=request.POST['item_text'], list=list_)
item.full_clean()
return redirect(f'/lists/{list_.id}/')
django.core.exceptions.ValidationError: {'text': ['This field cannot be blank.']}
继续修改视图:
from django.core.exceptions import ValidationError
def new_list(request):
list_ = List.objects.create()
item = Item.objects.create(text=request.POST['item_text'], list=list_)
try:
item.full_clean()
except ValidationError:
pass
return redirect(f'/lists/{list_.id}/')
AssertionError: 302 != 200
except ValidationError:
return render(request,'home.html')
AssertionError: False is not true : Couldn't find 'You can't have an empty list item' in response
传入变量
except ValidationError:
error = "You can't have an empty list item"
return render(request, 'home.html', {'error': error})
AssertionError: False is not true : Couldn't find 'You can't have an empty list item' in response
没有效果继续:
lists/tests/test_views.py
expected_error = "You can't have an empty list item"
print(response.content.decode())
<span class="help-block">You can't have an empty list item</span>
原来是转义了‘
lists/tests/test_views.py
from django.utils.html import escape
self.assertTemplateUsed(response,'home.html')
expected_error = escape("You can't have an empty list item")
self.assertContains(response,expected_error)
之后通过单元测试
确保无效的输入值不会存入数据库
lists/tests/test_views.py 增加一个测试
def test_invalid_list_items_arent_saved(self):
self.client.post('/lists/new',data={'item_text':''})
self.assertEqual(List.objects.count(),0)
self.assertEqual(Item.objects.count(),0)
AssertionError: 1 != 0
lists/views.py
try:
item.full_clean()
item.save()
except ValidationError:
list_.delete()
error = "You can't have an empty list item"
return render(request, 'home.html', {'error': error})
return redirect(f'/lists/{list_.id}/')
通过单元测试
提交:git commit -am 'Adjust new list view to do model validation'
13.3 Django模式:在渲染表单的视图中处理POST请求
现在的情况是显示清单用一个视图函数和URL,处理清单中的待办事项用的是另外一个URL。合并为一。
lists/templates/list.html
{% block form_action %}/lists/{{ list.id }}/{% endblock %}
python manage.py test functional_tests
AssertionError: '2: Use peacock feathers to make a fly' not found in ['1: Buy peacock feathers']
view_list视图还不知道如何处理请求
13.3.1 重构:把new_item实现的功能移到view_list中
lists/tests/test_views.py
删掉了NewItemTest类,修改两个方法:
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(
f'/lists/{correct_list.id}/',
data={'item_text':'A new item for an existing list'}
)
self.assertEqual(Item.objects.create(),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_POST_redirects_to_list_view(self):
other_list = List.objects.create()
correct_list = List.objects.create()
response = self.client.post(
f'/lists/{correct_list.id}/',
data={'item_text':'A new item for an existing list'}
)
self.assertRedirects(response,f'/lists/{correct_list.id}/')
python manage.py test lists
AssertionError: 200 != 302 : Response didn't redirect as expected: Response code was 200 (expected 302)
修改视图,删掉add_item
def view_list(request, list_id):
list_ = List.objects.get(id=list_id)
if request.method == 'POST':
Item.objects.create(text=request.POST['item_text'], list=list_)
return redirect(f'/lists/{list_.id}/')
return render(request, 'list.html', {'list': list_})
再测:
AttributeError: module 'lists.views' has no attribute 'add_item'
修改lists/urls.py
from django.conf.urls import url
from lists import views
urlpatterns = [
url(r'^new$', views.new_list, name='new_list'),
url(r'^(\d+)/$', views.view_list, name='view_list'),
]
python manage.py test
Ran 16 tests in 42.788s
FAILED (errors=1)
提交:git commit -am 'Refactor list view to handlenew item POSTs'
13.3.2 在view_list视图执行模型验证
lists/tests/test_views.py新增一个测试
def test_validation_errors_end_up_on_lists_page(self):
list_ = List.objects.create()
response = self.client.post(
f'/lists/{list_.id}/',
data={'item_text': ''}
)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'list.html')
expected_error = escape("You can't have an empty list item")
self.assertContains(response, expected_error)
self.assertEqual(response.status_code, 200)
AssertionError: 302 != 200
视图中执行验证
def view_list(request, list_id):
list_ = List.objects.get(id=list_id)
error = None
if request.method == 'POST':
try:
item = Item(text=request.POST['item_text'], list=list_)
item.full_clean()
item.save()
return redirect(f'/lists/{list_.id}/')
except ValidationError:
error = "You can't have an empty list item"
return render(request, 'list.html', {'list': list_, 'error': error})
通过所有测试
git commit -am 'enforce model vlidation in list view'
13.4 重构:去除硬编码的URL
url(r'^new$', views.new_list, name='new_list'),
url(r'^(\d+)/$', views.view_list, name='view_list'),
根据url修改home.html
{% extends 'base.html' %}
{% block header_text %}Start a new To-Do list{% endblock %}
{% block form_action %}{% url 'new_list' %}{% endblock %}
测试:python manage.py test lists
list.html
{% block form_action %}{% url 'view_list' list.id %}{% endblock %}
测试通过
提交:Git commit -am 'Rafactor jhard-coded URLs out of templates'
13.4.2 重定向时使用get_absolute_url
def new_list(request):
[...]
return redirect('view_list',list_.id)
lists/tests/test_models.py增加
def test_get_absolute_url(self):
list_ = List.objects.create()
self.assertEqual(list_.get_absolute_url(),f'/lists/{list_.id}/')
from django.core.urlresolvers import reverse
lists/models.py
class List(models.Model):
def get_absolute_url(self):
return reverse('view_list', args=[self.id])
视图:
def new_list(request):
return redirect(list_)
测试:
Ran 18 tests in 50.939s
OK
修改另一个:
def view_list(request, list_id):
return redirect(list_)
Ran 18 tests in 39.634s
OK
提交:Git commit -am 'Use get_absolute_url on List model to DRY urls in views '