Skip to content

Commit 4909f81

Browse files
committed
decent stab at post and db. no idea why test is failing
1 parent 2a73907 commit 4909f81

File tree

4 files changed

+147
-81
lines changed

4 files changed

+147
-81
lines changed

chapter_post_and_database.asciidoc

Lines changed: 123 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ Let's start by seeing how the template syntax lets us include a Python object in
332332
The notation is `{{ ... }}`, which displays the object as a string:
333333

334334
[role="sourcecode small-code"]
335-
.lists/templates/home.html (ch05l008)
335+
.lists/templates/home.html (ch05l008)
336336
====
337337
[source,html]
338338
----
@@ -1330,16 +1330,70 @@ OK
13301330

13311331
We're at green, time for a little refactor!
13321332

1333+
Looking at views.py, there's a small change
13331334

1334-
TODO little tidy up, do Item.objects.create()? Maybe get rid of new_item_text too?
1335+
[role="sourcecode currentcontents"]
1336+
.list/views.py
1337+
====
1338+
[source,python]
1339+
----
1340+
def home_page(request):
1341+
if request.method == "POST":
1342+
item = Item() # <1>
1343+
item.text = request.POST["item_text"] # <1>
1344+
item.save() # <1>
1345+
return redirect("/")
1346+
1347+
return render(
1348+
request,
1349+
"home.html",
1350+
{"new_item_text": request.POST.get("item_text", "")}, # <2>
1351+
)
1352+
----
1353+
1354+
<1> There's a quicker way to do these 3 lines with `.objects.create()`
1355+
<2> This line doesn't seem quite right now, in fact it won't work at all.
1356+
Let's make a note on our scratchpad to sort out passing list items to the template.
1357+
It's actually closely related to "Display multiple items",
1358+
so we'll put it next to that one:
1359+
1360+
[role="scratchpad"]
1361+
*****
1362+
* '[strikethrough line-through]#Don't save blank items for every request#'
1363+
* 'Code smell: POST test is too long?'
1364+
* 'Pass existing list items to the template somehow'
1365+
* 'Display multiple items in the table'
1366+
* 'Support more than one list!'
1367+
*****
1368+
1369+
And here's the refactored version of _views.py_ using the `.objects.create()`
1370+
helper method that Django provides, for one-line creation of objects:
1371+
1372+
[role="sourcecode"]
1373+
.lists/views.py (ch05l031)
1374+
====
1375+
[source,python]
1376+
----
1377+
def home_page(request):
1378+
if request.method == "POST":
1379+
Item.objects.create(text=request.POST["item_text"])
1380+
return redirect("/")
1381+
1382+
return render(
1383+
request,
1384+
"home.html",
1385+
{"new_item_text": request.POST.get("item_text", "")},
1386+
)
1387+
1388+
----
1389+
====
13351390
13361391
13371392
=== Better Unit Testing Practice: Each Test Should Test One Thing
13381393
13391394
((("unit tests", "testing only one thing")))
13401395
((("testing best practices")))
1341-
Our view now does a redirect after a POST, which is good practice,
1342-
and we've shortened the unit test somewhat, but we can still do better.
1396+
Let's address the "POST test is too long" code smell.
13431397
13441398
Good unit testing practice says that each test should only test one thing. The
13451399
reason is that it makes it easier to track down bugs. Having multiple
@@ -1352,7 +1406,7 @@ You may not always write perfect unit tests with single assertions on your
13521406
first go, but now feels like a good time to separate out our concerns:
13531407
13541408
[role="sourcecode"]
1355-
.lists/tests.py
1409+
.lists/tests.py (ch05l032)
13561410
====
13571411
[source,python]
13581412
----
@@ -1364,8 +1418,7 @@ first go, but now feels like a good time to separate out our concerns:
13641418
13651419
def test_redirects_after_POST(self):
13661420
response = self.client.post("/", data={"item_text": "A new list item"})
1367-
self.assertEqual(response.status_code, 302)
1368-
self.assertEqual(response["location"], "/")
1421+
self.assertRedirects(response, "/")
13691422
----
13701423
====
13711424
@@ -1381,69 +1434,76 @@ OK
13811434
----
13821435
13831436
1384-
Rendering Items in the Template
1385-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1386-
1387-
1437+
=== Rendering Items in the Template
13881438
1389-
((("database testing", "rendering items in the template", id="DTrender05")))Much
1390-
better! Back to our to-do list:
1439+
((("database testing", "rendering items in the template", id="DTrender05")))
1440+
Much better! Back to our to-do list:
13911441
13921442
[role="scratchpad"]
13931443
*****
13941444
* '[strikethrough line-through]#Don't save blank items for every request#'
13951445
* '[strikethrough line-through]#Code smell: POST test is too long?#'
1446+
* 'Pass existing list items to the template somehow'
13961447
* 'Display multiple items in the table'
13971448
* 'Support more than one list!'
13981449
*****
13991450
14001451
Crossing things off the list is almost as satisfying as seeing tests pass!
14011452
1402-
((("list items")))The
1403-
third item is the last of the "easy" ones. Let's have a new unit test
1404-
that checks that the template can also display multiple list items:
1453+
The third and fourth items are the last of the "easy" ones.
1454+
Our view now does the right thing for POST requests,
1455+
it saves new list items to the database.
1456+
Now we want GET requests to load all currently existing list items,
1457+
and pass them to the template for rendering.
1458+
Let's have a new unit test for that:
14051459
14061460
[role="sourcecode"]
1407-
.lists/tests.py
1461+
.lists/tests.py (ch05l033)
14081462
====
14091463
[source,python]
14101464
----
14111465
class HomePageTest(TestCase):
1412-
[...]
1466+
def test_uses_home_template(self):
1467+
[...]
14131468
14141469
def test_displays_all_list_items(self):
14151470
Item.objects.create(text="itemey 1")
14161471
Item.objects.create(text="itemey 2")
1417-
14181472
response = self.client.get("/")
1473+
self.assertContains(response, "itemey 1")
1474+
self.assertContains(response, "itemey 2")
14191475
1420-
self.assertIn("itemey 1", response.content.decode())
1421-
self.assertIn("itemey 2", response.content.decode())
1476+
def test_can_save_a_POST_request(self):
1477+
[...]
14221478
----
14231479
====
14241480
14251481
1482+
////
1483+
TODO: find a new home for this:
14261484
NOTE: Are you wondering about the line spacing in the test? I'm grouping
14271485
together two lines at the beginning which set up the test, one line in
14281486
the middle which actually calls the code under test, and the
14291487
assertions at the end. This isn't obligatory, but it does help see the
14301488
structure of the test. Arrange-Act-Assert is the typical structure
14311489
for a unit test.
1490+
////
14321491
14331492
14341493
That fails as expected:
14351494
14361495
----
1437-
AssertionError: 'itemey 1' not found in '<html>\n <head>\n [...]
1496+
AssertionError: False is not true : Couldn't find 'itemey 1' in response
14381497
----
14391498
1440-
((("templates", "tags", "{% for ... endfor %}")))((("{% for ... endfor %}")))The
1441-
Django template syntax has a tag for iterating through lists,
1442-
`{% for .. in .. %}`; we can use it like this:
1499+
((("templates", "tags", "{% for ... endfor %}")))
1500+
((("{% for ... endfor %}")))
1501+
The Django template syntax has a tag for iterating through lists,
1502+
`{% for .. in .. %}`; we can use it like this:
14431503
14441504
14451505
[role="sourcecode"]
1446-
.lists/templates/home.html
1506+
.lists/templates/home.html (ch05l034)
14471507
====
14481508
[source,html]
14491509
----
@@ -1460,13 +1520,13 @@ will render with multiple `<tr>` rows, one for each item in the variable
14601520
`items`. Pretty neat! I'll introduce a few more bits of Django template
14611521
magic as we go, but at some point you'll want to go and read up on the rest of
14621522
them in the
1463-
https://docs.djangoproject.com/en/1.11/topics/templates/[Django docs].
1523+
https://docs.djangoproject.com/en/4.2/topics/templates/[Django docs].
14641524
14651525
Just changing the template doesn't get our tests to green; we need to actually
14661526
pass the items to it from our home page view:
14671527
14681528
[role="sourcecode"]
1469-
.lists/views.py
1529+
.lists/views.py (ch05l035)
14701530
====
14711531
[source,python]
14721532
----
@@ -1490,11 +1550,12 @@ $ pass:quotes[*python functional_tests.py*]
14901550
AssertionError: 'To-Do' not found in 'OperationalError at /'
14911551
----
14921552
1493-
((("", startref="DTrender05")))((("debugging", "manual visits")))Oops, apparently not. Let's use another functional test debugging technique,
1494-
and it's one of the most straightforward: manually visiting the site! Open
1495-
up pass:[<em>http://localhost:8000</em>] in your web browser, and you'll see a Django debug
1496-
page saying "no such table: lists_item", as in <<operationalerror>>.
1497-
1553+
((("", startref="DTrender05")))
1554+
((("debugging", "manual visits")))
1555+
Oops, apparently not. Let's use another functional test debugging technique,
1556+
and it's one of the most straightforward: manually visiting the site!
1557+
Open up pass:[<em>http://localhost:8000</em>] in your web browser,
1558+
and you'll see a Django debug page saying "no such table: lists_item", as in <<operationalerror>>.
14981559
14991560
15001561
[[operationalerror]]
@@ -1504,22 +1565,20 @@ image::images/twp2_0502.png["OperationalError at / no such table: lists_item"]
15041565
15051566
15061567
[role="pagebreak-before less_space"]
1507-
Creating Our Production Database with migrate
1508-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1568+
=== Creating Our Production Database with migrate
15091569
1570+
((("database testing", "production database creation", id="DTproduction05")))
1571+
((("database migrations")))
1572+
Another helpful error message from Django,
1573+
which is basically complaining that we haven't set up the database properly.
1574+
How come everything worked fine in the unit tests, I hear you ask?
1575+
Because Django creates a special 'test database' for unit tests;
1576+
it's one of the magical things that Django's `TestCase` does.
15101577
1511-
1512-
((("database testing", "production database creation", id="DTproduction05")))((("database migrations")))Another
1513-
helpful error message from Django, which is basically complaining that
1514-
we haven't set up the database properly. How come everything worked fine
1515-
in the unit tests, I hear you ask? Because Django creates a special 'test
1516-
database' for unit tests; it's one of the magical things that Django's
1517-
`TestCase` does.
1518-
1519-
To set up our "real" database, we need to create it. SQLite databases
1520-
are just a file on disk, and you'll see in 'settings.py' that Django,
1521-
by default, will just put it in a file called 'db.sqlite3' in the base
1522-
project directory:
1578+
To set up our "real" database, we need to explicitly create it.
1579+
SQLite databases are just a file on disk,
1580+
and you'll see in 'settings.py' that Django, by default, will just put it in a file
1581+
called 'db.sqlite3' in the base project directory:
15231582
15241583
[role="sourcecode currentcontents"]
15251584
.superlists/settings.py
@@ -1528,7 +1587,7 @@ project directory:
15281587
----
15291588
[...]
15301589
# Database
1531-
# https://docs.djangoproject.com/en/4.1/ref/settings/#databases
1590+
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
15321591
15331592
DATABASES = {
15341593
"default": {
@@ -1539,10 +1598,10 @@ DATABASES = {
15391598
----
15401599
====
15411600
1542-
We've told Django everything it needs to create the database, first via
1543-
'models.py' and then when we created the migrations file. To actually apply
1544-
it to creating a real database, we use another Django Swiss Army knife
1545-
'manage.py' command, `migrate`:
1601+
We've told Django everything it needs to create the database,
1602+
first via 'models.py' and then when we created the migrations file.
1603+
To actually apply it to creating a real database,
1604+
we use another Django Swiss Army knife 'manage.py' command, `migrate`:
15461605
15471606
15481607
[subs="specialcharacters,macros"]
@@ -1584,18 +1643,12 @@ AssertionError: '2: Use peacock feathers to make a fly' not found in ['1: Buy
15841643
peacock feathers', '1: Use peacock feathers to make a fly']
15851644
----
15861645
1587-
// -- usually fails instead with:
1588-
1589-
// selenium.common.exceptions.InvalidSelectorException: Message: Given css'
1590-
// selector expression "tr" is invalid: TypeError: can\'t access dead object'
1591-
1592-
15931646
15941647
So close! We just need to get our list numbering right. Another awesome
15951648
Django template tag, `forloop.counter`, will help here:
15961649
15971650
[role="sourcecode"]
1598-
.lists/templates/home.html
1651+
.lists/templates/home.html (ch05l036)
15991652
====
16001653
[source,html]
16011654
----
@@ -1608,11 +1661,18 @@ Django template tag, `forloop.counter`, will help here:
16081661
16091662
If you try it again, you should now see the FT get to the end:
16101663
1664+
[subs="specialcharacters,macros"]
16111665
----
1612-
self.fail("Finish the test!")
1613-
AssertionError: Finish the test!
1666+
$ pass:quotes[*python functional_tests.py*]
1667+
.
1668+
---------------------------------------------------------------------
1669+
Ran 1 test in 5.036s
1670+
1671+
OK
16141672
----
16151673
1674+
Hooray!
1675+
16161676
But, as it's running, you may notice something is amiss, like in
16171677
<<items_left_over_from_previous_run>>.
16181678
@@ -1650,7 +1710,8 @@ And then (after restarting your server!) reassure yourself that the FT still
16501710
passes.
16511711
16521712
Apart from that little bug in our functional testing, we've got some code
1653-
that's more or less working. Let's do a commit.((("", startref="DTproduction05")))
1713+
that's more or less working. Let's do a commit.
1714+
((("", startref="DTproduction05")))
16541715
16551716
16561717
Start by doing a *`git status`* and a *`git diff`*, and you should see changes

tests/sourcetree.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ def check_listing_matches_commit(listing, commit, future_contents):
240240
if new_line.strip() not in stripped_listing_lines:
241241
# print('stripped_listing_lines', stripped_listing_lines)
242242
raise ApplyCommitException(
243-
f"could not find commit new line {new_line!r} in listing:\n{listing.contents}"
243+
f"could not find commit new line {new_line!r} in listing {listing.commit_ref}:\n{listing.contents}"
244244
)
245245

246246
check_chunks_against_future_contents(listing.contents, future_contents)

0 commit comments

Comments
 (0)