Skip to content

Commit 83dfb08

Browse files
authored
Merge pull request #231 from Xronophobe/update--chapter-13
Update and review Chapter 13
2 parents af73f88 + 7a928fd commit 83dfb08

File tree

1 file changed

+57
-15
lines changed

1 file changed

+57
-15
lines changed

chapter_13_database_layer_validation.asciidoc

Lines changed: 57 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ selenium.common.exceptions.NoSuchElementException: Message: Unable to locate
5656
element: .invalid-feedback; For documentation [...]
5757
----
5858

59+
// CSANAD: I don't have the "For documentation" here:
60+
// Unable to locate element: .invalid-feedback
61+
// I only have the stack trace.
62+
5963
It's expecting to see an error message if the user tries to input an empty
6064
item.
6165

@@ -111,7 +115,7 @@ TIP: If you're new to Python, you may never have seen the `with` statement.
111115
It's used with what are called "context managers", which wrap a block of code,
112116
usually with some kind of setup, cleanup, or error-handling code.
113117
There's a good write-up in the
114-
http://docs.python.org/release/2.5/whatsnew/pep-343.html[Python 2.5 release notes].
118+
https://docs.python.org/release/2.5/whatsnew/pep-343.html[Python 2.5 release notes].
115119
((("with statements")))
116120
((("Python 3", "with statements")))
117121

@@ -131,6 +135,8 @@ except ValidationError:
131135

132136
But the `with` formulation is neater. Now, we can try running the test,
133137
and see its expected failure:
138+
// CSANAD: I would highlight we are running the unit test here
139+
// python src/manage.py test lists
134140

135141
----
136142
with self.assertRaises(ValidationError):
@@ -145,16 +151,18 @@ A Django Quirk: Model Save Doesn't Run Validation
145151
((("model-layer validation", "running full validation")))
146152
And now we discover one of Django's little quirks.
147153
_This test should already pass_.
148-
f you take a look at the
149-
http://bit.ly/SuxPJO[docs for the Django model fields],
150-
you'll see that `TextField` actually defaults to `blank=False`, which means
151-
that it 'should' disallow empty values.
154+
If you take a look at the
155+
https://docs.djangoproject.com/en/4.2/ref/models/fields/#blank[docs for the Django model fields],
156+
you'll see under _Field options_ that the default setting for `blank` is `False`. Which means,
157+
since TextField is a type of Field, it 'should' disallow empty values.
158+
// CSANAD: I rephrased this a little, because `blank` is False for all `Field`s.
152159

153160
((("data integrity errors")))
154161
So why is the test still failing?
155162
Well, for
156163
http://bit.ly/2v3SfRq[slightly counterintuitive historical reasons],
157164
Django models don't run full validation on save.
165+
// CSANAD: I'd argue against the use of shortened URLs.
158166
As we'll see later,
159167
any constraints that are actually implemented in the database
160168
will raise errors on save,
@@ -167,6 +175,9 @@ But Django knows that SQLite doesn't support this type of constraint,
167175
so if we try to run `makemigrations`, it will report there's nothing to do:
168176

169177

178+
// CSANAD: maybe I'm missing something here but I don't think there would have
179+
// been any changes detected here even with another DB. I don't remember
180+
// changing the model since the last migration.
170181
[subs="specialcharacters,macros"]
171182
----
172183
$ pass:quotes[*python src/manage.py makemigrations*]
@@ -177,7 +188,7 @@ No changes detected
177188
((("full_clean method")))Django
178189
does have a method to manually run full validation, however, called
179190
`full_clean` (more info in
180-
http://bit.ly/2u5SIxA[the docs]).
191+
https://docs.djangoproject.com/en/4.2/ref/models/instances/#django.db.models.Model.full_clean[the docs]).
181192
Let's hack it in to see it work:
182193

183194

@@ -187,11 +198,17 @@ Let's hack it in to see it work:
187198
[source,python]
188199
----
189200
with self.assertRaises(ValidationError):
190-
item.save()
191201
item.full_clean()
202+
item.save()
192203
----
193204
====
194205
//19
206+
// CSANAD: full_clean() should be executed before save() is called. It doesn't
207+
// make a difference here since the test DB is destroyed anyway, but
208+
// we are actually saving an invalid item and only then calling full_clean().
209+
// Which results in raising the correct error. But I think we should always
210+
// show the correct order even when it doesn't matter: validation first and
211+
// only then attempting save().
195212

196213
That gets the unit test to pass:
197214

@@ -211,7 +228,7 @@ Surfacing Model Validation Errors in the View
211228
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
212229

213230
((("model-layer validation", "surfacing errors in the view", id="MLVsurfac13")))
214-
Let"s try to enforce our model validation in the views layer
231+
Let's try to enforce our model validation in the views layer
215232
and bring it up through into our templates, so the user can see them.
216233
Here's how we can optionally display an error in our HTML--we check whether the template has
217234
been passed an error variable, and if so, we do this:
@@ -241,8 +258,15 @@ been passed an error variable, and if so, we do this:
241258

242259
((("Bootstrap", "documentation")))
243260
((("form control classes (Bootstrap)")))
244-
Take a look at the http://getbootstrap.com/css/#forms[Bootstrap docs] for more
261+
Take a look at the https://getbootstrap.com/docs/5.3/forms/validation/#server-side[Bootstrap docs] for more
245262
info on form controls.
263+
// CSANAD: these are the new docs for Bootstrap, but for some reason they begin
264+
// with saying "We recommend client-side validation" which is bad.
265+
// Client side validation is fine for faster UI aesthetics, sure. But they are
266+
// not emphasizing that it is only for faster feedback and for security,
267+
// whatever reaches the server should be validated on the server side before
268+
// accepting it as an input for literally anything... Maybe we should mention
269+
// this in a side note?
246270

247271
Passing this error to the template is the job of the view function. Let's take
248272
a look at the unit tests in the `NewListTest` class. I'm going to use two
@@ -406,7 +430,7 @@ self.assertContains(response, expected_error)
406430
====
407431

408432
...will show us the cause—Django has
409-
https://docs.djangoproject.com/en/1.11/ref/templates/builtins/#autoescape[HTML-escaped]
433+
https://docs.djangoproject.com/en/4.2/ref/templates/builtins/#autoescape[HTML-escaped]
410434
the apostrophe:
411435

412436
----
@@ -538,7 +562,9 @@ element: .invalid-feedback; [...]
538562
----
539563

540564

541-
Not quite, but they did get a little further. Checking 'line 31', we can
565+
Not quite, but they did get a little further. Checking the line in which the
566+
error occurred -- 'line 31' in my case -- , we can
567+
// CSANAD: I think emphasizing what to look for in the error log is helpful.
542568
see that we've got past the first part of the test, and are now onto the second
543569
check--that submitting a second empty item also shows an error.
544570

@@ -573,6 +599,19 @@ The current situation is that we have one view and URL for displaying a list,
573599
and one view and URL for processing additions to that list. We're going to
574600
combine them into one. So, in 'list.html', our form will have a different
575601
target:
602+
// CSANAD: Since we are working in a not completely working state, I would
603+
// already mention or move the NOTE here from the end of the subsection:
604+
// "Refactor: Transferring the new_item Functionality into view_list".
605+
// And then just re-reference it from where it is right now.
606+
//
607+
// I think adding a few words on how this is not just a simple refactor but it's
608+
// going to get us to our working state would help the reader recognise these
609+
// situations later, independently.
610+
// Something like pointing out how the FT is partially passing for:
611+
// - adding one empty list item results in an error message
612+
// - adding one valid list item is also passing
613+
// - the error occurs when we try adding an empty list item again
614+
// from which we conclude why we are refactoring the views first
576615

577616
[role="sourcecode"]
578617
.src/lists/templates/list.html (ch11l030)
@@ -614,7 +653,10 @@ NOTE: In this section we're performing a refactor at the application level.
614653
and things are back to working as before.
615654
Have another look at the diagram from the end of <<chapter_04_philosophy_and_refactoring>>
616655
if you need to get your bearings.
617-
656+
// CSANAD: my comment from above to say a few words on why we are refactoring
657+
// despite the failing FT again feels justified since we are even
658+
// mentioning the diagram too. It might feel confusing without telling the
659+
// reader earlier what we are doing here.
618660

619661

620662
==== Refactor: Transferring the new_item Functionality into view_list
@@ -952,7 +994,7 @@ a [keep-together]#parameter#:
952994
====
953995

954996
See the
955-
http://bit.ly/2uKaMzA[Django
997+
https://docs.djangoproject.com/en/4.2/topics/http/urls/#reverse-resolution-of-urls[Django
956998
docs on reverse URL resolution] for more info. We run the tests again, and check that they all pass:
957999

9581000
[subs="specialcharacters,macros"]
@@ -1031,7 +1073,7 @@ AttributeError: 'List' object has no attribute 'get_absolute_url'
10311073
The implementation is to use Django's `reverse` function, which
10321074
essentially does the reverse of what Django normally does with 'urls.py'
10331075
(see the
1034-
https://docs.djangoproject.com/en/1.11/topics/http/urls/#reverse-resolution-of-urls[docs]):
1076+
https://docs.djangoproject.com/en/4.2/topics/http/urls/#reverse-resolution-of-urls[docs]):
10351077

10361078

10371079
[role="sourcecode"]
@@ -1065,7 +1107,7 @@ def new_list(request):
10651107
====
10661108

10671109
There's more info in the
1068-
https://docs.djangoproject.com/en/1.11/topics/http/shortcuts/#redirect[Django
1110+
https://docs.djangoproject.com/en/4.2/topics/http/shortcuts/#redirect[Django
10691111
docs]. Quick check that the unit tests still pass:
10701112

10711113
[subs="specialcharacters,macros"]

0 commit comments

Comments
 (0)