diff --git a/textbook/chap5.md b/textbook/chap5.md
new file mode 100644
index 0000000..e0f120f
--- /dev/null
+++ b/textbook/chap5.md
@@ -0,0 +1,283 @@
+# Chapter 5. Saving User Input: Testing the Database
+
+Benefit of TDD: iterative style of development.
+
+Our target: take to-do item input from user and sent it to server
+
+## 5.1 Wiring Up Our Form to Send a POST Request
+
+To let browser send a standard HTML POST request, steps needed:
+1. Give `` element a `name=` attribute
+2. Wrap it in a `
+```
+
+Result of running FT, page is layed with content but failed test, as server haven't wired to deal with POST request yet.
+
+## 5.2 Processing a POST Request on the Server
+
+Current `home.html` don't have `action=` attribute in `\n\n
\n \n'
+```
+
+We will pass variables from Python view code into HTML templates
+
+## 5.3 Passing Python Variables to Be Rendered in the Template
+
+* Django's template syntax lets us include a python object in template using notation `{{ ... }}`
+ * renderer will display the object as a string
+
+```html
+
+
Your To-Do list
+
+
+
+
{{ new_item_text }}
+
+
+```
+* `new_item_text` is the variable name for the user input
+
+Modify test case in `lists/tests.py`, `assertTemplateUsed` will check whether we are still using template
+
+```python
+ def test_can_save_a_POST_request(self):
+ response = self.client.post('/', data={'item_text': 'A new list item'})
+ self.assertIn('A new list item', response.content.decode())
+ self.assertTemplateUsed(response, 'home.html')
+```
+
+return error
+
+```
+ self.assertTemplateUsed(response, 'home.html')
+ File "/home/jason/miniconda3/envs/python-tdd-book/lib/python3.6/site-packages/django/test/testcases.py", line 578, in assertTemplateUsed
+ self.fail(msg_prefix + "No templates used to render the response")
+AssertionError: No templates used to render the response
+```
+
+QUES: Why fail?
+
+ANS: In `lists/views.py`, `home_page` function no longer return rendered page. When facing `POST` request, it return a HttpResponse
+
+```python
+ if request.method == 'POST':
+ return HttpResponse(request.POST['item_text'])
+ return render(request, 'home.html')
+```
+
+QUES: How to fix it?
+
+ANS: Modify `home_page` method to return rendered page.
+
+QUES: How to use `render` function?
+
+ANS: `render` function takes, (3rd arguments), a dictionary which maps template variable names to their values.
+
+```python
+def home_page(request):
+ return render(request=request, template_name='home.html', context={
+ 'new_item_text': request.POST['item_text'],
+ })
+```
+
+### 5.3.1 An Unexpected Failure
+
+Running test as shown above will return error
+
+```
+ERROR: test_uses_home_template (lists.tests.HomePageTest)
+...
+ File "/home/jason/miniconda3/envs/python-tdd-book/lib/python3.6/site-packages/django/utils/datastructures.py", line 85, in __getitem__
+ raise MultiValueDictKeyError(repr(key))
+django.utils.datastructures.MultiValueDictKeyError: "'item_text'"
+```
+
+Error is from `test_uses_home_template` test case. We broke the code path where there is no POST request; i.e. explain below
+* `test_can_save_a_POST_request` get POST request with `'/', data={'item_text': 'A new list item'}`
+* `test_uses_home_template` get POST request with only `'/'`, while `request.POST['item_text']` cannot find it.
+
+QUES: How to fix it?
+
+ANS: type of `request.POST` is 'django.http.request.QueryDict' is a dictionary. So dictionary method `dict.get` can be used to supply a default value `''`
+
+```python
+def home_page(request):
+ return render(request=request, template_name='home.html', context={
+ 'new_item_text': request.POST.get('item_text', ''),
+ })
+```
+
+...
+
+## 5.4 Three Strikes and Refactor
+
+Principle: **Don't Repeat Yourself (DRY)**, or *three strikes and refactor (事不过三)*; 同一个代码段重复三次以后就应该 remove duplication
+
+* QUES: What we do?
+* ANS: 将重复的 `table = ... rows = ... self.assertIn ...` 独立为 helper function (as shown below)
+
+```python
+ def check_for_row_in_list_table(self, row_text):
+ table = self.browser.find_element_by_id('id_list_table')
+ rows = table.find_elements_by_tag_name('tr')
+ self.assertIn(row_text, [row.text for row in rows])
+```
+
+## 5.5 The Django ORM and Our First Model
+
+* **Object-Relational Mapper (ORM)** is a layer of abstraction of data stored in database (tables, rows, and columns)
+* ORM let use work with database using OOP.
+ * *Classes map to database tables*
+ * *Attributes map to columns*
+ * *Individual instance of the class is mapped to a row of data in database*
+
+Django comes with ORM. Using which, creating a new record in data is easy:
+1. creating an object
+2. assigning some attributes
+3. calling `.save()` function.
+
+Django also provide API for:
+* querying the database using class attributes of object `.object`
+* use simplest possible query `.all()` to retrieves all records for the table
+* returned object is a list-like object called 'QuerySet'
+ * Using 'QuerySet', we can extract individual objects
+
+All these can be used in test case
+
+```python
+class ItemModelTest(TestCase):
+
+ def test_saving_and_retrieving_items(self):
+ first_item = Item()
+ first_item.text = 'The first (ever) list item'
+ first_item.save()
+
+ second_item = Item()
+ second_item.text = 'Item the second'
+ second_item.save()
+
+ saved_items = Item.objects.all()
+ self.assertEqual(saved_items.count(), 2)
+
+ first_saved_item = saved_items[0]
+ second_saved_item = saved_items[1]
+ self.assertEqual(first_saved_item.text, 'The first (ever) list item')
+ self.assertEqual(second_saved_item.text, 'Item the second')
+```
+
+### 5.5.1 Our First Database Migration
+
+* In Django, ORM's job is to model the database,
+* **migration** is a system that build the database. It has ability to add/remove tables and columns, based changes on `models.py` file. Like a version control system for the database.
+
+Steps to update database:
+1. `python manage.py makemigrations` to create `migrations/000x_initials.py`
+2. Make migration via `python manage.py migrate`
+
+### 5.5.2 The Test Gets Surprisingly Far
+
+To pass the test, add class `Item`:
+
+```python
+class Item(models.Model):
+ text = models.TextField(default='')
+```
+
+Then migrate the database again, and then we can pass the test
+
+## 5.6 Saving the POST to the Database
+
+Modify the POST request test case that view will save a new item to the database instead of just passing to response.
+
+```python
+ def test_can_save_a_POST_request(self):
+ response = self.client.post('/', data={'item_text': 'A new list item'})
+
+ self.assertEqual(Item.objects.count(), 1) # check that one new Item has been saved to the database
+ new_item = Item.objects.first() # same as doing objects.all()[0]
+ self.assertEqual(new_item.text, 'A new list item') # check that the item's text is correct
+
+ self.assertIn('A new list item', response.content.decode())
+ self.assertTemplateUsed(response, 'home.html')
+```
+
+First step to pass the test: modify `home_page` function to save request (including empty request just to '/')
+
+```python
+def home_page(request):
+ item = Item()
+ item.text = request.POST.get('item_text', '')
+ item.save()
+
+ return render(request=request, template_name='home.html', context={
+ 'new_item_text': request.POST.get('item_text', ''),
+ })
+```
+
+To-Do List to modify code (sometimes make sure code pass is ok, modify later):
+* Don't save blank items for every request (empty request)
+* Code small: POST test is too long?
+* Display multiple items in the table
+* Support more than one list
+
+Solve the first todo list by creating a UT:
+
+```python
+ def test_only_saves_items_when_necessary(self):
+ self.client.get('/')
+ self.assertEqual(Item.objects.count(), 0)
+```
+
+Running test will result `AssertionError: 1 != 0`
+
+We can pass this UT via modifying `home_page`:
+
+```python
+def home_page(request):
+ if request.method == 'POST':
+ new_item_text = request.POST['item_text'] # use new_item_text to either hold POST contents or empty string
+ Item.objects.create(text=new_item_text) # .object.create is shortcut for creating a new Item, without needing to call .save()
+ else:
+ new_item_text = ''
+
+ return render(request=request, template_name='home.html', context={
+ 'new_item_text': request.POST.get('item_text', ''),
+ })
+```
\ No newline at end of file
diff --git a/textbook/img/5-1.jpg b/textbook/img/5-1.jpg
new file mode 100644
index 0000000..dfdfe18
Binary files /dev/null and b/textbook/img/5-1.jpg differ