Skip to content

Commit bc3b7a6

Browse files
committed
first pass all the way thru listings
1 parent 7b3a51c commit bc3b7a6

File tree

2 files changed

+94
-93
lines changed

2 files changed

+94
-93
lines changed

chapter_22_outside_in.asciidoc

Lines changed: 93 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ And we fix it, still at the presentation level, in 'urls.py':
310310
urlpatterns = [
311311
path("new", views.new_list, name="new_list"),
312312
path("<int:list_id>/", views.view_list, name="view_list"),
313-
path("users/<str:email>/", views.view_list, name="view_list"),
313+
path("users/<str:email>/", views.my_list, name="view_list"),
314314
]
315315
----
316316
====
@@ -323,29 +323,29 @@ That gives us a test failure, which informs us of what we should do as we
323323
move down to the next level:
324324

325325
----
326+
path("users/<str:email>/", views.my_lists, name="my_lists"),
327+
^^^^^^^^^^^^^^
326328
AttributeError: module 'lists.views' has no attribute 'my_lists'
327329
----
328330

329-
* TODO: new django does not error in quite this way
330-
331331

332332
We move in from the presentation layer to the views layer, and create a
333333
minimal placeholder:
334334

335335
[role="sourcecode"]
336-
.lists/views.py (ch19l005)
336+
.lists/views.py (ch22l005)
337337
====
338338
[source,python]
339339
----
340340
def my_lists(request, email):
341-
return render(request, 'my_lists.html')
341+
return render(request, "my_lists.html")
342342
----
343343
====
344344

345345
And a minimal template:
346346

347347
[role="sourcecode"]
348-
.lists/templates/my_lists.html
348+
.lists/templates/my_lists.html (ch22l006)
349349
====
350350
[source,html]
351351
----
@@ -368,51 +368,47 @@ element: Reticulate splines
368368
----
369369

370370

371-
Another Pass, Outside-In
372-
~~~~~~~~~~~~~~~~~~~~~~~~
373-
374-
((("Outside-In TDD", "FT-driven development", id="OITDDft22")))At
375-
each stage, we still let the FT drive what development we do.
371+
=== Another Pass, Outside-In
376372

377-
Starting again at the outside layer, in the template, we begin to
378-
write the template code we'd like to use to get the "My Lists" page to
379-
work the way we want it to. As we do so, we start to specify the API
380-
we want from the code at the layers below.
373+
((("Outside-In TDD", "FT-driven development", id="OITDDft22")))
374+
At each stage, we still let the FT drive what development we do.
381375

376+
Starting again at the outside layer, in the template,
377+
we begin to write the template code we'd like to use
378+
to get the "My Lists" page to work the way we want it to.
379+
As we do so, we start to specify the API we want
380+
from the code at the layers below.
382381

383-
A Quick Restructure of the Template Inheritance Hierarchy
384-
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
385382

383+
==== A Quick Restructure of the Template Inheritance Hierarchy
386384

387385

388-
((("templates", "inheritance hierarchy")))Currently
389-
there's no place in our base template for us to put any new
390-
content. Also, the "My Lists" page doesn't need the new item form, so
391-
we'll put that into a block too, making it optional:
386+
((("templates", "inheritance hierarchy")))
387+
Currently there's no place in our base template for us to put any new content.
388+
Also, the "My Lists" page doesn't need the new item form,
389+
so we'll put that into a block too, making it optional:
392390

393391
[role="sourcecode"]
394392
.lists/templates/base.html (ch19l007-1)
395393
====
396-
[source,html]
394+
[source,diff]
397395
----
398-
<div class="row">
399-
<div class="col-md-6 col-md-offset-3 jumbotron">
400-
<div class="text-center">
401-
<h1>{% block header_text %}{% endblock %}</h1>
402-
{% block list_form %}
403-
<form method="POST" action="{% block form_action %}{% endblock %}">
404-
{{ form.text }}
405-
{% csrf_token %}
406-
{% if form.errors %}
407-
<div class="form-group has-error">
408-
<div class="help-block">{{ form.text.errors }}</div>
409-
</div>
410-
{% endif %}
411-
</form>
412-
{% endblock %}
413-
</div>
414-
</div>
415-
</div>
396+
@@ -57,6 +57,7 @@
397+
<div class="col-lg-6 text-center">
398+
<h1 class="display-1 mb-4">{% block header_text %}{% endblock %}</h1>
399+
400+
+ {% block list_form %}
401+
<form
402+
method="POST"
403+
action="{% block form_action %}{% endblock %}"
404+
@@ -75,6 +76,8 @@
405+
{% endif %}
406+
{% csrf_token %}
407+
</form>
408+
+ {% endblock %}
409+
+
410+
</div>
411+
</div>
416412
----
417413
====
418414

@@ -601,32 +597,31 @@ OK
601597
----
602598

603599

604-
The Next "Requirement" from the Views Layer: New Lists Should Record Owner
605-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
600+
=== The Next "Requirement" from the Views Layer: New Lists Should Record Owner
606601

607-
((("Outside-In TDD", "views layer")))Before
608-
we move down to the model layer, there's another part of the code
609-
at the views layer that will need to use our model: we need some way for
610-
newly created lists to be assigned to an owner, if the current user is
611-
logged in to the site.
602+
((("Outside-In TDD", "views layer")))
603+
Before we move down to the model layer,
604+
there's another part of the code at the views layer that will need to use our model:
605+
we need some way for newly created lists to be assigned to an owner,
606+
if the current user is logged in to the site.
612607

613608
Here's a first crack at writing the test:
614609

615610

616611
[role="sourcecode"]
617-
.lists/tests/test_views.py (ch19l014)
612+
.lists/tests/test_views.py (ch22l014)
618613
====
619614
[source,python]
620615
----
621616
class NewListTest(TestCase):
622617
[...]
623618
624619
def test_list_owner_is_saved_if_user_is_authenticated(self):
625-
user = User.objects.create(email='[email protected]')
620+
user = User.objects.create(email="[email protected]")
626621
self.client.force_login(user) #<1>
627-
self.client.post('/lists/new', data={'text': 'new item'})
628-
list_ = List.objects.first()
629-
self.assertEqual(list_.owner, user)
622+
self.client.post("/lists/new", data={"text": "new item"})
623+
new_list = List.objects.get()
624+
self.assertEqual(new_list.owner, user)
630625
----
631626
====
632627

@@ -642,20 +637,20 @@ AttributeError: 'List' object has no attribute 'owner'
642637
To fix this, we can try writing code like this:
643638

644639
[role="sourcecode"]
645-
.lists/views.py (ch19l015)
640+
.lists/views.py (ch22l015)
646641
====
647642
[source,python]
648643
----
649644
def new_list(request):
650645
form = ItemForm(data=request.POST)
651646
if form.is_valid():
652-
list_ = List()
653-
list_.owner = request.user
654-
list_.save()
655-
form.save(for_list=list_)
656-
return redirect(list_)
647+
nulist = List.objects.create()
648+
nulist.owner = request.user
649+
nulist.save()
650+
form.save(for_list=nulist)
651+
return redirect(nulist)
657652
else:
658-
return render(request, 'home.html', {"form": form})
653+
return render(request, "home.html", {"form": form})
659654
----
660655
====
661656
//015
@@ -671,30 +666,33 @@ AttributeError: 'List' object has no attribute 'owner'
671666
----
672667

673668

674-
A Decision Point: Whether to Proceed to the Next Layer with a Failing Test
675-
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
669+
==== A Decision Point: Whether to Proceed to the Next Layer with a Failing Test
676670

671+
* TODO: rewrite this section if we do decide to drop the next chapter.
677672

678-
((("Outside-In TDD", "model layer", id="OITDDmodel21")))In
679-
order to get this test passing, as it's written now, we have to move
680-
down to the model layer. However, it means doing more work with a failing
681-
test, which is not ideal.
682673

683-
((("mocks", "isolating tests using")))The
684-
alternative is to rewrite the test to make it more 'isolated' from the
685-
level below, using mocks.
674+
((("Outside-In TDD", "model layer", id="OITDDmodel21")))
675+
In order to get this test passing, as it's written now,
676+
we have to move down to the model layer.
677+
However, it means doing more work with a failing test, which is not ideal.
686678

687-
On the one hand, it's a lot more effort to use mocks, and it can lead to
688-
tests that are harder to read. On the other hand, imagine if our app was more
689-
complex, and there were several more layers between the outside and the inside.
690-
Imagine leaving three or four or five layers of tests, all failing while we
691-
wait to get to the bottom layer to implement our critical feature. While tests
692-
are failing, we're not sure that layer really works, on its own terms, or not.
679+
((("mocks", "isolating tests using")))
680+
The alternative is to rewrite the test
681+
to make it more _isolated_ from the level below, using mocks.
682+
683+
On the one hand, it's a lot more effort to use mocks,
684+
and it can lead to tests that are harder to read.
685+
On the other hand, imagine if our app was more complex,
686+
and there were several more layers between the outside and the inside.
687+
Imagine leaving three or four or five layers of tests, all failing
688+
while we wait to get to the bottom layer to implement our critical feature.
689+
While tests are failing, we're not sure that layer really works, on its own terms, or not.
693690
We have to wait until we get to the bottom layer.
694691

695-
This is a decision point you're likely to run into in your own projects. Let's
696-
investigate both approaches. We'll start by taking the shortcut, and leaving
697-
the test failing. In the next chapter, we'll come back to this exact point,
692+
This is a decision point you're likely to run into in your own projects.
693+
Let's investigate both approaches.
694+
We'll start by taking the shortcut, and leaving the test failing.
695+
In the next chapter, we'll come back to this exact point,
698696
and investigate how things would have gone if we'd used more isolation.
699697

700698
Let's do a commit, and then 'tag' the commit as a way of remembering our
@@ -707,19 +705,19 @@ $ *git tag revisit_this_point_with_isolated_tests*
707705
----
708706

709707

710-
Moving Down to the Model Layer
711-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
708+
=== Moving Down to the Model Layer
712709

713710
Our outside-in design has driven out two requirements for the model layer:
714-
we want to be able to assign an owner to a list using the attribute
715-
`.owner`, and we want to be able to access the list's owner with
716-
the API `owner.list_set.all`.
711+
we want to be able to assign an owner to a list using the attribute `.owner`,
712+
and we want to be able to access the list's owner with the API `owner.list_set.all()`.
713+
714+
// TODO: let's make this owner.lists.all() ?
717715

718716
Let's write a test for that:
719717

720718

721719
[role="sourcecode"]
722-
.lists/tests/test_models.py (ch19l018)
720+
.lists/tests/test_models.py (ch22l018)
723721
====
724722
[source,python]
725723
----
@@ -765,7 +763,7 @@ that too:
765763

766764

767765
[role="sourcecode"]
768-
.lists/tests/test_models.py (ch19l020)
766+
.lists/tests/test_models.py (ch22l020)
769767
====
770768
[source,python]
771769
----
@@ -777,18 +775,20 @@ that too:
777775
The correct implementation is this:
778776

779777
[role="sourcecode"]
780-
.lists/models.py
778+
.lists/models.py (ch22l021)
781779
====
782780
[source,python]
783781
----
784782
from django.conf import settings
785783
[...]
786784
787785
class List(models.Model):
788-
owner = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True)
786+
owner = models.ForeignKey(
787+
settings.AUTH_USER_MODEL, blank=True, null=True, on_delete=models.CASCADE
788+
)
789789
790790
def get_absolute_url(self):
791-
return reverse('view_list', args=[self.id])
791+
return reverse("view_list", args=[self.id])
792792
----
793793
====
794794
//21
@@ -840,16 +840,17 @@ Django represents users using a class called `AnonymousUser`, whose
840840

841841

842842
[role="sourcecode"]
843-
.lists/views.py (ch19l023)
843+
.lists/views.py (ch22l023)
844844
====
845845
[source,python]
846846
----
847847
if form.is_valid():
848-
list_ = List()
848+
nulist = List.objects.create()
849849
if request.user.is_authenticated:
850-
list_.owner = request.user
851-
list_.save()
852-
form.save(for_list=list_)
850+
nulist.owner = request.user
851+
nulist.save()
852+
form.save(for_list=nulist)
853+
return redirect(nulist)
853854
[...]
854855
----
855856
====

0 commit comments

Comments
 (0)