Skip to content

Commit 3914a0b

Browse files
authored
Merge branch 'main' into 20240411_chapter_11_jg_review
2 parents 85f3439 + aa6987b commit 3914a0b

13 files changed

+317
-83
lines changed

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,6 @@ jobs:
3333
steps:
3434
- uses: actions/checkout@v4
3535
- id: foo
36-
uses: hjwp/github-actions@v5
36+
uses: hjwp/github-actions@v6
3737
with:
3838
test_chapter: ${{ matrix.test_chapter }}

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ build: $(HTML_PAGES) $(TMPDIR)
4747

4848

4949
.venv/bin:
50-
which uv && uv venv .venv || python -m venv .venv
50+
which uv && uv venv .venv || python3 -m venv .venv
5151
which uv && uv pip install -e . || .venv/bin/pip install -e .
5252

5353
.PHONY: install

acknowledgments.asciidoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ James Evans,
110110
Patrick Cantwell,
111111
Devin Schumacher,
112112
Nick Nielsen,
113+
Teemu Viikeri,
114+
Andrew Zipperer,
113115
and to anyone I've missed off this list,
114116
my sincere apologies, and thank you thank you once again.
115117

chapter_04_philosophy_and_refactoring.asciidoc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -716,7 +716,7 @@ NOTE: This is another distinction between FTs and unit tests;
716716
we use them as the primary tool for testing our UI,
717717
and the HTML that implements it.
718718

719-
So, wanted an `<h1>`:
719+
So, we wanted an `<h1>`:
720720

721721
[role="sourcecode"]
722722
.lists/templates/home.html (ch04l008)
@@ -916,7 +916,7 @@ but it should be enough to reassure ourselves that things are working.
916916
To get that functional test to green,
917917
we then enter into the lower-level unit tests cycle,
918918
where we put together all the moving parts required,
919-
add tests for all the edge cases.
919+
and add tests for all the edge cases.
920920
Any time we get to green & refactored at the unit tests level,
921921
we can pop back up to the FT level to guide us towards the
922922
next thing we need to work.

chapter_05_post_and_database.asciidoc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ def home_page(request):
355355
OK that gets our unit tests passing, but it's not really what we want.footnote:[
356356
But we _did_ learn about `request.method` and `request.POST` right?
357357
I know it might seem that I'm overdoing it,
358-
but doing things in tiny little really does have a lot of advantages,
358+
but doing things in tiny little steps really does have a lot of advantages,
359359
and one of them is that you can really think about (or in this case, learn)
360360
one thing at a time.]
361361

@@ -633,7 +633,7 @@ on applying DRY too quickly.
633633

634634

635635
Now we get to the `self.fail('Finish the test!')`.
636-
If get rid of that and finish writing our FT,
636+
If we get rid of that and finish writing our FT,
637637
to add the check for adding a second item to the table
638638
(copy and paste is our friend),
639639
we begin to see that our first cut solution really isn't going to, um, cut it:
@@ -964,7 +964,7 @@ django.db.utils.OperationalError: no such table: lists_item
964964
----
965965

966966
In Django, the ORM's job is to model and read and write from database tables,
967-
but there's a second system that's in charge,of actually _creating_
967+
but there's a second system that's in charge of actually _creating_
968968
the tables in the database called "migrations".
969969
Its job is to let you to add, remove, and modify tables and columns,
970970
based on changes you make to your _models.py_ files.

chapter_06_explicit_waits_1.asciidoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,7 @@ to separate the timing and re-raising logic from the test assertions.
445445
But we'll wait until we need it in multiple places.
446446

447447
NOTE: If you've used Selenium before, you may know that it has a few
448-
https://www.selenium.dev/documentation/webdriver/waits/#explicit-wait[helper functions to do waits].
448+
https://www.selenium.dev/documentation/webdriver/waits/#explicit-waits[helper functions to do waits].
449449
I'm not a big fan of them, though not for any objective reason really.
450450
Over the course of the book we'll build a couple of wait helper tools
451451
which I think will make for nice, readable code,

chapter_09_docker.asciidoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1479,4 +1479,8 @@ Some typical pain points--networking, ports, static files, and the database::
14791479
and some configuration issues like static files.
14801480
14811481
1482+
// TODO: add debugging tips docker ps, docker inspect, docker logs.
1483+
// also brief description of network debugging: try curl outside, try curl inside, restart pc / docker /colima
1484+
// also maybe the lsof command to see who's using what port
1485+
14821486
*******************************************************************************

chapter_10_production_readiness.asciidoc

Lines changed: 97 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,25 @@ so that people can access it using a regular URL.
4141

4242
Perhaps more importantly, we shouldn't use the Django dev server for production;
4343
it's not designed for real-life workloads.
44-
Instead, we'll use the popular Gunicorn Python/WSGI server.
44+
Instead, we'll use the popular Gunicorn Python WSGI HTTP server.
45+
46+
NOTE: Django's `runserver` is built and optimised for local development and debugging.
47+
It's designed to handle one user at a time,
48+
it handles automatic reloading upon saving of the source code,
49+
but it isn't optimised for performance,
50+
nor has it been hardened against security vulnerabilities.
4551

4652
((("DEBUG settings")))
4753
In addition, several options in _settings.py_ are currently unacceptable.
48-
`DEBUG=True`, is strongly recommended against for production,
54+
`DEBUG=True`, is strongly discouraged for production,
4955
we'll want to set a unique `SECRET_KEY`,
5056
and, as we'll see, other things will come up.
5157

58+
NOTE: DEBUG=True is considered a security risk,
59+
because the django debug page will display sensitive information like
60+
the values of variables, and most of the settings in settings.py.
61+
62+
5263
Let's go through and see if we can fix things one by one.
5364

5465
// SEBASTIAN: How about linking to Django's documentation
@@ -68,6 +79,47 @@ which I guess is what you'd want next if you already had a pony...
6879
We'll need to first install Gunicorn into our container,
6980
and then use it instead of `runserver`:
7081

82+
//001
83+
84+
[subs="specialcharacters,quotes"]
85+
----
86+
$ *pip install gunicorn*
87+
Collecting gunicorn
88+
[...]
89+
Successfully installed gunicorn-21.2.0
90+
----
91+
92+
[[what-is-wsgi]]
93+
.What is WSGI?
94+
*******************************************************************************
95+
Gunicorn is a WSGI (usually pronounced as whizgee) HTTP server.
96+
Let's go through what this means:
97+
98+
* An HTTP server handles incoming HTTP requests and sends back responses to the
99+
clients.
100+
101+
* WSGI, the Web Server Gateway Interface, is a set of rules defining how an HTTP
102+
server (which can be Gunicorn itself, or Nginx, Apache, etc) and a Python
103+
program should communicate.
104+
105+
So, Gunicorn is an HTTP server that knows and uses the WSGI to "talk" to Python programs.
106+
And these Python programs are likewise expected to work in a specific way.
107+
You can check out a simple example https://gunicorn.org/[on Gunicorn's website] under "Installation".
108+
109+
https://peps.python.org/pep-3333[If you are interested, you can read the entire
110+
specification here].
111+
112+
// CSANAD: I felt like some explanation regarding what Gunicorn and WSGI are,
113+
// was missing.
114+
115+
*******************************************************************************
116+
117+
118+
Gunicorn will need to know a path to a "WSGI server" (see <<what-is-wsgi>>),
119+
which is usually a function called `application`.
120+
Django provides one in 'superlists/wsgi.py'.
121+
Let's change the command our image runs:
122+
71123
[role="sourcecode"]
72124
.Dockerfile (ch10l001)
73125
====
@@ -83,6 +135,9 @@ WORKDIR /src
83135
CMD gunicorn --bind :8888 superlists.wsgi:application <2>
84136
----
85137
====
138+
// CSANAD: shouldn't we try it out before adding this to the Dockerfile?
139+
// `cd src` and then
140+
// `gunicorn --bind :8888 superlists.wsgi:application`
86141

87142
<1> Installation is a standard pip install.
88143

@@ -172,6 +227,11 @@ MIDDLEWARE = [
172227
173228
----
174229
====
230+
// CSANAD: I would add a few thoughts on the significance of the order of
231+
// middlewares.
232+
233+
And if you take another manual look at your site after rebuilding the
234+
container, things should look much healthier.
175235

176236
And then we need to add it to our pip installs in the Dockerfile:
177237

@@ -290,7 +350,7 @@ Django, Gunicorn and Whitenoise:
290350
[subs="specialcharacters,quotes"]
291351
----
292352
$ *pip freeze | grep -i django*
293-
Django==4.2.7
353+
Django==4.2.11
294354
295355
$ *pip freeze | grep -i django >> requirements.txt*
296356
$ *pip freeze | grep -i gunicorn >> requirements.txt*
@@ -351,7 +411,7 @@ TIP: Itamar Turner-Traurig has a great guide to
351411

352412
Now let's see how we use that requirements file in our Dockerfile:
353413

354-
.Dockerfile (ch09l005)
414+
.Dockerfile (ch10l005)
355415
====
356416
[source,dockerfile]
357417
----
@@ -371,9 +431,9 @@ CMD python manage.py runserver
371431
----
372432
====
373433

374-
<3> We COPY our requirements file in, just like the src folder.
434+
<1> We COPY our requirements file in, just like the src folder.
375435

376-
<4> Now instead of just installing Django, we install all our dependencies
436+
<2> Now instead of just installing Django, we install all our dependencies
377437
by pointing pip at the _requirements.txt_ using the `-r` flag.
378438
Notice the `-r`.
379439

@@ -437,7 +497,8 @@ Environment variables also have the advantage of working for non-Django stuff to
437497
==== Setting DEBUG=True and SECRET_KEY
438498

439499
//RITA: What do you mean by "this"? Please add a word or two for context.
440-
There's lots of ways you might do this.
500+
There are lots of ways you might do this.
501+
441502
Here's what I propose; it may seem a little fiddly,
442503
but I'll provide a little justification for each choice.
443504
Let them be an inspiration (but not a template) for your own choices!
@@ -459,6 +520,10 @@ else:
459520
SECRET_KEY = "insecure-key-for-dev"
460521
----
461522
====
523+
// CSANAD: I think variable names like "something_false" are confusing, since
524+
// we need to set something to true so that they mean false.
525+
// How about `DJANGO_ENV_PRODUCTION` or something similar?
526+
// TODO yes i like this
462527

463528
<1> We say we'll use an environment variable called `DJANGO_DEBUG_FALSE`
464529
to switch debug mode off, and in effect require production settings
@@ -479,6 +544,14 @@ against accidentally forgetting to set one.
479544
TIP: Better to fail hard than allow a typo in an environment variable name to
480545
leave you running with insecure settings.
481546

547+
// CSANAD: I think it would worth pointing out the development environment
548+
// does not use Docker, launching the dev server should be done from
549+
// the reader's host system. I think this isn't immediately obvious, e.g. I
550+
// thought all along that from now on we would only run the server from Docker.
551+
// If we end up making a TIP or similar about it, I think we should also mention
552+
// in a development environment relying on containerization, programmers usually
553+
// mount the whole /src minimizing the time-consuming rebuilding of their images.
554+
482555
==== Setting environment variables inside the Dockerfile
483556

484557
Now let's set that environment variable in our Dockerfile using the `ENV` directive:
@@ -555,6 +628,9 @@ It's not quite working yet! Let's take a look manually: <<django-400-error>>.
555628
.An ugly 400 error
556629
image::images/twp2_1002.png["An unfriendly page showing 400 Bad Request"]
557630

631+
// DAVID: noticed in passing that the screenshots in this chapter are from the hosted version
632+
// which isn't covered until next chapter.
633+
558634
We've set our two environment variables but doing so seems to have broken things.
559635
But once again, by running our FTs frequently,
560636
we're able to identify the problem early,
@@ -627,6 +703,10 @@ $ *docker build -t superlists . && docker run \
627703
An FT run (or just looking at the site) reveals that we've had a regression
628704
in our static files:
629705

706+
// CSANAD: worth mentioning there was another clue. in the logs, once Gunicorn
707+
// starts running you can see:
708+
// `UserWarning: No directory at: /src/static/`
709+
630710
[role="small-code"]
631711
[subs="specialcharacters,macros"]
632712
----
@@ -658,6 +738,7 @@ ENV DJANGO_DEBUG_FALSE=1
658738
CMD gunicorn --bind :8888 superlists.wsgi:application
659739
----
660740
====
741+
// CSANAD: I think it would be important to use a non-privileged user in Docker
661742

662743
// DAVID: It would be nice to explain the difference between RUN and CMD.
663744

@@ -698,6 +779,12 @@ $ *git status*
698779
$ *git commit -am "Add collectstatic to dockerfile, and new location to gitignore"*
699780
----
700781

782+
// CSANAD: now would be a good time to check our changes and notice git marked
783+
// src/static as unstaged, so we should update .gitignore accordingly:
784+
// from `static` to `src/static`.
785+
// Or we could add this step to Chapter 09 at "Move all our code into a src
786+
// folder"
787+
701788

702789
=== Switching to a nonroot user
703790

@@ -715,7 +802,6 @@ USER nonroot
715802
----
716803
717804
718-
719805
=== Configuring logging
720806
721807
One last thing we'll want to do is make sure that we can get logs out of our server.
@@ -777,7 +863,7 @@ but it's more of shock to see that they are no longer appearing in the terminal
777863
If you're like me you might find yourself wondering if we really _did_ see them earlier
778864
and starting to doubt your own sanity.
779865
But the explanation is that Django's
780-
https://docs.djangoproject.com/en/5.0/ref/logging/#default-logging-configuration[default logging configuration]
866+
https://docs.djangoproject.com/en/4.2/ref/logging/#default-logging-configuration[default logging configuration]
781867
changes when DEBUG is turned off:
782868
783869
This means we need to interact with the standard library's `logging` module,
@@ -790,7 +876,7 @@ but sometimes you just wanna say "just print stuff to stdout pls",
790876
and you wish that configuring the simplest thing was a little easier.].
791877
792878
Here's pretty much the simplest possible logging config
793-
which just prints everything to the console (ie standard out).
879+
which just prints everything to the console (i.e. standard out).
794880
795881
796882
[role="sourcecode"]
@@ -832,7 +918,7 @@ django.db.utils.OperationalError: no such table: lists_list
832918
833919
Re-create the database with `./src/manage.py migrate` and we'll be back to a working state.
834920
835-
Don't forget to commit our changes to _settings.py_,
921+
Don't forget to commit our changes to _settings.py_ and _Dockerfile_,
836922
and I think we can call it job done!
837923
We've at least touched on many or most of the things you might need to think about
838924
when considering production-readiness,

0 commit comments

Comments
 (0)