Compare commits

..

No commits in common. "d6058c52104525ac08a055052c844e3942bd650d" and "c7cb960daabcd3b5ca1eeaa95f51cbf87c565d40" have entirely different histories.

4 changed files with 6 additions and 414 deletions

View File

@ -416,278 +416,4 @@ class ResultsView(generic.DetailView):
* 而这里我们使用 `template_name` 来指定模板 * 而这里我们使用 `template_name` 来指定模板
* 我们提供 context_object_name 属性,表示我们想使用 latest_question_list * 我们提供 context_object_name 属性,表示我们想使用 latest_question_list
重启服务器后就是新的投票应用同样的html但是内部用了generic view 重启服务器后就是新的投票应用同样的html但是内部用了generic view
## Part 5 Automated Test
TDD with python. Always stick to it.
### 5.1 First test
#### Create/Verify a bug
幸运的是,我们的 polls 应用现在就有一个小 bug 需要被修复:我们的要求是如果 Question 是在一天之内发布的, Question.was_published_recently() 方法将会返回 True ,然而现在这个方法在 Question 的 pub_date 字段比当前时间还晚时也会返回 True这是个 Bug
确认bug:
```
>>> import datetime
>>> from django.utils import timezone
>>> from polls.models import Question
>>> # create a Question instance with pub_date 30 days in the future
>>> future_question = Question(pub_date=timezone.now() + datetime.timedelta(days=30))
>>> # was it published recently?
>>> future_question.was_published_recently()
True
```
#### Create a test to verify this bug
All tests (polls/tests.py) are in app directory
```python
import datetime
from django.test import TestCase
from django.utils import timezone
from .models import Question
class QuestionModelTests(TestCase):
def test_was_published_recently_with_future_question(self):
"""
was_published_recently() returns False for questions whose pub_date is in future
"""
time = timezone.now() + datetime.timedelta(days=30)
future_question = Question(pub_date=time)
self.assertIs(future_question.was_published_recently(), False)
```
Result
```
python manage.py test polls
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
F
======================================================================
FAIL: test_was_published_recently_with_future_question (polls.tests.QuestionModelTests)
was_published_recently() returns False for questions whose pub_date is in future
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/jason/HomeWorkstation/SynologyGiteaSpace/djangoproject/src/polls/tests.py", line 17, in test_was_published_recently_with_future_question
self.assertIs(future_question.was_published_recently(), False)
AssertionError: True is not False
----------------------------------------------------------------------
Ran 1 test in 0.001s
FAILED (failures=1)
Destroying test database for alias 'default'...
```
解释上述msg:
* python manage.py test polls 将会寻找 polls 应用里的测试代码
* 它找到了 django.test.TestCase 的一个子类
* 它创建一个特殊的数据库供测试使用
* 它在类中寻找测试方法——以 test 开头的方法。
* 在 test_was_published_recently_with_future_question 方法中,它创建了一个 pub_date 值为 30 天后的 Question 实例。
* 接着使用 assertls() 方法,发现 was_published_recently() 返回了 True而我们期望它返回 False。
#### Fix the bug
Fix bug in `polls/models.py`
```python
def was_published_recently(self):
now = timezone.now()
return now - datetime.timedelta(days=1) <= self.pub_date <= now
```
#### Comprehensive Tests
Write more tests to make sure it's comprehensive
```python
def test_was_published_recently_with_old_question(self):
"""
was_published_recently() returns False for questions whose pub_date is older than 1 day.
"""
time = timezone.now() - datetime.timedelta(days=1, seconds=1)
old_quesion = Question(pub_date=time)
self.assertIs(old_quesion.was_published_recently(), False)
def test_was_published_recently_with_recent_question(self):
"""
was_published_recently() returns True for questions whose pub_date is within the last day.
"""
time = timezone.now() - datetime.timedelta(hours=23, minutes=59, seconds=59)
recent_question = Question(pub_date=time)
self.assertIs(recent_question.was_published_recently(), True)
```
### 5.2 Test a view
Target: 如果 pub_date 设置为未来某天,这应该被解释为这个问题将在所填写的时间点才被发布,而在之前是不可见的。
#### A test for a view
We use TDD (i.e. test first, then develop)
#### The Django test client
Django provides a test **Client** (a class) to simulate a user interacting with the code at the view level.
We can try it in interactive shell following the guide
#### Test and Improve our view
Add following in `tests.py`
```python
def create_question(question_text, days):
"""
Create a question with the given `question_text` and published the
given number of `days` offset to now (negative for questions published
in the past, positive for questions that have yet to be published).
"""
time = timezone.now() + datetime.timedelta(days=days)
return Question.objects.create(question_text=question_text, pub_date=time)
class QuestionIndexViewTests(TestCase):
def test_no_questions(self):
"""
If no questions exist, an appropriate message is displayed.
"""
response = self.client.get(reverse('polls:index'))
self.assertEqual(response.status_code, 200)
self.assertContains(response, "No polls are available.")
self.assertQuerysetEqual(response.context['latest_question_list'], [])
def test_past_question(self):
"""
Questions with a pub_date in the past are displayed on the
index page.
"""
create_question(question_text="Past question.", days=-30)
response = self.client.get(reverse('polls:index'))
self.assertQuerysetEqual(
response.context['latest_question_list'],
['<Question: Past question.>']
)
def test_future_question(self):
"""
Questions with a pub_date in the future aren't displayed on
the index page.
"""
create_question(question_text="Future question.", days=30)
response = self.client.get(reverse('polls:index'))
self.assertContains(response, "No polls are available.")
self.assertQuerysetEqual(response.context['latest_question_list'], [])
def test_future_question_and_past_question(self):
"""
Even if both past and future questions exist, only past questions
are displayed.
"""
create_question(question_text="Past question.", days=-30)
create_question(question_text="Future question.", days=30)
response = self.client.get(reverse('polls:index'))
self.assertQuerysetEqual(
response.context['latest_question_list'],
['<Question: Past question.>']
)
def test_two_past_questions(self):
"""
The questions index page may display multiple questions.
"""
create_question(question_text="Past question 1.", days=-30)
create_question(question_text="Past question 2.", days=-5)
response = self.client.get(reverse('polls:index'))
self.assertQuerysetEqual(
response.context['latest_question_list'],
['<Question: Past question 2.>', '<Question: Past question 1.>']
)
```
Testing generated
```
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
FF......
======================================================================
FAIL: test_future_question (polls.tests.QuestionIndexViewTests)
Questions with a pub_date in the future aren't displayed on
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/jason/HomeWorkstation/SynologyGiteaSpace/djangoproject/src/polls/tests.py", line 77, in test_future_question
self.assertContains(response, "No polls are available.")
File "/home/jason/miniconda3/envs/django/lib/python3.8/site-packages/django/test/testcases.py", line 470, 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 'No polls are available.' in response
======================================================================
FAIL: test_future_question_and_past_question (polls.tests.QuestionIndexViewTests)
Even if both past and future questions exist, only past questions
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/jason/HomeWorkstation/SynologyGiteaSpace/djangoproject/src/polls/tests.py", line 88, in test_future_question_and_past_question
self.assertQuerysetEqual(
File "/home/jason/miniconda3/envs/django/lib/python3.8/site-packages/django/test/testcases.py", line 1052, in assertQuerysetEqual
return self.assertEqual(list(items), values, msg=msg)
AssertionError: Lists differ: ['<Question: Future question.>', '<Question: Past question.>'] != ['<Question: Past question.>']
First differing element 0:
'<Question: Future question.>'
'<Question: Past question.>'
First list contains 1 additional elements.
First extra element 1:
'<Question: Past question.>'
- ['<Question: Future question.>', '<Question: Past question.>']
+ ['<Question: Past question.>']
----------------------------------------------------------------------
Ran 8 tests in 0.018s
FAILED (failures=2)
Destroying test database for alias 'default'...
```
Fix it by change `get_queryset()` method in Question. 让他它能通过将 Question 的 pub_data 属性与 timezone.now() 相比较来判断是否应该显示此 Question
#### Testing the DetailView
Target: hide future questions even if user type correct URL
Add test cases first
```python
class QuestionDetailViewTests(TestCase):
def test_future_question(self):
"""
The detail view of a question with a pub_date in the future returns a 404 not found.
"""
future_question = create_question(question_text="Future question.", days=5)
url = reverse('poll:detail', args=(future_question.id, ))
response = self.client.get(url)
self.assertEqual(response.status_code, 404)
def test_past_questioin(self):
"""
The detail view of a question with a pub_date in the past displays the question's text.
"""
past_question = create_question(question_text='Past Question.', days=-5)
url = reverse('polls:detail', args=(past_question.id,))
response = self.client.get(url)
self.assertContains(response, past_question.question_text)
```

View File

@ -1,7 +1,7 @@
import datetime import datetime
from django.db import models from django.db import models
from django.utils import timezone from django.utils import timezone as djtz
class Question(models.Model): class Question(models.Model):
# question_text & pub_date are instances of field # question_text & pub_date are instances of field
@ -9,8 +9,7 @@ class Question(models.Model):
pub_date = models.DateTimeField('date published') pub_date = models.DateTimeField('date published')
def was_published_recently(self): def was_published_recently(self):
now = timezone.now() return self.pub_date >= djtz.now() - datetime.timedelta(days=1)
return now - datetime.timedelta(days=1) <= self.pub_date <= now
def __str__(self) -> str: def __str__(self) -> str:
return self.question_text return self.question_text

View File

@ -1,124 +1,3 @@
import datetime
from django.conf.urls import url
from django.http import response
from django.test import TestCase from django.test import TestCase
from django.utils import timezone
from django.urls import reverse
from .models import Question # Create your tests here.
class QuestionModelTests(TestCase):
def test_was_published_recently_with_future_question(self):
"""
was_published_recently() returns False for questions whose pub_date is in future
"""
time = timezone.now() + datetime.timedelta(days=30)
future_question = Question(pub_date=time)
self.assertIs(future_question.was_published_recently(), False)
def test_was_published_recently_with_old_question(self):
"""
was_published_recently() returns False for questions whose pub_date is older than 1 day.
"""
time = timezone.now() - datetime.timedelta(days=1, seconds=1)
old_quesion = Question(pub_date=time)
self.assertIs(old_quesion.was_published_recently(), False)
def test_was_published_recently_with_recent_question(self):
"""
was_published_recently() returns True for questions whose pub_date is within the last day.
"""
time = timezone.now() - datetime.timedelta(hours=23, minutes=59, seconds=59)
recent_question = Question(pub_date=time)
self.assertIs(recent_question.was_published_recently(), True)
def create_question(question_text, days):
"""
Create a question with the given `question_text` and published the
given number of `days` offset to now (negative for questions published
in the past, positive for questions that have yet to be published).
"""
time = timezone.now() + datetime.timedelta(days=days)
return Question.objects.create(question_text=question_text, pub_date=time)
class QuestionIndexViewTests(TestCase):
def test_no_questions(self):
"""
If no questions exist, an appropriate message is displayed.
"""
response = self.client.get(reverse('polls:index'))
self.assertEqual(response.status_code, 200)
self.assertContains(response, "No polls are available.")
self.assertQuerysetEqual(response.context['latest_question_list'], [])
def test_past_question(self):
"""
Questions with a pub_date in the past are displayed on the
index page.
"""
create_question(question_text="Past question.", days=-30)
response = self.client.get(reverse('polls:index'))
self.assertQuerysetEqual(
response.context['latest_question_list'],
['<Question: Past question.>']
)
def test_future_question(self):
"""
Questions with a pub_date in the future aren't displayed on
the index page.
"""
create_question(question_text="Future question.", days=30)
response = self.client.get(reverse('polls:index'))
self.assertContains(response, "No polls are available.")
self.assertQuerysetEqual(response.context['latest_question_list'], [])
def test_future_question_and_past_question(self):
"""
Even if both past and future questions exist, only past questions
are displayed.
"""
create_question(question_text="Past question.", days=-30)
create_question(question_text="Future question.", days=30)
response = self.client.get(reverse('polls:index'))
self.assertQuerysetEqual(
response.context['latest_question_list'],
['<Question: Past question.>']
)
def test_two_past_questions(self):
"""
The questions index page may display multiple questions.
"""
create_question(question_text="Past question 1.", days=-30)
create_question(question_text="Past question 2.", days=-5)
response = self.client.get(reverse('polls:index'))
self.assertQuerysetEqual(
response.context['latest_question_list'],
['<Question: Past question 2.>', '<Question: Past question 1.>']
)
class QuestionDetailViewTests(TestCase):
def test_future_question(self):
"""
The detail view of a question with a pub_date in the future returns a 404 not found.
"""
future_question = create_question(question_text="Future question.", days=5)
url = reverse('polls:detail', args=(future_question.id, ))
response = self.client.get(url)
self.assertEqual(response.status_code, 404)
def test_past_questioin(self):
"""
The detail view of a question with a pub_date in the past displays the question's text.
"""
past_question = create_question(question_text='Past Question.', days=-5)
url = reverse('polls:detail', args=(past_question.id,))
response = self.client.get(url)
self.assertContains(response, past_question.question_text)

View File

@ -3,7 +3,6 @@ from django.shortcuts import render, get_object_or_404
from django.template import loader from django.template import loader
from django.urls import reverse from django.urls import reverse
from django.views import generic from django.views import generic
from django.utils import timezone
from .models import Question, Choice from .models import Question, Choice
@ -12,24 +11,13 @@ class IndexView(generic.ListView):
context_object_name = 'latest_question_list' context_object_name = 'latest_question_list'
def get_queryset(self): def get_queryset(self):
""" """Return the last five published questions."""
Return the last five published questions (not including those set to be published in the future). return Question.objects.order_by('-pub_date')[:5]
"""
return Question.objects.filter(
pub_date__lte=timezone.now()
).order_by('-pub_date')[:5]
# return Question.objects.order_by('-pub_date')[:5]
class DetailView(generic.DetailView): class DetailView(generic.DetailView):
model = Question model = Question
template_name = 'polls/detail.html' template_name = 'polls/detail.html'
def get_queryset(self):
"""
Excludes any questions that aren't published yet
"""
return Question.objects.filter(pub_date__lte=timezone.now())
class ResultsView(generic.DetailView): class ResultsView(generic.DetailView):
model = Question model = Question
template_name = 'polls/results.html' template_name = 'polls/results.html'