Python Lover(3)Django Doc - POST FORM and Testing
1. Form
How the POST Form Working
<h1>{{ question.question_text }}</h1>
{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
<form action="{% url 'polls:vote' question.id %}" method="post">
{% csrf_token %}
{% for choice in question.choice_set.all %}
<input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" />
<label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br />
{% endfor %}
<input type="submit" value="Vote" />
</form>
We have the csrf_token to handle the security issue.
The Result Page
<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
<li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
{% endfor %}
</ul>
<a href="{% url 'polls:detail' question.id %}">Vote again?</a>
The Action-View
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render
from django.shortcuts import get_object_or_404
from django.core.urlresolvers import reverse
from polls.models import Question, Choice
def results(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/results.html', {'question': question})
def vote(request, question_id):
p = get_object_or_404(Question, pk=question_id)
try:
selected_choice = p.choice_set.get(pk=request.POST['choice'])
except (KeyError, Choice.DoesNotExist):
return render(request, 'polls/detail.html', {
'question': p,
'error_message': "You did not select a choice.",
})
else:
selected_choice.votes +=1
selected_choice.save()
return HttpResponseRedirect(reverse('polls:results', args=(p.id,)))
Use Generic Views: Less Code
I do not like it, I want more control of the codes.
https://docs.djangoproject.com/en/1.7/intro/tutorial04/
2. Automated Testing
2.1 Try to Creating Test Class
Find the file easypoll/polls/tests.py
import datetime
from django.test import TestCase
from django.utils import timezone
from polls.models import Question
# Create your tests here.
class QuestionMethodTests(TestCase):
def test_was_published_recently_with_future_question(self):
"""
was_published_recently() should return False for questions whose
pub_date is in the future
"""
time = timezone.now() + datetime.timedelta(days=30)
future_question = Question(pub_date=time)
self.assertEqual(future_question.was_published_recently(), False)
def test_was_published_recently_with_old_question(self):
"""
was_published_recently() should return False for questions whose
pub_date is older than 1 day
"""
time = timezone.now() - datetime.timedelta(days=30)
old_question = Question(pub_date=time)
self.assertEqual(old_question.was_published_recently(), False)
def test_was_published_recently_with_recent_question(self):
"""
was_published_recently() should return True for questions whose
pub_date is within the last day
"""
time = timezone.now() - datetime.timedelta(hours=1)
recent_question = Question(pub_date=time)
self.assertEqual(recent_question.was_published_recently(), True)
It is easy to run the Tests
>python manage.py test polls
Creating test database for alias 'default'... . ---------------------------------------------------------------------- Ran 1 test in 0.001s
2.2 Test a View
The Django Test Client
We can use it even from the Console, that is amazing.
>python manage.py shell
Prepare the Context, then we can parse the response or something like that from my understanding.
>>> from django.test.utils import setup_test_environment >>> setup_test_environment()
Set up the Client
>>> from django.test import Client >>> client = Client()
Using the client to talk to Our Server
>>> response = client.get('/') >>> response.status_code 404
Reverse the URL from Server Conf
>>> from django.core.urlresolvers import reverse >>> response = client.get(reverse('polls:index')) >>> response.status_code 200
>>> response.content b'\n <ul>\n \n <li><a href="/polls/2/">National</a></li>\n \n <li><a href="/polls/1/">what's up?</a></li>\n \n </ul>\n'
Directly hit the URL
>>> response = client.get('/polls/') >>> response.content b'\n <ul>\n \n <li><a href="/polls/2/">National</a></li>\n \n <li><a href="/polls/1/">what's up?</a></li>\n \n </ul>\n'
Directly check the context, that is really amazing and you do not need to have your app running.
>>> response.context['latest_question_list'] [<Question: National 2014-09-15 19:23:15+00:00>, <Question: what's up? 2014-09-15 17:25:50+00:00>]
Not from the Shell, Write in tests.py
from django.test import TestCase
from django.utils import timezone
from django.core.urlresolvers import reverse
from polls.models import Question
#public static method?
def create_question(question_text, days):
"""
Creates a question with the given `question_text` 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)
Test Classes
class QuestionViewTests(TestCase):
def test_index_view_with_no_questions(self):
"""
If no questions exist, an appropriate message should be 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_index_view_with_a_past_question(self):
"""
Questions with a pub_date in the past should be 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_index_view_with_a_future_question(self):
"""
Questions with a pub_date in the future should not be displayed on
the index page.
"""
create_question(question_text="Future question.", days=30)
response = self.client.get(reverse('polls:index'))
#print(response.content)
self.assertContains(response, "No polls are available.",
status_code=200)
self.assertQuerysetEqual(response.context['latest_question_list'], [])
def test_index_view_with_future_question_and_past_question(self):
"""
Even if both past and future questions exist, only past questions
should be 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_index_view_with_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 QuestionIndexDetailTests(TestCase):
def test_detail_view_with_a_future_question(self):
"""
The detail view of a question with a pub_date in the future should
return a 404 not found.
"""
future_question = create_question(question_text='Future question.',
days=5)
response = self.client.get(reverse('polls:detail',
args=(future_question.id,)))
self.assertEqual(response.status_code, 404)
def test_detail_view_with_a_past_question(self):
"""
The detail view of a question with a pub_date in the past should
display the question's text.
"""
past_question = create_question(question_text='Past Question.',
days=-5)
response = self.client.get(reverse('polls:detail',
args=(past_question.id,)))
self.assertContains(response, past_question.question_text,
status_code=200)
Tips
gte, lte seems to me, it is less, greater.
You do not need to have your app running when you doing the tests. Run the test like this
>python manage.py test polls
Creating test database for alias 'default'... .......... ---------------------------------------------------------------------- Ran 10 tests in 0.039s
Some Issues and Fixes
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render
from django.shortcuts import get_object_or_404
from django.core.urlresolvers import reverse
from django.utils import timezone
from django.http import Http404
from polls.models import Question, Choice
# Create your views here.
def index(request):
latest_question_list = Question.objects.filter(
pub_date__lte = timezone.now()
).order_by('-pub_date')[:5]
#latest 5 records
context = {'latest_question_list': latest_question_list}
return render(request, 'polls/index.html', context)
def detail(request, question_id):
try:
question = Question.objects.filter(
pub_date__lte = timezone.now()
).get(pk=question_id)
except Question.DoesNotExist:
raise Http404
return render(request, 'polls/detail.html', {'question': question})
References:
https://docs.djangoproject.com/en/1.7/intro/tutorial04/
https://docs.djangoproject.com/en/1.7/intro/tutorial05/
More Topic
https://docs.djangoproject.com/en/1.7/topics/
How to deploy
https://docs.djangoproject.com/en/1.7/howto/deployment/
Spawn-fcgi, wsgi, fastcgi, cgi
http://lihuipeng.blog.51cto.com/3064864/890573