Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

rewrite 04 for 1.4. changes to view/forms tests. HP

  • Loading branch information...
commit 66f3c37260e3bb0d0f9c6dd56262ee3c717d456b 1 parent 86ef9ed
@hjwp authored
View
2  mysite/fts/test_polls.py
@@ -100,7 +100,7 @@ def test_voting_on_a_new_poll(self):
self.assertEquals(len(choice_inputs), 3)
# The buttons have labels to explain them
- choice_labels = choice_inputs = self.browser.find_elements_by_tag_name('label')
+ choice_labels = self.browser.find_elements_by_tag_name('label')
choices_text = [c.text for c in choice_labels]
self.assertEquals(choices_text, [
'Vote:', # this label is auto-generated for the whole form
View
2  mysite/polls/forms.py
@@ -4,7 +4,7 @@ class PollVoteForm(forms.Form):
vote = forms.ChoiceField(widget=forms.RadioSelect())
def __init__(self, poll):
- super(self.__class__, self).__init__()
+ forms.Form.__init__(self)
self.fields['vote'].choices = [(c.id, c.choice) for c in poll.choice_set.all()]
View
31 mysite/polls/tests/test_views.py
@@ -39,24 +39,16 @@ def test_root_url_shows_links_to_all_polls(self):
class TestSinglePollView(TestCase):
def test_page_shows_poll_title_and_no_votes_message(self):
- # set up two polls, to check the right one gets used
+ # set up two polls, to check the right one is displayed
poll1 = Poll(question='6 times 7', pub_date='2001-01-01')
poll1.save()
- choice1 = Choice(poll=poll1, choice='42', votes=0)
- choice1.save()
- choice2 = Choice(poll=poll1, choice='The Ultimate Answer', votes=0)
- choice2.save()
- poll2 = Poll(question='time', pub_date='2001-01-01')
+ poll2 = Poll(question='life, the universe and everything', pub_date='2001-01-01')
poll2.save()
- choice3 = Choice(poll=poll2, choice='PM', votes=0)
- choice3.save()
- choice4 = Choice(poll=poll2, choice="Gardener's", votes=0)
- choice4.save()
client = Client()
response = client.get('/poll/%d/' % (poll2.id, ))
- # check we've used the right template
+ # check we've used the poll template
self.assertTemplateUsed(response, 'poll.html')
# check we've passed the right poll into the context
@@ -68,13 +60,26 @@ def test_page_shows_poll_title_and_no_votes_message(self):
# check our 'no votes yet' message appears
self.assertIn('No-one has voted on this poll yet', response.content)
+
+ def test_page_shows_choices_using_form(self):
+ # set up a poll with choices
+ poll1 = Poll(question='time', pub_date=timezone.now())
+ poll1.save()
+ choice1 = Choice(poll=poll1, choice="PM", votes=0)
+ choice1.save()
+ choice2 = Choice(poll=poll1, choice="Gardener's", votes=0)
+ choice2.save()
+
+ client = Client()
+ response = client.get('/poll/%d/' % (poll1.id, ))
+
# check we've passed in a form of the right type
self.assertTrue(isinstance(response.context['form'], PollVoteForm))
# and check the check the form is being used in the template,
# by checking for the choice text
- self.assertIn(choice3.choice, response.content)
- self.assertIn(choice4.choice, response.content.replace(''', "'"))
+ self.assertIn(choice1.choice, response.content)
+ self.assertIn(choice2.choice, response.content.replace(''', "'"))
def test_view_shows_percentage_of_votes_and_total_votes(self):
View
2  tutorial03.rst
@@ -295,7 +295,7 @@ Re-running our tests should show us a different error::
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/harry/workspace/tddjango_site/source/mysite/polls/tests.py", line 92, in test_root_url_shows_all_polls
- respoviense = client.get('/')
+ response = client.get('/')
File "/usr/lib/pymodules/python2.7/django/test/client.py", line 445, in get
response = super(Client, self).get(path, data=data, **extra)
File "/usr/lib/pymodules/python2.7/django/test/client.py", line 229, in get
View
196 tutorial04.rst
@@ -1,5 +1,4 @@
-Welcome to part 4 of the tutorial! In this part at how we can let
-users vote on our poll, in other words, **web forms!**. Hooray.
+Welcome to part 4 of the tutorial! In this part at how we can let users vote on our poll, in other words, **web forms!**. Hooray.
Tutorial 4: Using a form
========================
@@ -16,8 +15,7 @@ Here's the outline of what we're going to do in this tutorial:
Extending the FT to vote using radio buttons
--------------------------------------------
-Let's start by extending our FT, to show Herbert voting on a poll. In
-``fts/test_polls.py``:
+Let's start by extending our FT, to show Herbert voting on a poll. In ``fts/test_polls.py``:
.. sourcecode:: python
:filename: mysite/fts/test_polls.py
@@ -45,13 +43,13 @@ Let's start by extending our FT, to show Herbert voting on a poll. In
# He also sees a form, which offers him several choices.
# There are three options with radio buttons
- choices = self.browser.find_elements_by_css_selector(
+ choice_inputs = self.browser.find_elements_by_css_selector(
"input[type='radio']"
)
self.assertEquals(len(choice_inputs), 3)
# The buttons have labels to explain them
- choice_labels = choice_inputs = self.browser.find_elements_by_tag_name('label')
+ choice_labels = self.browser.find_elements_by_tag_name('label')
choices_text = [c.text for c in choice_labels]
self.assertEquals(choices_text, [
'Very awesome',
@@ -106,14 +104,17 @@ in ``polls/tests.py``:
def test_page_shows_poll_title_and_no_votes_message(self):
# set up two polls, to check the right one is displayed
- poll1 = Poll(question='6 times 7', pub_date='2001-01-01')
+ poll1 = Poll(question='6 times 7', pub_date=timezone.now())
poll1.save()
- poll2 = Poll(question='life, the universe and everything', pub_date='2001-01-01')
+ poll2 = Poll(question='life, the universe and everything', pub_date=timezone.now())
poll2.save()
client = Client()
response = client.get('/poll/%d/' % (poll2.id, ))
+ # check we've used the poll template
+ self.assertTemplateUsed(response, 'poll.html')
+
# check we've passed the right poll into the context
self.assertEquals(response.context['poll'], poll2)
@@ -123,9 +124,6 @@ in ``polls/tests.py``:
# check our 'no votes yet' message appears
self.assertIn('No-one has voted on this poll yet', response.content)
- # check we've passed in a form of the right type
- self.assertTrue(isinstance(response.context['form'], PollVoteForm))
-
Running the tests gives::
@@ -156,12 +154,10 @@ Again, a minimal fix:
Now we get this error::
- self.assertEquals(response.templates[0].name, 'poll.html')
- IndexError: list index out of range
+ AssertionError: No templates used to render the response
+
-A slightly unhelpful error, but essentially it's telling us that the
-view didn't use a template. Let's try fixing that - but deliberately
-using the wrong template (just to check we are testing it)
+Let's try fixing that - but deliberately using the wrong template (just to check we are testing it)
.. sourcecode:: python
:filename: mysite/polls/views.py
@@ -171,7 +167,7 @@ using the wrong template (just to check we are testing it)
Good, looks like we are testiing it properly::
- AssertionError: 'home.html' != 'poll.html'
+ AssertionError: Template 'poll.html' was not a template used to render the response. Actual template(s) used: home.html
And changing it to ``poll.html`` gives us::
@@ -181,7 +177,7 @@ Fine and dandy, let's make one::
touch polls/templates/poll.html
-Now the tests want us to pass a ``poll`` variable in the template's context::
+You might argue that an empty file, all 0 bytes of it, is a fairly minimal template! Still, it seems to satisfy the tests. Now they want us to pass a ``poll`` variable in the template's context::
KeyError: 'poll'
@@ -206,24 +202,16 @@ And they even tell us what to do next - pass in the right `Poll` object:
poll = Poll.objects.get(pk=poll_id)
return render(request, 'poll.html', {'poll': poll})
-This is the first time we've used the Django API to fetch a single database
-object, and ``objects.get`` is the helper function for this - it raises an
-error if it can't find the object, or if it finds more than one. The special
-keyword argument ``pk`` stands for `primary key`. In this case, Django is
-using the default for primary keys, which is an automatically genereated
-integer ``id`` column.
+This is the first time we've used the Django API to fetch a single database object, and ``objects.get`` is the helper function for this - it raises an error if it can't find the object, or if it finds more than one. The special keyword argument ``pk`` stands for `primary key`. In this case, Django is using the default for primary keys, which is an automatically generated integer ``id`` column.
-That raises the question of what to do if a user types in a url for a poll
-that doesn't exist - ``/poll/0/`` for example. We'll come back to this in
-a later tutorial.
+That raises the question of what to do if a user types in a url for a poll that doesn't exist - ``/poll/0/`` for example. We'll come back to this in a later tutorial.
In the meantime, what do the tests say::
self.assertIn(poll2.question, response.content)
AssertionError: 'life, the universe and everything' not found in ''
-We need to get our template to include the poll's question. Let's make it
-into a page heading:
+We need to get our template to include the poll's question. Let's make it into a page heading:
.. sourcecode:: html+django
:filename: mysite/polls/templates/home.html
@@ -300,26 +288,17 @@ Now what does the FT say?::
FAIL: test_voting_on_a_new_poll (test_polls.TestPolls)
----------------------------------------------------------------------
Traceback (most recent call last):
- File "/home/harry/workspace/tddjango_site/source/mysite/fts/test_polls.py", line 82, in test_voting_on_a_new_poll
- 'Moderately awesome',
- AssertionError: Lists differ: [] != ['Very awesome', 'Quite awesom...
-
- Second list contains 3 additional elements.
- First extra element 0:
- Very awesome
+ File "/home/harry/workspace/mysite/fts/test_polls.py", line 100, in test_voting_on_a_new_poll
+ self.assertEquals(len(choice_inputs), 3)
+ AssertionError: 0 != 3
- - []
- + ['Very awesome', 'Quite awesome', 'Moderately awesome']
----------------------------------------------------------------------
-Ah, we need to add the poll Choices as a series of radio inputs. Now the official Django
-tutorial shows you how to hard-code them in HTML:
+Ah, we need to add the poll Choices as a series of radio inputs. Now the official Django tutorial shows you how to hard-code them in HTML:
-https://docs.djangoproject.com/en/1.3/intro/tutorial04/
+https://docs.djangoproject.com/en/1.4/intro/tutorial04/
-But Django can do even better than that - Django's forms system will generate
-radio buttons for us, if we can just give it the right incantations. Let's
-create a new test in ``tests.py``:
+But Django can do even better than that - Django's forms system will generate radio buttons for us, if we can just give it the right incantations. Let's create a new test in ``tests.py``:
.. sourcecode:: python
@@ -331,7 +310,7 @@ create a new test in ``tests.py``:
def test_form_renders_poll_choices_as_radio_inputs(self):
# set up a poll with a couple of choices
- poll1 = Poll(question='6 times 7', pub_date='2001-01-01')
+ poll1 = Poll(question='6 times 7', pub_date=timezone.now())
poll1.save()
choice1 = Choice(poll=poll1, choice='42', votes=0)
choice1.save()
@@ -339,7 +318,7 @@ create a new test in ``tests.py``:
choice2.save()
# set up another poll to make sure we only see the right choices
- poll2 = Poll(question='time', pub_date='2001-01-01')
+ poll2 = Poll(question='time', pub_date=timezone.now())
poll2.save()
choice3 = Choice(poll=poll2, choice='PM', votes=0)
choice3.save()
@@ -361,13 +340,9 @@ create a new test in ``tests.py``:
You might prefer to put the import at the top of the file.
-Looking through the code, you can see we instantiate a form, passing it a poll object.
-We then examine the form's ``fields`` attribute, find the one called ``vote``
-(this will also be the ``name`` of the HTML input element), and we check the
-``choices`` for that field.
+Looking through the code, you can see we instantiate a form, passing it a poll object. We then examine the form's ``fields`` attribute, find the one called ``vote`` (this will also be the ``name`` of the HTML input element), and we check the ``choices`` for that field.
-For the test to even get off the ground, we may as well create something
-minimal for it to import! Create a file called ``polls/forms.py``.
+For the test to even get off the ground, we may as well create something minimal for it to import! Create a file called ``polls/forms.py``.
.. sourcecode:: python
:filename: mysite/polls/forms.py
@@ -397,8 +372,7 @@ We override ``__init__.py`` to change the constructor:
self.assertEquals(form.fields.keys(), ['vote'])
AttributeError: 'PollVoteForm' object has no attribute 'fields'
-To give the form a 'fields' attribute, we can make it inherit from
-a real Django form class, and call its parent constructor:
+To give the form a 'fields' attribute, we can make it inherit from a real Django form class, and call its parent constructor:
.. sourcecode:: python
:filename: mysite/polls/forms.py
@@ -407,19 +381,15 @@ a real Django form class, and call its parent constructor:
class PollVoteForm(forms.Form):
def __init__(self, poll):
- super(self.__class__, self).__init__()
+ forms.Form.__init__(self)
-One day, Python 3 will make all this ``super`` nonsense much more sensible.
-One day. Anyway, now we get::
+Now we get::
AssertionError: Lists differ: [] != ['vote']
-Django form fields are defined a bit like model fields - using inline
-class attributes. There are various types of fields, in this case
-we want one that has `choices` - a ``ChoiceField``.
-You can find out more about form fields here:
+Django form fields are defined a bit like model fields - using inline class attributes. There are various types of fields, in this case we want one that has `choices` - a ``ChoiceField``. You can find out more about form fields here:
-https://docs.djangoproject.com/en/1.3/ref/forms/fields/
+https://docs.djangoproject.com/en/1.4/ref/forms/fields/
.. sourcecode:: python
:filename: mysite/polls/forms.py
@@ -428,37 +398,29 @@ https://docs.djangoproject.com/en/1.3/ref/forms/fields/
vote = forms.ChoiceField()
def __init__(self, poll):
- super(self.__class__, self).__init__()
+ forms.Form.__init__(self)
Now we get::
AssertionError: Lists differ: [] != [(1, '42'), (2, 'The Ultimate ...
-So now let's set the choices from the ``poll`` we passed into the
-constructor (you can read up on choices in Django here)
-
-https://docs.djangoproject.com/en/1.3/ref/models/fields/#field-choices
+So now let's set the choices from the ``poll`` we passed into the constructor (you can read up on choices in Django here https://docs.djangoproject.com/en/1.4/ref/models/fields/#field-choices)
.. sourcecode:: python
:filename: mysite/polls/forms.py
def __init__(self, poll):
- super(self.__class__, self).__init__()
+ forms.Form.__init__(self)
self.fields['vote'].choices = [(c.id, c.choice) for c in poll.choice_set.all()]
-Mmmmmh, list comprehensions...
+Mmmmmh, list comprehensions... That will now get the test almost to the end - we can instantiate a form using a poll object, and the form will automatically generate the choices based on the poll's ``choice_set.all()`` function, which gets related objects.
-The final test is to make sure we have radio boxes as the HTML input type.
-
-We're using ``as_p()``, a method provided on all Django forms which renders
-the form to HTML for us - we can see exactly what the HTML looks like in the
-next test output::
+The final test is to make sure we have radio boxes as the HTML input type. We're using ``as_p()``, a method provided on all Django forms which renders the form to HTML for us - we can see exactly what the HTML looks like in the next test output::
self.assertIn('input type="radio"', form.as_p())
AssertionError: 'input type="radio"' not found in u'<p><label for="id_vote">Vote:</label> <select name="vote" id="id_vote">\n<option value="1">42</option>\n<option value="2">The Ultimate Answer</option>\n</select></p>'
-Django has defaulted to using a ``select/option`` input form. We can change
-this using a `widget`, in this case a ``RadioSelect``
+Django has defaulted to using a ``select/option`` input form. We can change this using a `widget`, in this case a ``RadioSelect``
.. sourcecode:: python
:filename: mysite/polls/forms.py
@@ -467,31 +429,22 @@ this using a `widget`, in this case a ``RadioSelect``
vote = forms.ChoiceField(widget=forms.RadioSelect())
def __init__(self, poll):
- super(self.__class__, self).__init__()
+ forms.Form.__init__(self)
self.fields['vote'].choices = [(c.id, c.choice) for c in poll.choice_set.all()]
-OK so far? Django forms have *fields*, some of which may have *choices*, and
-we can choose how the field will be displayed on page using a *widget*. Right.
+OK so far? Django forms have *fields*, some of which may have *choices*, and we can choose how the field will be displayed on page using a *widget*. Right.
-And that should get the tests passing! If you're curious to see what the form
-HTML actually looks like, why not temporarily put a ``print form.as_p()`` at
-the end of the test? Print statements in tests can be very useful for
-exploratory programming... You could try ``form.as_table()`` too if you like...
+And that should get the tests passing! If you're curious to see what the form HTML actually looks like, why not temporarily put a ``print form.as_p()`` at the end of the test? Print statements in tests can be very useful for exploratory programming... You could try ``form.as_table()`` too if you like...
Right, where where we? Let's do a quick check of the functional tests.
-(*incidentally, are you rather bored of watching the FT run through the
-admin test each time? I was, so I've built in a second argument to the FT
-runner that lets you filter by name of test - just pass in* ``polls`` *and
-it will only run FTs in files whose names contain the world* ``polls``.)::
+(*incidentally, are you rather bored of watching the FT run through the admin test each time? I was, so I've built in a second argument to the FT runner that lets you filter by name of test - just pass in* ``polls`` *and it will only run FTs in files whose names contain the world* ``polls``.)::
python functional_tests.py polls
[...]
- AssertionError: Lists differ: [] != ['Very awesome', 'Quite awesom...
+ AssertionError: 0 != 3
-Ah yes, we still haven't actually *used* the form yet! Let's go back to
-our ``TestSinglePollView``, and add some extra code (you can copy and
-paste some of it from the form test)
+Ah yes, we still haven't actually *used* the form yet! Let's go back to our ``TestSinglePollView``, and a new test that checks we use our form)
.. sourcecode:: python
:filename: mysite/polls/tests.py
@@ -499,42 +452,29 @@ paste some of it from the form test)
class TestSinglePollView(TestCase):
def test_page_shows_poll_title_and_no_votes_message(self):
- # set up two polls, to check the right one gets used
- poll1 = Poll(question='6 times 7', pub_date='2001-01-01')
+ [...]
+
+
+ def test_page_shows_choices_using_form(self):
+ # set up a poll with choices
+ poll1 = Poll(question='time', pub_date=timezone.now())
poll1.save()
- choice1 = Choice(poll=poll1, choice='42', votes=0)
+ choice1 = Choice(poll=poll1, choice="PM", votes=0)
choice1.save()
- choice2 = Choice(poll=poll1, choice='The Ultimate Answer', votes=0)
+ choice2 = Choice(poll=poll1, choice="Gardener's", votes=0)
choice2.save()
- poll2 = Poll(question='time', pub_date='2001-01-01')
- poll2.save()
- choice3 = Choice(poll=poll2, choice='PM', votes=0)
- choice3.save()
- choice4 = Choice(poll=poll2, choice="Gardener's", votes=0)
- choice4.save()
client = Client()
- response = client.get('/poll/%d/' % (poll2.id, ))
-
- # check we've used the right template
- self.assertTemplateUsed(response, 'poll.html')
-
- # check we've passed the right poll into the context
- self.assertEquals(response.context['poll'], poll2)
-
- # check the poll's question appears on the page
- self.assertIn(poll2.question, response.content)
-
- # check our 'no votes yet' message appears
- self.assertIn('No-one has voted on this poll yet', response.content)
+ response = client.get('/poll/%d/' % (poll1.id, ))
# check we've passed in a form of the right type
self.assertTrue(isinstance(response.context['form'], PollVoteForm))
# and check the check the form is being used in the template,
# by checking for the choice text
- self.assertIn(choice3.choice, response.content)
- self.assertIn(choice4.choice, response.content)
+ self.assertIn(choice1.choice, response.content)
+ self.assertIn(choice2.choice, response.content)
+
Now the unit tests give us::
@@ -569,7 +509,8 @@ So:
And::
self.assertIn(choice3.choice, response.content)
- AssertionError: 'PM' not found in '<html>\n <body>\n <h1>Poll Results</h1>\n \n <h2>time</h2>\n\n <p>No-one has voted on this poll yet</p>\n \n </body>\n</html>\n'
+ AssertionError: 'PM' not found in '<html>\n <body>\n <h1>Poll Results</h1>\n\n <h2>6 times 7</h2>\n <p>No-one has voted on this poll yet</p>\n </body>\n</html>\n\n'
+
So, in ``polls/templates/poll.html``:
@@ -596,15 +537,14 @@ And re-running the tests - oh, a surprise!::
self.assertIn(choice4.choice, response.content)
AssertionError: "Gardener's" not found in '<html>\n <body>\n <h1>Poll Results</h1>\n \n <h2>time</h2>\n\n <p>No-one has voted on this poll yet</p>\n\n <h3>Add your vote</h3>\n <p><label for="id_vote_0">Vote:</label> <ul>\n<li><label for="id_vote_0"><input type="radio" id="id_vote_0" value="3" name="vote" /> PM</label></li>\n<li><label for="id_vote_1"><input type="radio" id="id_vote_1" value="4" name="vote" /> Gardener&#39;s</label></li>\n</ul></p>\n\n \n </body>\n</html>\n'
-Django has converted an apostrophe (``'``) into an html-compliant ``&#39;`` for
-us. I suppose that's my come-uppance for trying to include British in-jokes in
-my tutorial. Let's implement a minor hack in our test:
+Django has converted an apostrophe (``'``) into an html-compliant ``&#39;`` for us. I suppose that's my come-uppance for trying to include British in-jokes in my tutorial. Let's implement a minor hack in our test:
.. sourcecode:: python
:filename: mysite/polls/tests.py
- self.assertIn(choice4.choice, response.content.replace('&#39;', "'"))
+ self.assertIn(choice1.choice, response.content.replace('&#39;', "'"))
+ self.assertIn(choice2.choice, response.content.replace('&#39;', "'"))
And now we have passination::
@@ -639,9 +579,7 @@ So let's ask the FTs again!::
----------------------------------------------------------------------
-Hm, not quite according to the original plan - our form has auto-generated an
-extra label which says "Vote:" above the radio buttons - well, since it doesn't
-do any harm, for now maybe it's easiest to just change the FT:
+Hm, not quite according to the original plan - our form has auto-generated an extra label which says "Vote:" above the radio buttons - well, since it doesn't do any harm, for now maybe it's easiest to just change the FT:
.. sourcecode:: python
:filename: mysite/fts/test_polls.py
@@ -668,12 +606,9 @@ The FT should now get a little further::
NoSuchElementException: Message: u'Unable to locate element: {"method":"css selector","selector":"input[type=\'submit\']"}'
-There's no submit button on our form! When Django generates a form, it only
-gives you the inputs for the fields you've defined, so no submit button (and no
-``<form>`` tag either for that matter).
+There's no submit button on our form! When Django generates a form, it only gives you the inputs for the fields you've defined, so no submit button (and no ``<form>`` tag either for that matter).
-Well, a button is easy enough to add, although it may not do much... In the
-template:
+Well, a button is easy enough to add, although it may not do much... In the template:
.. sourcecode:: html+django
:filename: mysite/polls/templates/home.html
@@ -707,6 +642,5 @@ And now... our tests get to the end!::
----------------------------------------------------------------------
-Tune in next week for when we finish our tests, handle POST requests, and do
-super-fun form validation too...
+Tune in next week for when we finish our tests, handle POST requests, and do super-fun form validation too...
Please sign in to comment.
Something went wrong with that request. Please try again.