วันอาทิตย์ที่ 29 มีนาคม พ.ศ. 2558

A1 - Electricity Charge Calaulator Web Application

BitBucket Link
https://bitbucket.org/pongsarkorn_chinbut/a1/commits/all

Unit Test ยังมีปัญหาในฟังก์ชันตรวจสอบการคำนวนค่าเนื่องจากยังไม่สามารถเรียกดูโค้ด html จาก response ได้


Functional Test มีการตรวจสอบการใช้งานสำหรับผู้ใช้งานหลายคน ผ่านแล้ว

แต่ในการรันด้วย Server ที่ 0.0.0.0:8000 ยังมีปัญหาอยู่เช่นเดิม (http://bss56-20056.blogspot.com/2015/03/a1_23.html)

วันจันทร์ที่ 23 มีนาคม พ.ศ. 2558

สิ่งที่ได้ไปทำเพิ่มเติมใน A1 (เพิ่มเติม)

ตอนนี้สามารถทำให้ Functional Test มีการแยก URL ในการใส่ข้อมูลเครื่องใช้ไฟฟ้าของแต่ละคนได้แล้ว
เมื่อใส่ข้อมูลของคนแรกแล้วกด submit จะ redirect จาก root มาที่ /electricity_charge/1/



เมื่อใส่ข้อมูลของคนที่สองแล้วกด submit จะ redirect จาก root มาที่ /electricity_charge/2/ 

แต่เมื่อรันด้วย server ที่ 0.0.0.0:8000 เมื่อกดส่งข้อมูลยังขึ้นหน้านี้อยู่

วันอาทิตย์ที่ 22 มีนาคม พ.ศ. 2558

สิ่งที่ได้ไปทำเพิ่มเติมใน A1

ตอนนี้ทำถึงให้แต่ละหน้าของการคำนวนค่าไฟฟ้ามี URL เป็นของตนเองแล้ว Unit test ผ่านทั้งหมด แต่เกิดปัญหาตรงที่ Functional test ขึ้นว่า Server error 500

ขึ้น error ดังนี้


เมื่อลองรันโดย server ไปที่ 0.0.0.0:8000 เมื่อลองกดส่งข้อมูลขึ้นดังนี้




วันพฤหัสบดีที่ 12 กุมภาพันธ์ พ.ศ. 2558

Test-Driven Development with Python - สรุป Chapter. 6

คำสำคัญที่พูดถึงในหนังสือ TDD with Python

Big Design Up Front (BDUF)

เป็นแนวทางการพัฒนา software ที่จะมีการออกแบบโปรแกรมให้เสร็จสิ้นก่อนจะเริ่มสร้างโปรแกรมขึ้นมา ซึ่งเกี่ยวข้องกับการพัฒนา software แบบ "Waterfall Model"
(ซึ่งตามหนังสืออธิบายว่าแนวทางของ TDD นั้นตรงข้ามกับ BDUF คือเป็นการพัฒนาที่มีความคล่องตัว นั่นคือการแก้ไขปัญหาจากการเขียนโค้ดและให้ได้โค้ดที่สามารถใช้งานจริงได้ในระดับนึง แล้วพัฒนาโปรแกรมนั้นตามการใช้งานจริงของ User (พัฒนาตาม feedback ของ User))
ศึกษาข้อมูลเพิ่มเติม :
http://en.wikipedia.org/wiki/Big_Design_Up_Front

Representational State Transfer (REST)

เป็นสถาปัตยกรรมที่ใช้สำหรับการแพร่กระจายสื่อ ที่ออกแบบมาให้มีความง่าย โดยการแลกเปลี่ยนข้อมูลกันระหว่าง HTTP และ XML โดยการร้องขอแบบ GET Request (การร้องขอโดยการส่ง attribute ของข้อมูลที่ต้องการแนบมากับ URL)
ศึกษาข้อมูลเพิ่มเติม :
http://java-thai-talk.blogspot.com/2012/01/restful.html
https://eddyatlab.wordpress.com/2009/06/11/rest-representational-state-transfer/

Key

การแก้ไขเปลี่ยนแปลงข้อมูล หรือการกำหนดความสัมพันธ์ระหว่างข้อมูลจะเกิดขึ้นอย่างมีประสิทธิภาพ จะต้องกำหนด คีย์ (Key) ให้กับ Table ก่อน นอกจากนี้การกำหนดคีย์จะทำให้การอ้างอิงและการประมวลผลข้อมูลได้สะดวกขึ้นและยังช่วยประหยัดเนื้อที่ในการจัดเก็บ
ศึกษาข้อมูลเพิ่มเติม :
http://www2.tsu.ac.th/cst/course/computer_it/database/key.html

สรุปการทดลองใช้งานคำสั่งตามหนังสือ TDD with Python

เริ่มต้นตามหนังสืออธิบายว่าตอนนี้ database ที่เราใช้ในการ Functional Test นั้นเป็น database จริง ซึ่งมีการบันทึกข้อมูลจากตัว test ลงไปใน db.sqlite3 ซึ่งจริงๆแล้วตัว test ไม่ควรจะไปยุ่งกับข้อมูลใน database ที่เราใช้งาน โดยใน Django มี Class ชื่อว่า LiveServerTestCase ที่จะช่วยจำลอง database ขึ้นมาให้เรา
ซึ่ง LiveServerTestCase ใช้ตัวรัน Functional Test ของ Django ซึ่งต้องรันที่ manage.py และจะค้นหาไฟล์ที่ขึ้นต้นด้วย test ซึ่งเราจะทำการสร้าง directory สำหรับ FT โดยเฉพาะ และเปลี่ยนชื่อ file เป็น test.py

สร้าง directory ใช้คำสั่ง
mkdir functional_tests
และต้องสร้าง init file สำหรับ directory ของ python ที่จะใช้กับ Django ใช้คำสั่ง
touch functional_tests/__init__.py
ย้ายไปยัง directory functional_testและเปลี่ยนชื่อเป็น tests.py ใช้คำสั่ง
git mv functional_tests.py functional_tests/tests.py

เปลี่ยน Class ใน FT ให้รับ parameter จาก LiveServerTestCase
from django.test import LiveServerTestCase 
     [....]
    class NewVisitorTest(LiveServerTestCase):
และเปลี่ยนจากการเรียก localhost port 8000 เป็นใช้ attribute ของ LiveServerTestCase
    def test_can_start_a_list_and_retrieve_it_later(self):
        # Edith has heard about a cool new online to-do app. She goes
        # to check out its homepage
        self.browser.get(self.live_server_url)
คำสั่งในการรัน FT ก็จะเปลี่ยนไปสั่งที่ manage.py
python3 manage.py test functional_tests
และคำสั่งที่จะรัน Unit Test ก็ต้องเปลี่ยนด้วยเนื่องจากมี FT เพิ่มเข้ามา จึงต้องเจาะจงไปที่ test ใน lists
python3 manage.py test lists

-------------------------------------------------------------------------------------------------------------------------------

ต่อไปเราจะใช้แนวคิดของ REST ซึ่งจะใช้ URL structure ในการทำงานในกรณีต่างๆ ของ app lists ของเรา
  • โดยการระบุว่าจะเป็น list ไหน (รองรับหลาย list) ให้แต่ละ list มี URL เป็นของตัวเอง (ส่งการร้องขอไปยัง server แบบ GET Request คือการส่ง attribute ระบุข้อมูลที่ต้องการจาก server บนส่ง URL)
/lists/<คำที่จะเจาะจงว่าเป็น list ไหน (ชื่อ list)>/
  • สร้าง list ใหม่ให้ส่งการร้องขอแบบ POST Request
/lists/new
  • เพิ่มข้อมูลไปยัง list ที่มีอยู่แล้วทาง POST Request
/lists/<คำที่จะเจาะจงว่าเป็น list ไหน (ชื่อ list)>/add_item

จากนั้นเราจะเพิ่มตัว FT ให้ตรวจสอบการใช้งานหลายๆ list
    inputbox.send_keys('Buy peacock feathers')

    # When she hits enter, she is taken to a new URL,
    # and now the page lists "1: Buy peacock feathers" as an item in a
    # to-do list table
    inputbox.send_keys(Keys.ENTER)
    edith_list_url = self.browser.current_url
    self.assertRegex(edith_list_url, '/lists/.+') #1
    self.check_for_row_in_list_table('1: Buy peacock feathers')

    # There is still a text box inviting her to add another item. She
    [...]
จะตรวจสอบว่า URL นี้ตรงกับ regular expression (ลำดับของการเขียน URL) หรือไม่

จากนั้นเราจะลอง test ให้สามารถใส่ข้อมูลได้หลาย list
    [...]
    # The page updates again, and now shows both items on her list
    self.check_for_row_in_list_table('2: Use peacock feathers to make a fly')
    self.check_for_row_in_list_table('1: Buy peacock feathers')

    # Now a new user, Francis, comes along to the site.

    ## We use a new browser session to make sure that no information
    ## of Edith's is coming through from cookies etc #1
    self.browser.quit()
    self.browser = webdriver.Firefox()

    # Francis visits the home page.  There is no sign of Edith's
    # list
    self.browser.get(self.live_server_url)
    page_text = self.browser.find_element_by_tag_name('body').text
    self.assertNotIn('Buy peacock feathers', page_text)
    self.assertNotIn('make a fly', page_text)

    # Francis starts a new list by entering a new item. He
    # is less interesting than Edith...
    inputbox = self.browser.find_element_by_id('id_new_item')
    inputbox.send_keys('Buy milk')
    inputbox.send_keys(Keys.ENTER)

    # Francis gets his own unique URL
    francis_list_url = self.browser.current_url
    self.assertRegex(francis_list_url, '/lists/.+')
    self.assertNotEqual(francis_list_url, edith_list_url)

    # Again, there is no trace of Edith's list
    page_text = self.browser.find_element_by_tag_name('body').text
    self.assertNotIn('Buy peacock feathers', page_text)
    self.assertIn('Buy milk', page_text)

    # Satisfied, they both go back to sleep
เมื่อลองรัน FT แล้วจะ error ว่า
AssertionError: Regex didn't match: '/lists/.+' not found in
'http://localhost:8081/'
-------------------------------------------------------------------------------------------------------------------------------

ต่อไปหนังสือจะให้สร้างตัว Unit Test สำหรับการ Redirect เมื่อได้รับการร้องขอแบบ POST ให้ไปยัง
/lists/the-only-list-in-the-world/)เปลี่ยนจากกลับไปยัง home_page ('/')
self.assertEqual(response.status_code, 302)
self.assertEqual(response['location'], '/lists/the-only-list-in-the-world/')
และไปเปลี่ยนที่ views.py ด้วย
def home_page(request):
    if request.method == 'POST':
        Item.objects.create(text=request.POST['item_text'])
        return redirect('/lists/the-only-list-in-the-world/')

    items = Item.objects.all()
    return render(request, 'home.html', {'items': items})
ซึ่ง UT จะผ่านแต่ FT จะ error ว่า
    self.check_for_row_in_list_table('1: Buy peacock feathers')
[...]
selenium.common.exceptions.NoSuchElementException: Message: 'Unable to locate
element: {"method":"id","selector":"id_list_table"}' ; Stacktrace:
เนื่องจากยังไม่มี list item ชื่อว่า the-only-list-in-the-world

-------------------------------------------------------------------------------------------------------------------------------

ต่อไปเราจะใช้ Django test client ช่วย Unit Test ในการตรวจสอบ การ map URL ,การตรวจสอบ views และการตรวจสอบการ render template ของ views โดยการสร้าง Class ชื่อ
ListViewTest และย้ายฟังก์ชัน test_displays_all_items(self) มา
class ListViewTest(TestCase):

    def test_displays_all_items(self):
        Item.objects.create(text='itemey 1')
        Item.objects.create(text='itemey 2')

        response = self.client.get('/lists/the-only-list-in-the-world/') #1

        self.assertContains(response, 'itemey 1') #2
        self.assertContains(response, 'itemey 2') #3
เมื่อลองรัน error เนื่องจากยังหา URL ไม่เจอว่า
AssertionError: 404 != 200 : Couldn't retrieve content: Response code was 404
เราจึงไปสร้าง URL ใหม่ที่ urls.py
urlpatterns = patterns('',
    url(r'^$', 'lists.views.home_page', name='home'),
    url(r'^lists/the-only-list-in-the-world/$', 'lists.views.view_list',
        name='view_list'
    ),
    # url(r'^admin/', include(admin.site.urls)),
)
รัน UT อีกครั้ง error ว่า import view_list ไม่ได้
AttributeError: 'module' object has no attribute 'view_list'
[...]
django.core.exceptions.ViewDoesNotExist: Could not import
lists.views.view_list. View does not exist in module lists.views.
ดังนั้นจึงไปสร้าง view_list ใน views.py
def view_list(request):
    items = Item.objects.all()
    return render(request, 'home.html', {'items': items})
เมื่อรัน UT จะผ่านแต่รัย FT จะ error ว่า
AssertionError: '2: Use peacock feathers to make a fly' not found in ['1: Buy
peacock feathers']
เนื่องจากเราใช้ template เดียวกันทั้งหน้า home page และ หน้าที่จะแสดง list (home.html)


ต่อไปจะทำการ refactoring เอา test ที่ไม่จำเป็นแล้วออกจาก unit test
ดูว่าในไฟล์นั้นมี class และ function อะไรบ้างใช้คำสั่ง
grep -E "class|def" lists/tests.py

เราจะลบ test_home_page_displays_all_list_items ออกเนื่องจากไม่จำเป็นแล้ว

-------------------------------------------------------------------------------------------------------------------------------

ต่อไปเราจะทำการแบ่ง template ของ home page กับ แสดง list ออกจากกัน
เริ่มต้นด้วยการสร้าง Unit Test ตรวจสอบว่าหน้าแสดง list ก็ใช้ template ของมันเอง
class ListViewTest(TestCase):

    def test_uses_list_template(self):
        response = self.client.get('/lists/the-only-list-in-the-world/')
        self.assertTemplateUsed(response, 'list.html')


    def test_displays_all_items(self):
        [...]
และไปเปลี่ยนที่ views.py ให้เรียก list.html
def view_list(request):
    items = Item.objects.all()
    return render(request, 'list.html', {'items': items})
เมื่อสร้าง test เสร็จจึงไปสร้าง template มา สร้างไฟล์ใหม่ใช้คำสั่ง
touch lists/templates/list.html
แล้วก็ copy เนื้อใน file มาจาก home.html ก่อน
cp lists/templates/home.html lists/templates/list.html

จากนั้นที่หน้า home เราจะเปลี่ยนคำที่แสดงบนหน้าแรกว่า start a new to-do list ให้สื่อความหมายว่าเป็นหน้าเริ่มต้น
<body>
    <h1>Start a new To-Do list</h1>
    <form method="POST">
        <input name="item_text" id="id_new_item" placeholder="Enter a to-do item" />
        {% csrf_token %}
    </form>
</body>
และไปแก้ที่ views.py ให้หลังจาก POST Request แล้วให้ redirect ไปหน้า list
def home_page(request):
    if request.method == 'POST':
        Item.objects.create(text=request.POST['item_text'])
        return redirect('/lists/the-only-list-in-the-world/')
    return render(request, 'home.html')
เมื่อรัน UT จะผ่าน  แต่ FT จะ error
จากนั้นแก้ที่ list.html ให้มี action ไปยัง home_page (ไม่ให้กลับมาที่เดิมตาม default ของมัน)
   <form method="POST" action="/">
เมื่อลองรัน FT จะ error ว่า
    self.assertNotEqual(francis_list_url, edith_list_url)
AssertionError: 'http://localhost:8081/lists/the-only-list-in-the-world/' ==
'http://localhost:8081/lists/the-only-list-in-the-world/'
แต่เราก็ได้ทำการ refactoring การเรียกใช้ template แยกกันเสร็จแล้ว

-------------------------------------------------------------------------------------------------------------------------------

ตอนนี้เรามี URL สำหรับแต่ละ list แต่ยังไม่มีการสร้าง list หลายๆ list ไว้เก็บ
ดังนั้นเริ่มต้นด้วยการสร้าง UT ใหม่ แต่ย้าย 2 ฟังก์ชั่นนี้มา
class NewListTest(TestCase):

    def test_saving_a_POST_request(self):
        request = HttpRequest()
        request.method = 'POST'
        [...]

    def test_redirects_after_POST(self):
        [...]
แล้วเราก็จะแก้ไขให้ใช้การทำงานของ Django test client
class NewListTest(TestCase):

    def test_saving_a_POST_request(self):
        self.client.post(
            '/lists/new',
            data={'item_text': 'A new list item'}
        )
        self.assertEqual(Item.objects.count(), 1)
        new_item = Item.objects.first()
        self.assertEqual(new_item.text, 'A new list item')


    def test_redirects_after_POST(self):
        response = self.client.post(
            '/lists/new',
            data={'item_text': 'A new list item'}
        )
        self.assertEqual(response.status_code, 302)
        self.assertEqual(response['location'], '/lists/the-only-list-in-the-world/')
สังเกตว่า URL ที่ไม่มี '/' ปิดท้ายจะหมายถึง URL ที่จะดำเนินการแก้ไขข้อมูลใน database

ลองรัน UT จะ error ว่า
    self.assertEqual(Item.objects.count(), 1)
AssertionError: 0 != 1
[...]
    self.assertEqual(response.status_code, 302)
AssertionError: 404 != 302
เพราะเรายังไม่มี URL ชื่อว่า list/new ดังนั้นไปเพิ่มที่ urls.py
urlpatterns = patterns('',
    url(r'^$', 'lists.views.home_page', name='home'),
    url(r'^lists/the-only-list-in-the-world/$', 'lists.views.view_list',
        name='view_list'
    ),
    url(r'^lists/new$', 'lists.views.new_list', name='new_list'),
    # url(r'^admin/', include(admin.site.urls)),
)
และเพิ่มที่ views.py ด้วย
def new_list(request):
    pass
UT จะ error ว่า "The view lists.views.new_list didn’t return an HttpResponse object" ดังนั้นต้องให้มัน return
def new_list(request):
    return redirect('/lists/the-only-list-in-the-world/')
เมื่อรันจะกลับไป error
    self.assertEqual(Item.objects.count(), 1)
AssertionError: 0 != 1
[...]
AssertionError: 'http://testserver/lists/the-only-list-in-the-world/' !=
'/lists/the-only-list-in-the-world/'
เราจึงไปเพิ่มฟังก์ชันใน views.py
def new_list(request):
    Item.objects.create(text=request.POST['item_text'])
    return redirect('/lists/the-only-list-in-the-world/')
และไปแก้การ redirect ใน list.py ให้ใช้ความสามารถของ Django test client
    def test_redirects_after_POST(self):
        response = self.client.post(
            '/lists/new',
            data={'item_text': 'A new list item'}
        )
        self.assertRedirects(response, '/lists/the-only-list-in-the-world/') 
 เมื่อรัน UT ก็จะผ่าน

-------------------------------------------------------------------------------------------------------------------------------

ต่อไปก็จะไป refactoring ใน views.py ด้วยการลบส่วน if request.method == 'POST' ออก
def home_page(request):
    return render(request, 'home.html')

 ต่อไปเราจะแก้ไขให้ template ทั้งสองอันเชื่อมต่อกันด้วย list/new URL โดยไปแก้ทั้งสอง template เป็น
    <form method="POST" action="/lists/new">
-------------------------------------------------------------------------------------------------------------------------------

ต่อไปจะเป็นการปรับ model ของเราให้มีโมเดลที่รวบรวม list ด้วย โดยเริ่มแก้ไขที่ UT
@@ -3,7 +3,7 @@ from django.http import HttpRequest
 from django.template.loader import render_to_string
 from django.test import TestCase

-from lists.models import Item
+from lists.models import Item, List
 from lists.views import home_page

 class HomePageTest(TestCase):
@@ -60,22 +60,32 @@ class ListViewTest(TestCase):

-class ItemModelTest(TestCase):
+class ListAndItemModelsTest(TestCase):

     def test_saving_and_retrieving_items(self):
+        list_ = List()
+        list_.save()
+
         first_item = Item()
         first_item.text = 'The first (ever) list item'
+        first_item.list = list_
         first_item.save()

         second_item = Item()
         second_item.text = 'Item the second'
+        second_item.list = list_
         second_item.save()

+        saved_list = List.objects.first()
+        self.assertEqual(saved_list, list_)
+
         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(first_saved_item.list, list_)
         self.assertEqual(second_saved_item.text, 'Item the second')
+        self.assertEqual(second_saved_item.list, list_)

 เมื่อรัน UT ก็แน่นอนว่าจะไม่พบ List ดังนั้นจึงไปเพิ่มที่ models.py ให้มี Class ของ List
class List(models.Model):
    pass
 จะ error ว่า
django.db.utils.OperationalError: no such table: lists_list
 ดังนั้นเราก็จะไปสร้าง table ของ list ขึ้นมา ด้วย migrations เป็น table ของ list
python3 manage.py makemigrations
 และเมื่อลองรันอีกครั้งจะ error ว่า
    self.assertEqual(first_saved_item.list, list_)
AttributeError: 'Item' object has no attribute 'list'
 เนื่องจาก table ของ Item และ list นั้นยังไม่มีความสัมพันธ์ซึ่งกันและกัน ดังนั้นเราจะเพิ่มให้มี
Foreign Key ซึ่งจะเชื่อม table ให้มีความสัมพันธ์กัน โดยการแก้ดังนี้
from django.db import models

class List(models.Model):
    pass

class Item(models.Model):
    text = models.TextField(default='')
    list = models.TextField(default='')
และสร้าง migrations ใหม่สำหรับ list โดยจะ add field list เข้าไปใน Item
python3 manage.py makemigrations
แต่ UT error ว่า
AssertionError: 'List object' != <List: List object>
 ซึ่งต้องบอก Django ว่า 2 Class นี้สัมพันธ์กัน
from django.db import models

class List(models.Model):
    pass


class Item(models.Model):
    text = models.TextField(default='')
    list = models.ForeignKey(List, default=None)
 ลบ migrations แล้วสร้างใหม่
rm lists/migrations/0004_item_list.py
python3 manage.py makemigrations
แต่ UT จะ error ว่า
ERROR: test_displays_all_items (lists.tests.ListViewTest)
django.db.utils.IntegrityError: NOT NULL constraint failed: lists_item.list_id
[...]
ERROR: test_redirects_after_POST (lists.tests.NewListTest)
django.db.utils.IntegrityError: NOT NULL constraint failed: lists_item.list_id
[...]
ERROR: test_saving_a_POST_request (lists.tests.NewListTest)
django.db.utils.IntegrityError: NOT NULL constraint failed: lists_item.list_id

Ran 7 tests in 0.021s

FAILED (errors=3)
โดยจะต้องมีการสร้าง list ให้ตอนที่ test การใส่ item ด้วย
class ListViewTest(TestCase):

    def test_displays_all_items(self):
        list_ = List.objects.create()
        Item.objects.create(text='itemey 1', list=list_)
        Item.objects.create(text='itemey 2', list=list_)
และแก้ใน views.py
from lists.models import Item, List
[...]
def new_list(request):
    list_ = List.objects.create()
    Item.objects.create(text=request.POST['item_text'], list=list_)
    return redirect('/lists/the-only-list-in-the-world/')
เมื่อรัน Unit Test ก็จะผ่าน

-------------------------------------------------------------------------------------------------------------------------------

ต่อไปเราจะทำให้แต่ละ list มี URL เป็นของตัวเองโดยเป็นชื่อของ list นั่นเอง โดยแก้ที่ unit test
class ListViewTest(TestCase):

    def test_uses_list_template(self):
        list_ = List.objects.create()
        response = self.client.get('/lists/%d/' % (list_.id,))
        self.assertTemplateUsed(response, 'list.html')


    def test_displays_only_items_for_that_list(self):
        correct_list = List.objects.create()
        Item.objects.create(text='itemey 1', list=correct_list)
        Item.objects.create(text='itemey 2', list=correct_list)
        other_list = List.objects.create()
        Item.objects.create(text='other list item 1', list=other_list)
        Item.objects.create(text='other list item 2', list=other_list)

        response = self.client.get('/lists/%d/' % (correct_list.id,))

        self.assertContains(response, 'itemey 1')
        self.assertContains(response, 'itemey 2')
        self.assertNotContains(response, 'other list item 1')
        self.assertNotContains(response, 'other list item 2')
และแก้ที่ urls.py
urlpatterns = patterns('',
    url(r'^$', 'lists.views.home_page', name='home'),
    url(r'^lists/(.+)/$', 'lists.views.view_list', name='view_list'),
    url(r'^lists/new$', 'lists.views.new_list', name='new_list'),
    # url(r'^admin/', include(admin.site.urls)),
)
โดยในตำแหน่งของ (.+) จะใส่เป็นชื่อของ list ที่จะรับมาจากการ parameter ของ views.py
def view_list(request, list_id):
    list_ = List.objects.get(id=list_id)
    items = Item.objects.filter(list=list_)
    return render(request, 'list.html', {'items': items})
และไปแก้ที่ UT ในการตรวจสอบการ redirect ว่า
    def test_redirects_after_POST(self):
        response = self.client.post(
            '/lists/new',
            data={'item_text': 'A new list item'}
        )
        new_list = List.objects.first()
        self.assertRedirects(response, '/lists/%d/' % (new_list.id,))
เมื่อรัน Unit Test จะผ่าน แต่ Functional Test จะ error ว่า
AssertionError: '2: Use peacock feathers to make a fly' not found in ['1: Use
peacock feathers to make a fly']
-------------------------------------------------------------------------------------------------------------------------------

ต่อไปจะเป็นการเพิ่ม item ที่รับมาไปยัง list ที่ต้องการ โดยเริ่มจาก UT
class NewItemTest(TestCase):

    def test_can_save_a_POST_request_to_an_existing_list(self):
        other_list = List.objects.create()
        correct_list = List.objects.create()

        self.client.post(
            '/lists/%d/add_item' % (correct_list.id,),
            data={'item_text': 'A new item for an existing list'}
        )

        self.assertEqual(Item.objects.count(), 1)
        new_item = Item.objects.first()
        self.assertEqual(new_item.text, 'A new item for an existing list')
        self.assertEqual(new_item.list, correct_list)


    def test_redirects_to_list_view(self):
        other_list = List.objects.create()
        correct_list = List.objects.create()

        response = self.client.post(
            '/lists/%d/add_item' % (correct_list.id,),
            data={'item_text': 'A new item for an existing list'}
        )

        self.assertRedirects(response, '/lists/%d/' % (correct_list.id,))
จะ error
AssertionError: 301 != 302 : Response didn't redirect as expected: Response
code was 301 (expected 302)
เราจะแก้ที่ urls.py โดยใช้ regular expression \d
    url(r'^lists/(\d+)/$', 'lists.views.view_list', name='view_list'),
ก็จะเปลี่ยนมาเป็น
AssertionError: 404 != 302 : Response didn't redirect as expected: Response
code was 404 (expected 302)
เนื่องจากไม่พบหน้า add_item โดยจะเพิ่ม
urlpatterns = patterns('',
    url(r'^$', 'lists.views.home_page', name='home'),
    url(r'^lists/(\d+)/$', 'lists.views.view_list', name='view_list'),
    url(r'^lists/(\d+)/add_item$', 'lists.views.add_item', name='add_item'),
    url(r'^lists/new$', 'lists.views.new_list', name='new_list'),
    # url(r'^admin/', include(admin.site.urls)),
)
และจะต้องไปเพิ่ม add ที่ views ด้วย
def add_item(request, list_id):
    list_ = List.objects.get(id=list_id)
    Item.objects.create(text=request.POST['item_text'], list=list_)
    return redirect('/lists/%d/' % (list_.id,))

และในหน้า template เราอยากให้มีการ action ที่ add_item ก็แก้ที่ form ว่า
    <form method="POST" action="/lists/{{ list.id }}/add_item">
และสร้าง UT ใหม่เพื่อทดสอบว่า list ที่ list template นั้นถูกอัน
    def test_passes_correct_list_to_template(self):
        other_list = List.objects.create()
        correct_list = List.objects.create()
        response = self.client.get('/lists/%d/' % (correct_list.id,))
        self.assertEqual(response.context['list'], correct_list)
และแก้ไขที่ views.py
def view_list(request, list_id):
    list_ = List.objects.get(id=list_id)
    return render(request, 'list.html', {'list': list_})
จะ error ว่า
AssertionError: False is not true : Couldn't find 'itemey 1' in response

 โดยจะแก้ที่ list template ให้เอาข้อมูลมาจาก list
   <form method="POST" action="/lists/{{ list.id }}/add_item">

    [...]

        {% for item in list.item_set.all %}
            <tr><td>{{ forloop.counter }}: {{ item.text }}</td></tr>
        {% endfor %}
 เมื่อรัน UT และ FT จะผ่าน

-------------------------------------------------------------------------------------------------------------------------------

สุดท้ายจะเป็นการแยก URL ที่เกี่ยวข้องกับหน้า home_page และหน้า list ออกจากกัน

โดยจะย้ายของ list ซึ่งเป็นของ app ไปไว้ใน app lists
cp superlists/urls.py lists/

urls.py ของ home_page ใน superlists
urlpatterns = patterns('',
    url(r'^$', 'lists.views.home_page', name='home'),
    url(r'^lists/', include('lists.urls')),
    # url(r'^admin/', include(admin.site.urls)),
)
urls.py ของ list ใน lists
from django.conf.urls import patterns, url

urlpatterns = patterns('',
    url(r'^(\d+)/$', 'lists.views.view_list', name='view_list'),
    url(r'^(\d+)/add_item$', 'lists.views.add_item', name='add_item'),
    url(r'^new$', 'lists.views.new_list', name='new_list'),
)


ศึกษาข้อมูลเพิ่มเติม :
http://chimera.labs.oreilly.com/books/1234000000754/ch06.html#_a_final_refactor_using_url_includes