วันอาทิตย์ที่ 7 กุมภาพันธ์ พ.ศ. 2559

Test-Driven Development with Python Chapter 1

Install pip3

$ sudo apt-get install python3-pip

Install Selenium

$ sudo pip3 install selenium

Install git

$ sudo apt-get install git



Chapter 1
Getting Django Set Up Using a Functional Test

เขียน Test ก่อนเขียน Code !!

เริ่มต้นเขียน test

สร้างไฟล์ functional_test.py
โดยใส่ code ดังนี้

from selenium import webdriver

browser = webdriver.Firefox()
browser.get('http://localhost:8000')

assert 'Django' in browser.title

แล้วลอง run
$ python3 functional_test.py

จะเปิดหน้า web browser ขึ้นมาที่ http://localhost:8000 และขึ้น assertion error ใน terminal


สร้าง Django Project

$ django-admin.py startproject superlists

จากนั้น เข้าไปใน /superlists ที่เราสร้างขึ้น และ run server

$ python3 manage.py runserver

และเปิด Terminal มาเพื่อลอง run test

$ python3 functional_test.py

คราวนี้ เราจะ test ผ่านแล้ว เย้ !!



วันพุธที่ 3 กุมภาพันธ์ พ.ศ. 2559

Django Exercise Part 2

- Upload CSV File

สร้าง view ชื่อ uploadCSV ขึ้นมา เพื่อเป็นหน้าอัพโหลด โดยทำการเพิ่มเข้าไปใน /polls/views.py


และตั้งค่า URL Config สำหรับ uploadCSV() view ใน /polls/urls.py



และสร้าง template สำหรับหน้า Upload Form ขึ้นมา ชื่อว่า upload_csv.html ไปที่ /polls/templates/polls

ซึ่งหน้า template นำโค้ด HTML Form จาก w3schools มาดัดแปลง




ซึ่งจะสังเกตได้ว่า ผมได้ให้ form action ไปที่ loadcsv ซึ่งเป็น view ที่ใช้สำหรับ load csv แบบ local file เนื่องจากผมคิดว่า มันมีลักษณะการทำงานคล้ายๆกัน ไม่น่าจะต้องแก้มาก

จากนั้นเพิ่มลิ้งจากหน้า index ของ polls app เพื่อให้เข้าไปสู่หน้าอัพโหลดได้


จะได้หน้าตาเป็นแบบนี้





จากนั้น ผมได้ไปลองหาอ่านเกี่ยวกับการที่ Django จัดการกับ File ที่อัพโหลดมาจาก https://docs.djangoproject.com/es/1.9/topics/http/file-uploads/


 จุดสำคัญคือ เวลา Client ส่ง request มา ถ้ามีไฟล์ ข้อมูลจะถูกเก็บไว้ใน Attribute FILES สังเกตได้จาก request.FILES

ผมจึงลอง upload file และให้ print(request.FILES) ออกมา




สังเกตได้ว่า request.FILES จะเป็น Dictionary ที่มี key ว่า csvFile (มาจาก name ใน upload form)
ซึ่งถ้าไม่มีไฟล์ จะเป็น Empty Dictionry ( {} )

ทีนี้ ผมก็สงสัยว่าไฟล์ที่ upload มานั้น ถูกเก็บไว้ที่ไหน ในเว็บก็บอกไว้ว่า


ผมจึงไปหาวิธีเซฟไฟล์มา จนเจอลิ้งจากเว็บเดิมไป https://docs.djangoproject.com/es/1.9/topics/files/


ผมจึง import default_storage จาก from django.core.files.storage และจัดการ ทดสอบ upload file



ผมจึง import os มาเพื่อทำการเปลี่ยนชื่อไฟล์


และให้ส่วนที่อ่านไฟล์ csv อ่านจากชื่อที่กำหนดไว้ ก็จะสามารถทำการ upload คำถามจากไฟล์ csv ได้แล้ว


วันพุธที่ 27 มกราคม พ.ศ. 2559

Django Exercise

Ex.1 - Create homepage at http://localhost:8000 link to admin, index page

สร้าง view สำหรับเป็นหน้า homepage
โดยสร้างไฟล์ views.py ใน /mysite และเขียนโค้ดดังนี้

from django.template import loader
from django.shortcuts import render,get_object_or_404
from django.views import generic

def index(request):
    return render(request, 'homepage/index.html')


สร้าง directory ชื่อ homepage ใน /polls/template
จากนั้นสร้าง template ชื่อ index.html ใน /polls/template/homepage
มีโค้ด html ดังนี้

<html>
<head>
<title>Polls Application</title>
</head>
<body>
Homepage
<br><br>
<a href='/polls/'>Polls Application</a> <br><br>
<a href='/admin/'>Admin Setting</a>
</body>
</html> 


จากนั้น ตั้งค่า URL ให้เมื่อเข้ามาหน้าแรก จะเจอหน้า homepage ที่เราสร้างไว้
โดยแก้ไขในไฟล์ urls.py ใน /mysite

from django.conf.urls import include, url
from django.contrib import admin

from . import views

urlpatterns = [
    url(r'^polls/', include('polls.urls')),
    url(r'^admin/', admin.site.urls),
    url(r'^$', views.index , name='index'),
]


ก็จะได้หน้า homepage เป็นแบบนี้

 
 ปัญหาที่พบ
1. พยายามที่จะทำเป็น generic views โดยสร้าง class IndexView ดังนี้

class IndexView(generic.DetailView):
    template_name = 'homepage/index.html'


และแก้ไข URL เป็นดังนี้

 url(r'^$', views.IndexView.as_view() , name='index'),

แต่ไม่สามารถทำได้ เนื่องจากขึ้น Error ดังนี้

  
2. Template อยู่ในที่ๆไม่น่าจะอยู่

ตอนแรกผมพยายามนำ template ไว้ที่ /templates/homepage และ /mysite/templates/homepage แต่กลับขึ้น Error ว่าหา template ไม่เจอ
ผมจึงแก้ปัญหาด้วยการนำ template ไปไว้ใน /polls/templates/homepage จึงจะสามารถหา template เจอ


Ex.2 - Create a view that shows all questions and results

ผมจะสร้าง view ขึ้นมาใหม่ โดยตั้งชื่อว่า AllResultView

เพิ่มเติมในไฟล์ views.py ใน /polls ดังนี้

class IndexView(generic.ListView):
    template_name = 'polls/allresult.html'
    context_object_name = 'all_question_list'
    def get_queryset(self):
        return Question.objects.all()


จากนั้นผมก็สร้าง template ชื่อ allresult.html ใน /polls/templates/polls/

{% if all_question_list %}
    {% for question in all_question_list %}
        <li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
        {% for choice in question.choice_set.all %}
            {{ choice.choice_text }} - {{ choice.votes }} Votes
            <br>
        {% endfor %}
        <br>
    {% endfor %}
{% else %}
    <p>No polls are available.</p>
{% endif %}


จากนั้นก็ทำการตั้งค่า URL ให้ view นี้
โดยการเพิ่มใน urls.py ใน /polls

url(r'^allresult/', views.AllResultView.as_view(), name='allresult'),

และเนื่องจากผมต้องการให้หน้าแสดงผลลัพธ์ทั้งหมด สามารถลิ้งมาจาก IndexView ของ polls app ได้ ผมจึงไปแก้ไข template ของ IndexView ใน /polls/templates/polls/index.html

{% if latest_question_list %}
    <ul>
    {% for question in latest_question_list %}
        <li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
    {% endfor %}
    </ul>
    <br><br><a href="{% url 'polls:allresult' %}">View All Results</a>
{% else %}
    <p>No polls are available.</p>
{% endif %}






Ex.3 - Load data from csv file (local file, submitted file)

ตอนนี้ผมทำแค่แต่แบบ local file

สร้างไฟล์ csv ขึ้นมาเก็บข้อมูล Question , Choice และ Vote
โดยตั้งชื่อไฟล์ว่า question_data.csv นำไปเก็บไว้ที่ /polls


จากนั้นทำการสร้าง view ที่ไว้จัดการกับ csv file ใน /polls/views.py
โดยตั้งชื่อ view ว่า loadCSV

def loadCSV(request):
    question_list = Question.objects.all()
    for question in question_list:
        choice_list = question.choice_set.all()
        for choice in choice_list:
            choice.delete()
        question.delete()
    with open('polls/question_data.csv',encoding='utf-8') as csvfile:
        dataReader = csv.DictReader(csvfile)
        question = None
        for row in dataReader:
            if row["Question"] != "" and row["Choice"] == "":
                question = Question(question_text = row["Question"] , pub_date=timezone.now())
                question.save()
            elif row["Question"] == "" and row["Choice"] != "":
                question.choice_set.create(choice_text = row["Choice"] , votes = int(row["Vote"]))
    return HttpResponseRedirect(reverse('polls:index'))


จากนั้นทำการเพิ่ม URL ให้ view นี้ โดยแก้ไขไฟล์ urls.py ใน /polls

url(r'^loadcsv/', views.loadCSV, name='loadcsv'),

ผมต้องการให้มีปุ่มกด load CSV ในหน้า admin แต่เนื่องจากผมทำไม่เป็น จึงใช้การจำ URL และใส่ในช่อง address

ก่อน load csv

หลังจาก load csv โดยการใส่ address เป็น http://127.0.0.1:8000/polls/loadcsv


วันอาทิตย์ที่ 24 มกราคม พ.ศ. 2559

Django Tutorial Part 4

ใน part 4 จะเป็นการนำ HTML Form เข้ามาประยุกต์ใน app polls ของเรา

แก้ไฟล์ polls/templates/polls/detail.html

และแก้ไข vote() view ใน polls/views.py

 polls ของเราก็จะมี radio button ให้ vote ได้ จากการแก้ template

 เมื่อกด vote ไปแล้ว จะเข้าไปหน้า result

จากนั้น เราจึงมาแก้ result() view ใน polls/views.py

 จากนั้นก็สร้าง template ให้หน้า result ที่ polls/templates/polls/results.html


  เมื่อแก้แล้ว เมื่อกด vote แล้ว จะขึ้นหน้าแสดงผลโหวตมาแทน


วันพุธที่ 20 มกราคม พ.ศ. 2559

Django Tutorial Part 3

ใน Part 3 จะกล่าวถึงการสร้าง views

view คือ อะไร ?

   view คือ ชนิดของ web page ใน django application ที่มีการทำงานเฉพาะ และมีรูปแบบเฉพาะ ยกตัวอย่างเช่น Blog application จะมี views ดังนี้
- Homepage : ใช้แสดงบทความล่าสุดหลายๆบทความ
- Entry detail page : ใช้แสดงบทความเดียว
- Comment action : ใช้สำหรับ post comment ลงบทความ

สำหรับ Poll application ของเรานั้น จะมีอยู่ 4 views ด้วยกัน
Question index page : แสดงคำถามล่าสุด หลายๆคำถาม
Question detail page : แสดงข้อความของคำถาม มีแบบฟอร์มสำหรับโหวต แต่ไม่ได้แสดงผลลัพธ์
Question result page : แสดงผลลัพธ์ของการโหวต
Vote action : สำหรับควบคุมการโหวต

ใน django นั้น webpage หรือเนื้อหาต่างๆจะถูกส่งมาจาก views แต่ละ view ถูกแทนในรูป python function/method ซึ่ง django จะเลือกใช้งาน view จาก URL ที่ request มา

หลังจากอธิบายเบื้องต้น Tutorial จึงให้เราลองเขียน view อื่นๆเพิ่มขึ้นมา

โดยให้ไปเพิ่ม function ที่ polls/views.py

และเชื่อมต่อ view ที่สร้างขึ้นมาใหม่ กับ url ใน polls/urls.py

แล้วก็ทดสอบเข้าหน้าเว็บที่สร้างขึ้น
(อย่าลืมเปิด server ด้วย $ python3 manage.py runserver)



หลังจากนั้น Tutorial ก็อธิบายเราต่อ

แต่ละ view จะมีการตอบสนอง 1 ใน 2 อย่าง
- return HttpResponse object ที่มีข้อมูลเนื้อหาของหน้าเพจที่ request
- สร้าง exception เช่น Http404

จากนั้น tutorial จะให้เราทำ view ให้ทำอะไรได้มากขึ้น โดยให้ index() view สามารถแสดงผล 5 คำถามล่าสุด โดยแบ่งแต่ละคำถามด้วย comma

และเมื่อลอง runserver ดู ก็สามารถแสดงผลลัพธ์ได้





แต่จากที่เห็น หน้าเพจมีลักษณะเป็นข้อความธรรมดาๆ ถ้าต้องการจะเปลี่ยนรูปแบบหน้าเพจ ต้องแก้ไข python code ใน view
ดังนั้น ใช้ template ของ django น่าจะดีกว่า

สร้าง directory ชื่อ templates ใน polls ซึ่ง django จะหา template ในนี้
และสร้าง subdirectory ชื่อ polls ใน templates ด้วย

สร้างไฟล์ index.html ใน polls/templates/polls/index.html

และอัพเดทให้ view เรียกใช้ templates

เมื่อทดลองเปิดหน้าเว็บจากการ run server

สร้าง 404 Error เมื่อหาไม่มีคำถามนั้นอยู่ในฐานข้อมูล (หา id นอกช่วงของ id ที่มีอยู่)

และสร้าง template ขึ้นมาด้วย


ทดลองดูคำถามที่มี id เกินกว่าที่มีอยู่




Django Tutorial Part 2

หลังจากในคาบ ผมก็ได้กลับมาลองทำตาม Tutorial Part 2 ต่อ

ได้เพิ่ม method __str__ ใน class Question และ Choice
ซึ่งเป็น method ที่ไว้แปลง object เป็น string ของ Python ลักษณะเหมือน method toString ในภาษาอื่น

และได้เพิ่ม method was_published_recently ใน class
ที่เป็น method สำหรับตรวจสอบว่า พึ่ง publish หรือเปล่า (publish มานานไม่เกิน1 วัน)

จากนั้นก็มาลอง python shell ตาม Tutorial ต่อ






พอถึงบรรทัด Question.objects.get(pub_date__year=current_year) ก็เกิด Error ขึ้นมา เพราะเนื่องจากผมทำซ้ำอีกรอบ แล้วทำให้มี object ที่เป็น class Question อยู่ 2 ตัว แล้วเมื่อใช้ method get มา เกิดการ return มา 2 object ซึ่ง method ควร return ค่าเดียว จึงทำให้ Error

แต่ไม่เป็นไร ผมจะข้ามบรรทัดนี้ไป ทำบรรทัดถัดๆไป


ใน part 2 มีการกล่าวถึงหน้า admin และการทำให้หน้า admin สามารถปรับ Question ได้ แต่ผมไม่ได้ทำตรงส่วนนี้