วันเสาร์ที่ 17 มีนาคม พ.ศ. 2561

(Week 8-1) วันมาฆบูชา

วันมาฆบูชา (บาลี: มาฆปูชา; อักษรโรมัน: Magha Puja) เป็นวันสำคัญของชาวพุทธเถรวาทและวันหยุดราชการในประเทศไทย "มาฆบูชา" ย่อมาจาก "มาฆปูรณมีบูชา" หมายถึง การบูชาในวันเพ็ญกลางเดือนมาฆะตามปฏิทินอินเดีย หรือเดือน 3 ตามปฏิทินจันทรคติของไทย (ตกช่วงเดือนกุมภาพันธ์หรือมีนาคม) ถ้าปีใดมีเดือนอธิกมาส คือมีเดือน 8 สองหน (ปีอธิกมาส) ก็เลื่อนไปทำในวันเพ็ญเดือน 3 หลัง (วันเพ็ญเดือน 4)

วันมาฆบูชาได้รับการยกย่องเป็นวันสำคัญทางศาสนาพุทธ เนื่องจากเหตุการณ์สำคัญที่เกิดขึ้นเมื่อ 2,500 กว่าปีก่อน คือ พระโคตมพุทธเจ้าทรงแสดงโอวาทปาติโมกข์ท่ามกลางที่ประชุมมหาสังฆสันนิบาตครั้งใหญ่ในพระพุทธศาสนา คัมภีร์ปปัญจสูทนีระบุว่าครั้งนั้นมีเหตุการณ์เกิดขึ้นพร้อมกัน 4 ประการ คือ พระภิกษุ 1,250 รูป ได้มาประชุมพร้อมกันยังวัดเวฬุวันโดยมิได้นัดหมาย, พระภิกษุทั้งหมดนั้นเป็น "เอหิภิกขุอุปสัมปทา" หรือผู้ได้รับการอุปสมบทจากพระพุทธเจ้าโดยตรง, พระภิกษุทั้งหมดนั้นล้วนเป็นพระอรหันต์ผู้ทรงอภิญญา 6, และวันดังกล่าวตรงกับวันเพ็ญเดือน 3 ดังนั้น จึงเรียกวันนี้อีกอย่างหนึ่งว่า "วันจาตุรงคสันนิบาต" หรือ วันที่มีการประชุมพร้อมด้วยองค์

เดิมนั้นไม่มีพิธีมาฆบูชาในประเทศพุทธเถรวาท จนมาในสมัยของพระบาทสมเด็จพระจอมเกล้าเจ้าอยู่หัว (รัชกาลที่ 4) พระองค์ได้ทรงปรารภถึงเหตุการณ์ครั้งพุทธกาลในวันเพ็ญเดือน 3 ดังกล่าวว่า เป็นวันที่เกิดเหตุการณ์สำคัญยิ่ง ควรประกอบพิธีทางพระพุทธศาสนา เพื่อเป็นที่ตั้งแห่งความศรัทธาเลื่อมใส จึงมีพระมหากรุณาธิคุณโปรดเกล้าฯ ให้จัดการพระราชกุศลมาฆบูชาขึ้น การประกอบพระราชพิธีคงคล้ายกับวันวิสาขบูชา คือ มีการบำเพ็ญพระราชกุศลต่าง ๆ และมีการพระราชทานจุดเทียนตามประทีปเป็นพุทธบูชาในวัดพระศรีรัตนศาสดารามและพระอารามหลวงต่าง ๆ เป็นต้น ในช่วงแรก พิธีมาฆบูชาคงเป็นการพระราชพิธีภายใน ยังไม่แพร่หลายทั่วไป ต่อมา ความนิยมจัดพิธีมาฆบูชาจึงได้ขยายออกไปทั่วราชอาณาจักร

ปัจจุบัน วันมาฆบูชาได้รับการประกาศให้เป็นวันหยุดราชการในประเทศไทย โดยพุทธศาสนิกชนทั้งพระบรมวงศานุวงศ์ พระสงฆ์และประชาชนประกอบพิธีต่าง ๆ เช่น การตักบาตร การฟังพระธรรมเทศนา การเวียนเทียน เป็นต้น เพื่อบูชารำลึกถึงพระรัตนตรัยและเหตุการณ์สำคัญดังกล่าวที่ถือได้ว่า เป็นวันที่พระพุทธเจ้าประทานโอวาทปาฏิโมกข์ ซึ่งกล่าวถึงหลักคำสอนอันเป็นหัวใจของพระพุทธศาสนา ได้แก่ การไม่ทำความชั่วทั้งปวง การบำเพ็ญความดีให้ถึงพร้อม และการทำจิตของตนให้ผ่องใส เพื่อเป็นหลักปฏิบัติของพุทธศาสนิกชนทั้งมวล

นอกจากนี้ ในปี พ.ศ. 2549 รัฐบาลไทยได้ประกาศให้วันมาฆบูชาเป็น "วันกตัญญูแห่งชาติ" เนื่องจากในสังคมไทยปัจจุบัน หญิงสาวมักเสียตัวในวันวาเลนไทน์ หลายหน่วยงานจึงพยายามรณรงค์ให้วันมาฆบูชาเป็นวันแห่งความรัก (อันบริสุทธิ์) แทน
 Related image

ข้อมูลจาก : wikipedia TH : วันมาฆบูชา

Test-Driven Development with Python(Chapter 7)

เนื่องจากเดิมอันเดิมยาวและติดกันเกินไปทำให้ยากต่อการอ่าน
จึงแยก (Week 3-2) - (Week 5-1) Test-Driven Development with Python ออกเป็น 7 part

Chapter 7


เริ่ม chapter 7 หนังสือบอกจะพัฒนาให้ app ของเราสามารถลองรับ user ได้มากขึ้น โดยแบ่งเป็นหลายๆ list แยกกัน โดยมี url ดังรูป
โดยมี use case ดังนี้
ในขั้นต้นแยก functional test เป็นสำหรับ user เดียว และ multi user (เพิ่มเรื่องราว user story โดย Francis เข้ามาใช้งานด้วยอีกคนนอกจาก Edith)
โดยมีหน้าตาเต็มๆดังนี้
- ในขั้นต้น update test_redirects_after_POST() โดย assert locationว่า ไปที่ '/lists/the-only-list-in-the-world/' หรือไม่
- เมื่อ unit test ก็ fail ตามคาดเพราะยังไม่ได้แก้อะไรเพิ่มตามที่ test
- แก้แบบลวกๆโดยให้ redirect ไปที่ '/lists/the-only-list-in-the-world/'
- unit test ผ่าน
แต่ functional test จะติด error มากมายดังรูปเพราะ page not found (ยังไม่รู้จัก url path '/the-only-list-in-the-world/' )
- จึงมา write minimal code (เขียน unit test) ต่อ โดยเขียน test ว่าแสดง item ครบหรือไม่
- เมื่อ unit test จะ error ที่ self.assertContains(response, 'itemey 1') เพราะยังไม่ได้สร้าง function view_list() ใน views.py และเชื่อม url ใน urls.py

- จึงสร้าง url ของ the-only-list-in-the-world
- เมื่อ test ทั้งหมด จะติด error เพราะยังไม่ได้สร้าง function view_list() ใน views.py
จึงสร้าง view_list() ใน views.py รับ items ทั้งหมดจาก database แล้ว render หน้าเดิม(home.html)
เมื่อ functional test จะติด error 
test สำหรับ user เดียว : หา '2: Use peacock feathers to make a fly' ไม่เจอ
test สำหรับ multi-user :  'Buy peacock feathers' ดันไปรวมอยู่กับ 'Your To-Do list' กลายเป็น 'Your To-Do list\n1: Buy peacock feathers'
- test สำหรับ user เดียวเป็นเพราะไม่เลือก url ที่จะไปไว้เฉพาะเจาะจง ค่าถึงถูก reset จึงเพิ่ม action="/" เข้าไปใน <form> (เดิมผ่านเพราะเรามีหน้าเดียว POST ถึง auto ส่งข้อมูลกลับไปหน้าเดิม)
- เมื่อเรา functional test อีกที error สำหรับ test user เดียวจะหายไป เหลือแต่ error สำหรับ test multi-user
โดยเมื่อเรามี test เยอะขึ้น ทำให้ยากต่อการมอง สามารถใช้คำสั่ง grep -E "class|def" lists/tests.py เพื่อดู class และ function(def) ต่างๆได้และลบอันที่ไม่จำเป็นออกไปได้
- ต่อมาเพิ่ม test_uses_list_template เข้าไปใน class ListViewTest เพื่อ test template สำหรับ function view_list โดยมีชื่อ template ชื่อว่า list.html
- ต่อมาจึงทำการสร้าง list.html
- แล้ว copy ทั้งหมดใน home.html มาใส่
- แล้วจึงไปแก้หน้า home.html ให้ไม่ show data ใดๆอีกต่อไป
- แก้ home_page() ใน views.py ให้ถ้า POST เก็บข้อมูลแล้ว redirect ไปที่ url path '/lists/the-only-list-in-the-world/' เลย
- run unit test จะผ่าน แต่ functional test จะติดไม่เจอ '1: Buy milk' แต่ไปเจอ ['1: Buy peacock feathers', '2: Buy milk'] แทนเพราะ list ของเรายังถูกรวมกันอยู่
ทำการ commit แล้วสร้าง unit test class ใหม่ NewListTest พร้อม 2 test
- test_can_save_a_POST_request ทดสอบ save POST ลงใน database
- test_redirects_after_POST ทดสอบ redirect หลัง POST ว่าไปถูกหน้าหรือไม่
เมื่อ run unit จะโชว์ error ของทั้ง 2 function test ที่เขียนขึ้นมา คือ
1. object count = 0 เพราะยังไม่ได้สร้าง function ใน views.py และใส่ path ใน urls.py สำหรับ '/lists/new'
2. ไม่ได้ redirect '/lists/the-only-list-in-the-world/' เพราะติด 404 page not found เพราะยังไม่ได้สร้าง function ใน views.py และใส่ path ใน urls.py สำหรับ '/lists/new'
ทำการสร้าง function new_list ใน views.py และใส่ path ใน urls.py สำหรับ function
ทำการลบการเอาข้อมูลใส่ data base เพื่อไม่ให้ redundant (ใส่ข้อมูลลง database ทั้งใน home_page() และ new_list() )
แต่เมื่อ functional test ติดของ one user กลับมาเพราะ forms ยังคงชี้ไปที่ URL เก่าอยู่ทั้ง home.html และ lists.html
- จึงแก้ form action ให้ชี้ไปที่ function new_list()
- แต่ functional test ยังคงติด muti-user อันเดิม คือ ไม่เจอ '1: Buy milk' แต่ไปเจอ ['1: Buy peacock feathers', '2: Buy milk'] แทนเพราะ list ของเรายังถูกรวมกันอยู่
จึงทำการปรับ unit test ต่างๆดังนี้ (เพิ่ม list เข้ามาใน ItemModelTest เปลี่ยนชื่อต่างๆให้เหมาะสม)

lists/tests.py
@@ -1,5 +1,5 @@
 from django.test import TestCase
-from lists.models import Item
+from lists.models import Item, List


 class HomePageTest(TestCase):
@@ -44,22 +44,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_)
- ทำการเพิ่ม List เข้ามาใน models.py และ test ไปด้วยคล้าย TDD Chapter 5 จนถึงขั้นตอนดังภาพด้านล่าง(รูปบนสุด)
- unit test จะพบ error message ดังรูป (รูปรองลงมา) เป็นเพราะ Django จะ save string สำหรับ List object ไม่ได้เซฟสำหรับ relationship ระหว่าง object
จึงทำการ update ใน models.py ให้ list เป็น foreinkey ระหว่าง 2 ตาราง

(Foreign key  คือ คีย์นอก  เป็นคีย์ที่เชื่อม Table ที่เกี่ยวข้องหรือมีความสัมพันธ์กัน  เช่น  ใน Table หลักสูตร กำหนดให้รหัสวิชาเป็น Primary Key และทำการเชื่อมโยงไปยัง Table ลงทะเบียนเพื่อต้องการทราบชื่อวิชาและหน่วยกิตที่นักเรียนลงทะเบียน  โดยกำหนดฟิลด์  รหัสวิชา  ใน Table ลงทะเบียนเป็น Foreign Key ในลักษณะความสัมพันธ์ One to Many หมายความว่า รหัสวิชา  1 วิชา สามารถให้นักเรียนลงทะเบียนได้มากกว่า 1 คน  ดังนั้นจึงมีรหัสซ้ำกันได้ใน Table ลงทะเบียน
ต่อมาทำการ makemigration แต่ไม่เป็นไปตามหนังสือ เนื่องจาก django update version ใหม่
จึงทำการเปิด reference ใน website ของ django ดูและ update ตามกรอบแดงในภาพข้างล่างลงไปใน models.py ของเรา
เมื่อแก้แล้วเป็นดังภาพด้านล่างและ makemigration ได้
แต่จะติด test อันเก่าเนื่องจาก foreignkey ทำให้เกิดความสัมพันธ์แบบ one-to-many ระหว่าง 2 ตาราง
จึงทำการแก้ unit test และ function ใน views.py ของเราให้เหมาะสมกับ database
functional test จะติด error เดิม
( AssertionError: '1: Buy milk' not found in ['1: Buy peacock feathers', '2: Buymilk'] )
แต่ลอง unit test จะผ่านทั้งหมด จึงทำการ commit
แล้วจึงทำการปรับ code บางส่วน test_displays_all_items test เปลี่ยนชื่อเป็น test_displays_only_items_for_that_list ให้สื่อความหมาย แล้ว update ให้ตรวจสอบเป็น list ของใครของมัน
แก้ urls.py (ตามหนังสือใช้ url() แต่ computer ผมรันแล้ว error จึงใช้ path repath มาตั้งแต่ต้น)
ใช้ repath() กับ function view_list ให้แสดงแยกว่า list ไหนเป็น list ผ่านตามลำดับ โดยใช้ regular expression ของ django บอก link url
( ข้อมูลเพิ่มเติม regular expression ผมได้ใส่ไว้ใน Django tutorial 3 )
ซึ่งเมื่อ run unit test จะติด error ของ function view_list() เพราะ ไม่ได้ใส่ argument ว่าอยู่ list ไหน
- ทำการแก้โดยใส่ list_id กับตัวแปรรับค่ามาใช้
- เมื่อ unit test จะติด test เก่า ( test_redirects_after_POST() ) เนื่องจาก ยังคงดู url 'the-only-list-in-the-world' อยู่
แก้ test_redirects_after_POST และ new_list ให้ตาม urls.py อันใหม่ที่อัพเดท
จะรัน unit test ผ่าน
แต่ functional test จะติด one user test เพราะ ทุกครั้งที่เราใส่อะไรลงไปจะสร้าง list ใหม่ ยังไม่สามารถเพิ่มค่าลงใน list เดิมได้
update unit test function 2 อันได้แก่ test_can_save_a_POST_request_to_an_existing_list และ test_redirects_to_list_view ก่อนเขียน Add Items ไปยัง List ที่มีอยู่แล้ว
เมื่อ unit test จะได้
ดูจาก wiki list http code ได้ดังนี้


หนังสือบอกเป็นเพราะว่า "we’ve used a very "greedy" regular expression in our URL"
เพราะเมื่อใครก็ตามขอ URL เช่น /lists/1/add_item/  นั้น (.+) ของเราจะหาทั้งประโยคคือ ( 1/add_item/ ) และไปใช้ function view_list() ไม่ใช้ add_item() เพราะ (.+) เป็นตัวอักษรหรืออะไรก็ได้

จึงได้ทำการแก้ให้เป็น numerical digit (ตัวเลข) เท่านั้น
- จึงปรับ urls.py ที่เกี่ยวข้องให้เป็น (.d+) และ ใส่ path ของ add_item()
- สร้าง function add_item()
- run unit test ผ่าน

ต่อมาทำการแก้ action ใน list ให้เรียกใช้ add_item()
update unit test ทดสอบว่า list ที่ถูกส่งไปยัง template ถูก list หรือไม่
เมื่อ run unit test จะได้ KeyError: 'list' เพราะเราไม่ได้ส่ง list ไปยัง template
KeyError: 'list'
- จึงทำการแก้ view_list ให้เก็บค่า list_id ไว้ในตัวแปร list_ และส่งไปยัง template ด้วย
- นำ list_ ที่ได้มา มาชี้ที่ loop
 run test ทั้งหมดจะผ่านทั้งหมด
เหมือนจะเสร็จแล้วแต่ในหนังสือเค้าบอกว่า เอา urls ของ app ไปใส่ไว้ใน urls.py ของ project มันไม่เป็นมืออาชีพ จึงทำการแก้ดังภาพด้านล่าง


ตอนนี้ก็เสร็จ Obey the Testing Goat! "Test-Driven Web Development with Python" ใน Part 1: The Basics of TDD and Django ทั้ง 7 บทเรียบร้อย

Test-Driven Development with Python(Chapter 6)

เนื่องจากเดิมอันเดิมยาวและติดกันเกินไปทำให้ยากต่อการอ่าน
จึงแยก (Week 3-2) - (Week 5-1) Test-Driven Development with Python ออกเป็น 7 part

Chapter 6


เพื่อเปลี่ยนไปใช้ LiveServerTestCase เพื่อจะผ่าน manage.py ของ django
- สร้าง folder functional_tests
- สร้างไฟล์ __init__.py เพื่อให้ django สามารถเข้าไปจัดการได้
- ย้ายไฟล์ functional_tests.py ไปข้างใน folder ที่สร้างขึ้นแล้วเปลี่ยนชื่อเป็น tests.py
- แล้วจึงทำการเช็คสถานะ vcs ของเรา (git)
- ทำการแก้ใน functional test file ของเราให้ใช้ LiveServerTestCase แทน NewVisitorTest
- แก้จาก localhost port 8000 เป็น live_server_url ของ LiveServerTestCase
ตอนนี้ test ของเราเปลี่ยนไปตามรูปด้านล่าง
ทดสอบ test ทั้งหมด
ทดสอบ functional test
ทดสอบ unit test
การรอต่างดังรูปด้านล่าง เค้าบอกว่าที่เราทำมาเป็นรอแบบ voodoo พึ่งศัยศาสตร์ ทำให้อาจเสียเวลาถ้าเรากำหนดเวลารอไม่ใกล้เคียงกับเวลาโหลด หรืออาจ error ถ้าโหลดช้ากว่าเวลารอ

#slide-wait
จึงทำการแก้ implicit wait (timesleep) ทั้งหมด
แก้เป็นใช้ฟังก์ชันเสริมที่สร้างขึ้นมาใหม่ซึ่งรวมกับฟังก์ชันหาภายในตารางไปด้วยในตัว
#1 - สร้างตัวแปร MAX_WAIT ว่ารอสูงสุด 10 วินาที
#2 - ตั้งตัวแปร start_time เท่ากับเวลาขณะนั้นแล้วเข้าลูปทันที
#3 - จากฟังก์ชัน assert หาคำในตารางอันเดิม
#4 - ถ้าเจอตารางแล้วกลับเข้าฟังก์ชันหลักทันที
#5 - ถ้าไม่เจอ error จาก assert รอ 0.5 วินาทีเรื่อยๆ และ จะทำ #6
#6 - ถ้าเวลา start_time - เวลาปัจจุบัน > ตัวแปร MAX_WAIT จะโชว์ error จาก #slide-wait ข้างต้น
แล้วจึงเปลี่ยนฟังก์ชันหาในตารางทั้งหมดมาใช้อันใหม่ wait_for_row_in_list_table()
ลองเทียบเวลา test ทั้งหมดในอันใหม่ กับ functional test แค่อย่างเดียวในอันเก่าไวกว่าประมาณ 0.5 วินาที
ต่อมาลองจงใจ error โดยการ assert คำว่า 'foo' เวลาจะนานเพิ่มขึ้นอย่างที่เรากำหนดหรือไม่ ประกฏว่านานขึ้น
ลองใส่ id ผิดบ้าง ประกฏว่านานขึ้น