วันเสาร์ที่ 1 กุมภาพันธ์ พ.ศ. 2557

Django Book Chapter 7 : Forms

    จาก Chapter 6 ที่ผ่านไปแล้วนั้นทำให้เราได้รู้ถึงวิธีการใช้ admin กันไปแล้วแต่ในบทนี้เราจะมาศึกษาเกี่ยวกับการใช้ form ใน Django พร้อมกับการนำไปใช้ใน Templates

1.พื้นฐานเกี่ยวกับ request ที่มีอยู่ใน views ของบทที่ 3
def hello(request):
    return HttpResponse("Hello world")
ซึ่ง request นั้นมี method พื้นฐานสำหรับการดึงค่าออกมาให้แล้วดังนี้
  • request.path ใช้สำหรับดึง url ออกมาโดยไม่รวม domain
  • request.get_host() ใช้สำหรับดึง domain ออกมา
  • request.get_full_path() ใช้สำหรับดึง url ออกมาเช่นเดียวกับ request.path แต่จะเพิ่ม query string เข้ามาหากว่ามี
  • request.is_secure() จะให้ค่าออกมาเป็น True กับ False โดยจะเป็น True เมื่อ request นี้เป็น https นอกนั้นเป็น False ทั้งหมด
ตัวอย่างการนำไปใช้
def current_url_view_good(request):
    return HttpResponse("Welcome to the page at %s" % request.path)
สิ่งที่จะ return ไปจะได้เป็น Welcome to the page at ตามด้วย Url ณ ปัจจุบันนั่นเอง

2.โดยนอกจาก method ในข้อแรกนั้น request ยังมี request.META อยู่อีกด้วยซึ่งเป็นข้อมูลแบบ Dictionary โดยมีตัวอย่าง Key ดังนี้
  • HTTP_USER_AGENT ซึ่งจะได้ผลลัพธ์เป็นข้อมูลเกี่ยวกับ Browser ที่เรียก request
  • REMOTE_ADDR จะให้ค่าออกมาเป็น IP Address ของ Client ที่เรียก request
ตัวอย่างการใช้งาน
def ua_display_good1(request):
    try:
        ua = request.META[’HTTP_USER_AGENT’]
    except KeyError:
        ua = ’unknown’
    return HttpResponse("Your browser is %s" % ua)
    เหตุผลที่ต้องใส่ try & except ก็เพราะว่าข้อมูลนี้เป็นแบบ Dictionary เวลาที่ดึงข้อมูลหากไม่มีก็จะเกิด Error KeyError ได้ดังนั้นเลยต้องใส่เงื่อนไขไว้เพื่อรับมือกับปัญหานี้

3.request.GET และ request.POST ทั้งสองอย่างนี้เป็น object ที่คล้ายๆกับ Dictionary ซึ่งทั้งสองนี้จะมีค่าได้โดยรับมาจาก form ผ่านทาง HTML แต่ GET นั้นจะแตกต่างกับ POST ที่ GET จะแสดงข้อมูลบน Url ด้วยแต่ POST จะไม่แสดงข้อมูลใดๆใน Url ด้วยเหตุนี้ทำให้ GET เหมาะกับการใช้ในการดึงข้อมูลออกมาแสดงผล แต่หากเป็นข้อมูลที่ผลต่อการเปลี่ยนใน Database หรือมีผลต่อคำนวณอื่นๆจะเหมาะกับการใช้ POST มากกว่า

4.ทดลองใช้ form ผ่าน Template โดยเริ่มต้นเข้าไปแก้ไขไฟล์ Views ใน books ดังนี้
from django.http import HttpResponse
from django.shortcuts import render
from books.models import Book

def search_form(request):
    return render(request, 'search_form.html')

def search(request):
    if 'q' in request.GET and request.GET['q']:
        q = request.GET['q']
        books = Book.objects.filter(title__icontains=q)
        return render(request, 'search_results.html', {'books': books, 'query': q})
    else:
        return HttpResponse('Please submit a search term.')
    ฟังก์ชั่น search นั้นจะดึงค่าของ q ออกมาจาก request.GET โดยถ้าไม่ใส่ค่าอะไรมาเลยก็จะไม่มี q ใน request แล้วแสดงผลออกทางหน้าจอว่า Please submit a search term. แทนแต่หากเราใส่ค่ามาก็จะแสดงรายชื่อหนังสือออกมาให้เราดูพร้อมบอกคำที่เราใส่ใน form ก่อนหน้านี้

จากนั้นสร้าง Template ขึ้นมาชื่อ search_form.html เก็บไว้ใน Folder Templates ที่สร้างไว้ใน Chapter 3 และมีโค้ดภายในดังนี้
<html>
<head>
    <title>Search</title>
</head>
<body>
    <form action="/search/" method="get">
        <input type="text" name="q">
        <input type="submit" value="Search">
    </form>
</body>
</html>
    โดย action ใน form คือการบอกว่า form นี้เมื่อมีการ submit จะไปยัง Address ไหนหากไม่ใส่ค่าก็จะเป็นการ submit มายัง Address เดิมที่อยู่ ณ ปัจจุบัน ใน method ใช้ GET หรือ POST โดยการใช้นั้นอยู่ในข้อ 3 ที่ได้อธิบายไว้ก่อนหน้านี้ จากนั้นมี input 2 ตัวซึ่งเป็น input text เอาไว้รับตัวอักษรและค่าตัวนี้สามารถดึงค่าออกมาโดยมี name เป็น key และ input submit เป็นปุ่มกดเอาไว้ส่งค่าใน input text ไปยัง GET และ เปลี่ยน Url ไปตาม action

สร้าง Templates อีกตัวชื่อ search_results.html
<p>You searched for: <strong>{{ query }}</strong></p>

{% if books %}
    <p>Found {{ books|length }} book{{ books|pluralize }}.</p>
    <ul>
        {% for book in books %}
        <li>{{ book.title }}</li>
        {% endfor %}
    </ul>
{% else %}
    <p>No books matched your search criteria.</p>
{% endif %}
และเข้าไปแก้ไข Url ใน mysite โดยเพิ่มโค้ดเข้าไปดังนี้ from books import views
และเพิ่ม (r’^search-form/$’, views.search_form), กับ (r’^search/$’, views.search), เข้าไปใน urlpatterns

5.จาก form ในข้อ 4 หากเราไม่ใส่ค่าอะไรก็จะเด้งไปแสดงผลอีกหน้าว่าให้ใส่ค่าแต่เราต้องมานั่งกดย้อนกลับมาหน้าใส่ค่าใหม่ดังนั้นเราจะทำให้หน้าเว็บนี้กลับไปหน้าที่ให้ใส่ค่าใหม่เองและแสดงผล Please submit a search term. เอง โดยแก้ไข Templates search_form.html ดังนี้
<html>
<head>
    <title>Search</title>
</head>
<body>
    {% if error %}
        <p style="color: red;">Please submit a search term.</p>
    {% endif %}
    <form action="/search/" method="get">
        <input type="text" name="q">
        <input type="submit" value="Search">
    </form>
</body>
</html>
และแก้ไขฟังก์ชั่น search ใน views ของ books ดังนี้
def search(request):
    error = False
    if 'q' in request.GET:
        q = request.GET['q']
        if not q:
            error = True
        else:
            books = Book.objects.filter(title__icontains=q)
            return render(request, 'search_results.html', {'books': books, 'query': q})
    return render(request, 'search_form.html', {'error': error})
สึ่งที่แตกต่างจากตอนแรกก็คือหากเช็คแล้วว่า q ไม่ได้ใส่ค่าอะไรมานั้นจะเรียกหน้า search_form.html เองอีกครั้งเพียงแต่ครั้งนี้ error จะส่งค่า True ไปให้ทำหน้าเว็บนั้นแสดงผล Please submit a search term. ออกมาแล้วก็มี form ซึ่งอยู่ในหน้า search_form.html รอให้เรากรอกค่าใหม่ลงไป

6.จากข้อ 5 นั้นหากเราต้องการจะกำหนดเพิ่มว่า q ที่ส่งมานั้นต้องมีความยาวไม่เกิน 20 ตัวหากยาวเกินจะไม่ค้นหาให้ซึ่งส่วนที่ต้องแก้หลักๆก็ยังคงเป็น Templates และ Views เช่นเดิมดังนั้นเปิดไฟล์ Views.py ใน books แล้วแก้ไขตามนี้
def search(request):
    errors = []
    if 'q' in request.GET:
        q = request.GET['q']
        if not q:
            errors.append('Enter a search term.')
        elif len(q) > 20:
            errors.append('Please enter at most 20 characters.')
        else:
            books = Book.objects.filter(title__icontains=q)
            return render(request, 'search_results.html', {'books': books, 'query': q})
    return render(request, 'search_form.html', {'errors': errors})
แล้วทำการแก้ไข Templates search_form.html อีกรอบ
<html>
<head>
    <title>Search</title>
</head>
<body>
    {% if errors %}
        <ul>
            {% for error in errors %}
            <li>{{ error }}</li>
            {% endfor %}
        </ul>
    {% endif %}
    <form action="/search/" method="get">
        <input type="text" name="q">
        <input type="submit" value="Search">
    </form>
</body>
</html>
    โดยในฟังก์ชั่น search รอบนี้นั้นเปลี่ยน error ไปเป็น errors ซึ่งกก็คือการเปลี่ยนจากค่า True False ไปเป็น list ที่เก็บข้อมูลของ error ไว้แทนซึ่งหาก error เข้ากรณีใดก็ตามก็จะนำประโยคที่บอก error ใส่เข้าไปใน list นั่นเองหากเราไม่ใช่ list แบบนี้แต่ยังคงให้เป็น True False แบบเดิมก็ยังสามารถเช็ค error ได้แต่เราจะไม่สามารถระบุข้อความในการแสดง error ได้เลยนั่นเอง โดยวิธีเช็คว่าเกิน 20 ตัวอักษรหรือไม่ก็ใช้ len() ซึ่งเป็น API พื้นฐานของ Python ใช้ระบุความยาวของ object ได้ซึ่งในที่นี้จะได้ความยาวของตัวอักษร q ออกมา ส่วนใน Templates เมื่อเปลี่ยนเป็น list ของ error ดังนั้นเราจึงให้ Templates วนลูปเข้าถึงทุกตัวแปรที่อยู่ใน errors เพื่อปริ้นข้อความของ errors ออกมาให้หมด

7.ทดลองการสร้าง Contact form โดยศึกษาจากโค้ดดังต่อไปนี้
<html>
<head>
    <title>Contact us</title>
</head>
<body>
    <h1>Contact us</h1>

    {% if errors %}
        <ul>
            {% for error in errors %}
            <li>{{ error }}</li>
            {% endfor %}
        </ul>
    {% endif %}

    <form action="/contact/" method="post">
        <p>Subject: <input type="text" name="subject" value="{{ subject }}"></p>
        <p>Your e-mail (optional): <input type="text" name="email" value="{{ email }}"></p>
        <p>Message: <textarea name="message" rows="10" cols="50">**{{ message }}**</textarea></p>
        <input type="submit" value="Submit">
    </form>
</body>
</html>
และมีฟังก์ชั่น contact ดังนี้
def contact(request):
    errors = []
    if request.method == 'POST':
        if not request.POST.get('subject', ''):
            errors.append('Enter a subject.')
        if not request.POST.get('message', ''):
            errors.append('Enter a message.')
        if request.POST.get('email') and '@' not in request.POST['email']:
            errors.append('Enter a valid e-mail address.')
        if not errors:
           send_mail(
               request.POST['subject'],
               request.POST['message'],
               request.POST.get('email', 'noreply@example.com'),
        ['siteowner@example.com'],)
        return HttpResponseRedirect('/contact/thanks/’)
    return render(request, 'contact_form.html', {
        'errors': errors,
        'subject': request.POST.get('subject', ''),
        'message': request.POST.get('message', ''),
        'email': request.POST.get('email', ''),})
    โดยจะมี Form รับ Input แบบ POST มาส่งให้ทางฟังก์ชั่นใช้ซึ่งต้องกรอกค่าให้ครบทุกช่องหากขาดช่องใดช่องหนึ่งจะเกิด error แล้วโหลดหน้า contact ใหม่อีกครั้งแต่หากไม่มี error ก็จะเรียกฟังก์ชั่นส่งเมลไป ในส่วนของ Template นั้น <textarea name="message" rows="10" cols="50"> โดย input ตัวนี้นั้นเป็นกล่องที่ใส่ข้อความโดยสามารถกำหนดขนาดได้เองจาก rows และ cols

8.นอกจาก Django จะสามารถใช้งาน Form ในแบบทั่วๆไปแล้วนั้น Django เองก็สามารถใช้ Form ในรูปแบบของ Class ได้อีกด้วยดังนั้นเริ่มต้นด้วยการสร้างไฟล์ forms.py ใน โฟลเดอร์ books และมีโค้ด ดังนี้
from django import forms

class ContactForm(forms.Form):
    subject = forms.CharField()
    email = forms.EmailField(required=False)
    message = forms.CharField()
    โดยใน Code นี้จะเป็นการเอา API forms จาก Django มาใช้ซึ่งมีมาให้อยู่แล้ว และสร้าง Class ContactForm โดยสืบทอดมาจาก Class Form ใน forms และมีตัวแปรรับค่า 3 ตัวคือ subject, email, message โดย email จะมี required=False ซึ่งเป็นการระบุว่าตัวแปรนี้ไม่จำเป็นต้องใส่มาให้ก็ได้เพราะโดยปกติแล้วนั้นใน Class forms ของ Django จะตั้งค่าพื้นฐานให้ทุกตัวแปรต้องใส่ค่ามาให้ถึงจะไม่เกิด error ขึ้นเมื่อใช้งานเพื่อรับข้อมูลจากหน้าเว็บซึ่ง required=False มีไว้เพื่อให้สามารถตั้งค่าได้ตามต้องการ

9.ทดสอบ ContactForm() ใน forms.py ผ่าน shell ของ Django


    จากการ print ค่าของ ContactForm() ออกมาจะเห็นว่าจะได้ออกมาเป็น Code HTML ในรูปของตารางนั่นเองซึ่งโดยพื้นฐานแล้ว Django จะใช้รูปแบบของตารางเป็นค่าเริ่มต้น แต่ก็มีรูปแบบอื่นให้เลือกใช้อีกเช่น <ul>, <p>


    จากนั้นจากใน Class ที่เราสร้างไว้และได้กล่าวถึง required=False เราจะมาทดสอบกันว่ามันมีผลอย่างไรบ้าง


    แบบแรก (In [6]) เราสร้าง Object ContactForm โดยกำหนดค่าให้ครบทุกตัวแล้วตรวจสอบโดยใช้ method is_valid() ซึ่งได้ผลออกมาว่าเป็น True แสดงว่า Form เมื่อรับค่านี้ทำงานได้ปกติไม่มีปัญหา จากนั้นในแบบที่สอง (In [8]) ทดลองตัด email ออกไปแล้วทดสอบด้วน is_valid() เหมือนเดิมก็ยังได้ True เพราะ required=False ที่เรากำหนดไว้ดังนั้นเมื่อไม่มีค่าอะไร Form นี้ก็ยังคงไม่มีปัญหาใดๆเกิดขึ้น ในแบบที่สาม (In [10]) รอบนี้เหลือไว้เพียง subject ตัวเดียวแล้วทดสอบโดย is_valid() เช่นเดิมผลที่ได้จะเป็น False ก็เพราะว่าเราไม่ได้ใส่ค่า message เข้าไปด้วยสังเกตุได้ว่าจากใน In [12] เราใช้ f.errors จะได้เหตุผลที่เกิด error ขึ้นซึ่งผลที่ได้คือ {'message': [u'This field is required.']} และเหตุผลก็เพราะใน Class ContactForm ตัวแปร message ของเราไม่ได้ใส่ required=False ไว้นั่นเอง

10.นำ ContactForm() ที่เราสร้างไว้มาใช้ใน contact(request) โดยแก้ไขฟังก์ชั่น contact(request) และ Template contact_form.html ดังนี้
from books.forms import ContactForm
def contact(request):
    if request.method == 'POST':
        form = ContactForm(request.POST)
        if form.is_valid():
            cd = form.cleaned_data
            send_mail(cd['subject'], cd['message'], cd.get('email', 'noreply@example.com'), ['siteowner@example.com'],)
            return HttpResponseRedirect('/contact/thanks/')
    else:
        form = ContactForm()
    return render(request, 'contact_form.html', {'form': form})
Template contact_form.html แก้ไขดังนี้
<html>
<head>
    <title>Contact us</title>
</head>
<body>
    <h1>Contact us</h1>
    {% if form.errors %}
        <p style="color: red;">
            Please correct the error{{ form.errors|pluralize }} below.
        </p>
    {% endif %}
    <form action="" method="post">
        {% csrf_token %}
        <table>
            {{ form.as_table }}
        </table>
        <input type="submit" value="Submit">
    </form>
</body>
</html>
    จาก Templates ที่แก้ไขนั้นเราจะนำ form ที่สร้างขึ้นจาก ContactForm() มาใส่ในช่วง {{ form.as_table }} และหากมี error ก็จะดึงค่าจาก form.errors มาแสดงผลเช่นเดียวกันจากตรงนี้หน้า Contact ของเราจะทำงานได้เหมือนก่อนหน้านี้แต่ว่าส่วนของ message นั้นจะกลายเป็น input text ธรรมดา และ จำนวนโค้ดที่ใช้นั้นจะสั้นลงอย่างมากถือเป็นข้อดีในการใช้ Class ContactForm เข้าช่วย นอกจากนี้ในฟังก์ชั่น contact ใน views ของเราเองก็ไม่ต้องนั่งเขียนส่วนที่ใช้ตรวจสอบ error ของ form ที่ส่งมาเองอีกด้วย

11.จากข้อ 10 message นั้นถูกเปลี่ยนไปเป็น input text ธรรมดาแต่หากเราต้องการให้เป็น input textarea แบบเดิมก็สามารถทำได้โดยเข้าไปแก้ไข Class ContactForm ดังนี้
class ContactForm(forms.Form):
    subject = forms.CharField(max_length=100)
    email = forms.EmailField(required=False)
    message = forms.CharField(widget=forms.Textarea)
    โดยการเพิ่ม widget=forms.Textarea เข้าไปใน message ทำให้เมื่อเรา render templates ขึ้นมาใช้ message ก็กลับไปเป็น input textarea เช่นเดิม

12.นอกจากเราจะกำหนด input แบบที่เราต้องการได้แล้วเราก็ยังสามารถกำหนดจำนวนตัวอักษรสูงสุดและกำหนด Label ของ input ได้อีกเช่นกันโดยวิธีการคือการเพิ่ม max_length=100 เข้าไปใน subject และเพิ่ม label='Your e-mail address' เข้าไปใน email ของ Class ContactForm จะได้รูปแบบดังนี้
class ContactForm(forms.Form):
    subject = forms.CharField(max_length=100)
    email = forms.EmailField(required=False, label='Your e-mail address')
    message = forms.CharField(widget=forms.Textarea)
13.นอกจากจะกำหนด label, input, ความยาวตัวอักษรสูงสุด ได้เองเรายังสามารถใส่ค่าเริ่มให้ form ของเราได้อีกด้วยโดยแก้ไขในฟังก์ชั่น contact ดังนี้
def contact(request):
    if request.method == 'POST':
        form = ContactForm(request.POST)
        if form.is_valid():
            cd = form.cleaned_data
            send_mail(cd['subject'], cd['message'], cd.get('email', 'noreply@example.com'), ['siteowner@example.com'],)
            return HttpResponseRedirect('/contact/thanks/')
    else:
        form = ContactForm(initial={'subject': 'I love your site!'})
    return render(request, 'contact_form.html', {'form': form})
    จาก initial={'subject': 'I love your site!'} คือการกำหนดค่าเริ่มต้นให้กับ subject นั่นเองหากเราอยากกำหนดให้ email และ message ก็ได้เช่นกันโดยใส่ค่าเข้าไปใน initial นั่นเอง

14.การใช้ Class ContactForm ก็ช่วยให้เราลดโค้ดในฟังก์ชั่น contact ลงไปได้แต่เราไม่สามารถกำหนดเงื่อนไขในเช็คตัวแปรใดๆได้เอง ทาง Django จะเป็นตัวจัดการให้แต่หากเราต้องการเพิ่มเงื่อนไขเองก็สามารถทำได้เช่นกันโดยการเพิ่มฟังก์ชั่นนี้เข้าไปใน Class ContactForm
def clean_message(self):
    message = self.cleaned_data[’message’]
    num_words = len(message.split())
    if num_words < 4:
        raise forms.ValidationError("Not enough words!")
    return message
    ซึ่ง Django จะเรียกฟังก์ชั่น clean_message เองเมื่อตอนที่เช็คว่า message นั้นถูกต้องหรือไม่ โดยหลักการก็คือให้เราตั้งชื่อฟังก์ชั่นว่า clean_ ตามด้วยชื่อตัวแปรที่ต้องการตรวจสอบ Django ก็จะรู้เองว่าฟังก์ชั่นนี้มีไว้เพื่อเช็คตัวแปรไหน โดยฟังก์ชั่นที่เขียนเพิ่มมานี้จะทำการเช็คว่า message ที่ส่งมานั้นมีจำนวนคำน้อยกว่า 4 ตัวหรือไม่ หากน้อยกว่า 4 คำก็จะ error แล้วส่ง exception ไปว่า Not enough words! เพื่อไปแสดงผลในหน้าเว็บนั่นเอง

15.จากข้อ 10 ที่เรานำ class ContactForm มาใช้ครั้งแรกนั้นเราให้ Django สร้างรูปแบบของ Form เองทั้งหมดแต่หากเราต้องการจัดรูปแบบเองอย่างละเอียด เราก็สามารถทำได้เช่นกันโดยการแก้ไข Templates ดังตัวอย่าง
<form action="" method="post">
    {% csrf_token %}
    <div class="field">
        {{ form.subject.errors }}
        <label for="id_subject">Subject:</label>
        {{ form.subject }}
    </div>
    <div class="field">
        {{ form.email.errors }}
        <label for="id_email">Your e-mail address:</label>
        {{ form.email }}
    </div>
    <div class="field">
        {{ form.message.errors }}
        <label for="id_message">Message:</label>
        {{ form.message }}
    </div>
    <input type="submit" value="Submit">
</form>
    จากตัวอย่างจะเห็นว่าเราสามารถเข้าถึง form ของแต่ละตัวตัวแปรได้โดยตรงเลยเช่น {{ form.subject }} หรือถ้าหากต้องการดู error ของตัวไหนก็ทำได้เช่น {{ form.subject.errors }} ดังนั้นเมื่อเราเข้าถึงตัว form และ error แบบนี้ได้นั้นเราก็สามารถจัดรูปแบบของหน้าเว็บเองได้เช่นกันไม่จำเป็นต้องใช้ตามรูปแบบที่ Django จัดมาให้

    จาก Chapter 7 นี้เราได้เรียนรู้เกี่ยวกับการใช้งาน Form ใน Django กันไปแล้วโดยใช้ Form ทั้งในรูปแบบของ Class หรือสร้างไว้ใน Template เองก็ได้ซึ่งก็แล้วแต่ความถนัดและความชอบของแต่ละคนว่าใช้จะใช้แบบไหนดีเพราะถ้าใช้ใน Class เราไม่ต้องกังวลเรื่องการเช็คว่าค่าที่ส่งมาใน Form จะถูกต้องไหมเพราะ Django เองก็จะตรวจสอบให้ในเบื้องต้น และ เรายังสามารถกำหนดเงื่อนไขเพิ่มเองได้อีกด้วย แต่การจัดรูปแบบใน Template ก็จะยุ่งยากกว่าแบบไม่ใช่ Class ส่วนรูปแบบที่ไม่ใช่ Class ก็มีข้อเสียคือเราต้องจัดการตรวจเช็คทุกอย่างจาก Form เองอีกทั้งโค้ดที่ต้องเขียนนั้นยังมีความยาวมากกว่าอีกด้วย
Read more ...

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

Django Book Chapter 6 : The Django Admin Site

    จาก Chapter 5 นั้นหากเราต้องการจะแก้ไขข้อมูลใน Database เราต้องเข้าไปผ่าน shell ซึ่งยุ่งยากและต้องจำคำสั่งดังนั้นเพื่อให้ง่ายต่อการเพิ่ม ลบ แก้ไข ข้อมูลนั้นหน้า Admin จึงเกิดขึ้นในบทนี้จะกล่าวถึงการจัดการและการใช้หน้า Admin

1.ใน Django เวอร์ชั่น 1.6 นั้นหน้า admin ถูกเปิดไว้อยู่แล้วเป็นค่าพื้นฐานดังนั้นจึงไม่ต้องแก้ไขอะไรเพื่อให้สามารถใช้งานหน้า admin เลยทันทีดังนั้นพิมคำสั่ง python manage.py runserver เพื่อเปิด Server จากนั้น เข้าไปที่หน้า http://127.0.0.1:8000/admin/ ผ่าน Browser ของเรา

2.เมื่อเข้าไปแล้วจะเจอหน้าให้ Log in ให้เราใส่ Username และ Password ที่เราได้ตั้งไว้ต้องสร้าง Superuser ใน Chapter 5 ที่ผ่านมาแต่ถ้าหากใครที่ใน Chapter 5 ยังไม่ได้สร้าง Superuser นั้นให้พิมคำสั่ง python manage.py createsuperuser ใน Terminal เพื่อสร้าง Superuser แล้ว log in เข้าระบบ admin ไป

ภาพตัวอย่างหน้า http://127.0.0.1:8000/admin/


ภาพตัวอย่างเมื่อ Log in เข้าไปแล้วด้วย Superuser


3.จะเห็นว่าเมื่อ Log in เข้าไปแล้วเราสามารถเข้าไปแก้ไขข้อมูลของ User เพิ่มลบ ได้ตามต้องการในหัวข้อ Users ทดลองเล่นได้ครับและยังมี History ให้เราดูอีกด้วยว่ามีการแก้ไขอะไรเกี่ยวกับ User ไหนไปบ้าง เมื่อวันไหนแก้ไขอะไร



4.ทำการเพิ่ม Models ของเราที่สร้างไว้ใน Chapter 5 มาใส่ใน admin เพื่อให้เราสามารถแก้ไขได้โดยเปิดไฟล์ books/admin.py แล้วแก้ไขดังนี้
from django.contrib import admin
from books.models import Publisher, Author, Book

admin.site.register(Publisher)
admin.site.register(Author)
admin.site.register(Book)
แล้วลองรีเฟรชหน้า admin ใหม่จะเห็น Class ใน models ของเราปรากฎอยู่ในหน้า admin แล้ว


    การทำงานของ admin เบื้องต้นนั้นเมื่อเราเริ่ม runserver ตัว Django จะอ่านค่าจากใน Urls.py ซึ่งเมื่อเจอ admin.autodiscover() เป็นบรรทัดที่บอกให้ admin เริ่มทำงานดังนั้นก็จะไปอ่านค่าจากใน INSTALLED_APPS สำหรับ Setting ของ admin และจากนั้นจึงไปหาไฟล์ admin.py ซึ่งเมื่อเจอก็จะโหลดโค้ดภายในไฟล์ไปใส่ในเว็บ admin ด้วยดังนั้นเมื่อเราแก้ไขเพื่อเพิ่ม models ใน admin.py จึงทำให้เราเห็น models ทั้ง 3 มาโผล่อยู่ในเว็บ admin นั่นเอง

5.เพิ่มทางเลือกให้กับ Author ใน admin โดยปกตินั้นหากเราเข้าไปเพิ่ม Author ใน admin นั้นเราจำเป็นต้องกรอก ข้อมูลทั้งหมด 3 อย่างคือ First name, Last name, E-mail ซึ่ง E-mail นั้นบางคนก็ไม่ได้ระบุมาให้ในหนังสือ ซึ่งถ้าเราไม่ใส่ E-mail เราก็ Save ข้อมูลลงใน Database ไม่ได้ดังนั้นเพื่อให้เราไม่จำเป็นต้องใส่ E-mail ก็สามารถใส่ค่าแล้ว Save ลงใน Database ได้ให้เข้าไปแก้ models.py ใน books อีกครั้งจากเดิม Author เรามีหน้าตาประมาณนี้
class Author(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=40)
    email = models.EmailField()
ให้เราแก้ไขในส่วนของ email ให้เป็นดังนี้
class Author(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=40)
    email = models.EmailField(blank=True)
จากที่แก้ไปเมื่อสักครู่นั้นเป็นการบอกว่าหากจะสร้าง Object ของ Author นั้นในส่วนของ email นั้นหากไม่ใส่ค่ามาก็สามารถสร้าง Object ได้นั่นเอง

6.จากข้อ 5 การใส่ blank=True สามารถใส่ได้ในทุกๆ Field เพื่อให้ Admin ไม่ต้องถูกบังคับว่าต้องใส่ค่าถึงจะเพิ่มข้อมูลได้แต่ก็มี Field บางแบบที่ใส่ไม่ได้เช่น DateField, TimeField,
DateTimeField, IntegerField, DecimalField, FloatField เป็นต้น ซึ่งเมื่อเราไม่ใส่ค่าลงใน Field ใดๆก็ตามหากเราปล่อยว่างไว้ Field เหล่านั้นจะถูกกำหนดค่าให้เป็น Empty String เองโดยอัตโนมัติ แต่ Field ในตัวอย่างเบื้องต้นนั้นไม่สามารถใส่ได้เพราะมี Data Type ที่ถูกระบุไว้อยู่แล้วเช่น IntegerField ถูกระบุว่าต้องใส่ int ไว้อยู่แล้วเมื่อใส่ Empty String เข้าจึงเกิด Error ขึ้น วิธีการแก้ก็คือนอกจากเราจะใส่ blank เข้าไปแล้วยังมีอีกตัวที่ต้องใส่เข้าไปด้วยคือ null เพื่อให้เมื่อไม่ใส่ค่าก็จะให้ตัวแปรนั้นเป็น null ไปโดยจะแก้ไขใน publication_date ของ Book ดังนี้
class Book(models.Model):
    title = models.CharField(max_length=100)
    authors = models.ManyToManyField(Author)
    publisher = models.ForeignKey(Publisher)
    publication_date = models.DateField(blank=True, null=True)
โดยเมื่อแก้ไขเสร็จแล้วต้อง Update Table ใน Database ใหม่ด้วย

7.เมื่อเราเข้าไปเพิ่ม Book, Author, Publisher นั้นจะมีช่องให้กรอกข้อมูลต่างๆ ซึ่งชื่อที่ใช้ระบุว่าช่องที่กรอกคือข้อมูลของอะไรจะกำหนดมาให้โดยอัตโนมัติจากชื่อตัวแปรที่เราตั้งไว้ใน models.py แต่ถ้าหากเราต้องการตั้งเองโดยอิสระก็สามารถทำได้โดยการเพิ่ม verbose_name เข้าไปดังนี้
class Author(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=40)
    email = models.EmailField(blank=True, verbose_name='e-mail')
ภาพตัวอย่างก่อนเพิ่ม verbose_name


ภาพตัวอย่างหลังเพิ่ม verbose_name


8.หากเรามีข้อมูลใน Database แล้วนั้นจะพบว่าการแสดงชื่อของข้อมูลนั้นจะแสดงตาม __unicode__ ที่เราเขียนไว้ใน models ดังนั้นเพื่อความสวยงามและให้ง่ายต่อการอ่านข้อมูล เราสามารถตั้งค่าได้เองโดยเพิ่ม Class เข้าไปใน admin.py และแก้ไขดังนี้
from django.contrib import admin
from books.models import Publisher, Author, Book

class AuthorAdmin(admin.ModelAdmin):
    list_display = ('first_name', 'last_name', 'email')
    search_fields = ('first_name', 'last_name')

class BookAdmin(admin.ModelAdmin):
    list_display = ('title', 'publisher', 'publication_date')
    list_filter = ('publication_date',)
    date_hierarchy = 'publication_date'
    ordering = ('-publication_date',)

admin.site.register(Publisher)
admin.site.register(Author, AuthorAdmin)
admin.site.register(Book, BookAdmin)
    โดย list_display เป็นการแบ่งช่องข้อมูลที่จะแสดงออกมาตามลำดับใน tuple นั้นจากตัวอย่าง first_name จะอยู่ซ้ายสุดตามด้วย last_name และ email ส่วน search_fields จะเป็นการเพิ่มช่อง search โดยข้อความที่พิมไปในช่องจะค้นหาจากข้อมูลที่ระบุไว้ใน tuple เท่านั้น list_filter คือการเพิ่ม filter เข้าไปในหน้าแอดมินช่วยให้สามารถ filter ข้อมูลได้ง่ายขึ้นโดยข้อมูลที่ใช้ในการ filter มาจาก publication_date ของ Book ทุกเล่ม date_hierarchy จะเพิ่มเมนูขึ้นมาให้กดเลือกซึ่งเป็น filter อีกเช่นกันแต่จะเป็น filter จากวันที่เท่านั้น ordering จะเป็นการกำหนดการเรียงลำดับข้อมูลที่แสดงผลมาให้ทันทีโดยในตัวอย่างจะเรียงจากวันที่เก่ากว่าขึ้นก่อน

ภาพตัวอย่างหน้า admin ของ Author


ภาพตัวอย่างหน้า admin ของ Book


9.นอกจากเราจะสามารถจัดรูปแบบการแสดงผลในหน้า admin ได้เองแล้วเรายังสามารถจัดรูปแบบตอนแก้ไขข้อมูลหรือเพิ่มข้อมูลได้อีกด้วยเข้าไปแก้ในไฟล์ admin.py ดังนี้
class BookAdmin(admin.ModelAdmin):
    list_display = (’title’, ’publisher’, ’publication_date’)
    list_filter = (’publication_date’,)
    date_hierarchy = ’publication_date’
    ordering = (’-publication_date’,)
    filter_horizontal = (’authors’,)
    raw_id_fields = (’publisher’,)
    โดย filter_horizontal เป็นการเปลี่ยนรูปแบบการเลือกให้กลายเป็นหน้าต่าง 2 ฝั่งเป็นฝั่งที่เลือกกับไม่ได้เลือก ซึ่งเดิมๆหากเราต้องการจะเลือก authors หลายๆคนต้องกด Control ค้างแล้วคลิ๊กเพิ่ม แต่ในแบบนี้เราแค่ดับเบิ้ลคลิ๊กชื่อ ก็จะถูกเลือกทันทีเพิ่มความสะดวกมากขึ้น ส่วน raw_id_fields เป็นการเปลี่ยนจากเดิมจะให้เราเลือกโดยใช้ชื่อเป็นตัวกำหนดแต่เมื่อกำหนดตัวนี้ขึ้นมานั้นจะใช้ id เป็นตัวเลือกแทนแต่เราก็สามารถกดดูชื่อเต็มๆได้เช่นเดิม


    จาก Chapter 6 นั้นหน้า Admin นั้นมีประโยชน์ในการช่วยจัดการกับ Database ของเราผ่านหน้าเว็บซึ่งง่ายกว่าการพิมคำสั่งผ่าน Shell อย่างแน่นอนและนอกจากจะช่วยให้จัดการง่ายขึ้นเรายังสามารถปรับเปลี่ยนรูปแบบการแสดงผลหรือการแก้ไขข้อมูลได้ตามใจเราต้องการอีกด้วย
Read more ...

วันอังคารที่ 28 มกราคม พ.ศ. 2557

Django Book Chapter 5 : Models

    จาก Chapter 4 เราได้เรียนรู้เกี่ยวกับใช้ Templates ใน Django ไปแล้วดังนั้นในบทที่ 5 จะเริ่มเข้าสู่การพัฒนา Web Application โดยใช้ Models และหากมีการใช้ Database เราก็สามารถจัดการ Database ในรูปแบบของ Views ที่เราทำมาใน Chapter 4 ก็สามารถทำได้แต่จะมีความยุ่งยากซับซ้อนในการเขียนมาก ดังนั้นเพื่อให้ง่ายต่อการพัฒนาเราจึงใช้ Models ของ Django มาช่วย

1.รูปแบบของ Database ที่เลือกใช้นั้นจะเป็นของ sqlite3 ซึ่งมีอยู่แล้วใน Django จึงไม่ต้องทำการแก้ไขหรือตั้งค่าอะไรเพิ่มเติม แต่หากอยากใช้ Database ตัวอื่นศึกษาเพิ่มเติมได้ที่นี่ คลิก

2.เริ่มสร้าง Model โดยการพิม python manage.py startapp ชื่อแอพ ใน Terminal ในตัวอย่างจะใช้ books
python manage.py startapp books
ซึ่งจะได้โฟลเดอร์ใหม่ขึ้นมาชื่อ books และมีโครงสร้างภายในดังนี้
books/
    admin.py
    __init__.py
    models.py
    tests.py
    views.py
3.เปิดไฟล์ models.py แล้วแก้ไขไฟล์ดังนี้
from django.db import models

class Publisher(models.Model):
    name = models.CharField(max_length=30)
    address = models.CharField(max_length=50)
    city = models.CharField(max_length=60)
    state_province = models.CharField(max_length=30)
    country = models.CharField(max_length=50)
    website = models.URLField()

class Author(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=40)
    email = models.EmailField()
class Book(models.Model):
    title = models.CharField(max_length=100)
    authors = models.ManyToManyField(Author)
    publisher = models.ForeignKey(Publisher)
    publication_date = models.DateField()
    จากโค้ดที่แก้ไขนั้นได้ import models จาก django.db มาซึ่งเป็น API พื้นฐานที่มากับ Django เป็นโครงร่างของ Class Models ที่ให้เรานำมาใช้งานได้ซึ่งในตัวอย่างนี้แบ่งออกเป็นหลักๆ 3 Class ใหญ่คือ Publisher, Author, Book

    ซึ่งใน Publisher จะเป็น Class ที่เก็บ name, address, city, state_province, country ซึ่งเก็บข้อมูลในรูปแบบของ CharField ซึ่งสามารถเก็บได้สูงสุด 30, 50, 60, 30, 50 ตัวอักษรตามลำดับและ website ซึ่งเป็นรูปแบบ URLField เอาไว้เก็บ URL โดยเฉพาะซึ่งคุณสมบัติก็คล้ายๆกับ CharField แต่หากเราไม่กำหนด max_length เองตัว URLField จะกำหนดให้เองไว้ที่ 200 ตัวอักษรทำให้ถูกลิมิตจำนวนตัวอักษรไว้ที่ 200 ตัวโดยอัตโนมัติ ซึ่งจะแตกต่างจาก CharField หาก CharField ไม่กำหนดค่า max_length นั้นก็จะใส่ตัวอักษรได้ไม่จำกัดนั่นเอง และนอกจากนี้ URLField ยังสามารถตรวจสอบได้ว่าค่าที่ใส่มาในช่องนั้นเป็น URL หรือไม่โดยหากไม่มี http:// นำหน้าชื่อเว็บแล้วก็จะถือว่าค่าที่กรอกนั้นผิดและต้องกรอกให้ถูกจึงจะบันทึกค่าลงใน Database ได้

    ในส่วนของ Author เป็น Class ที่เก็บ first_name, last_name ซึ่งจะเก็บข้อมูลในรูปแบบ CharField ที่มีความยาว 30, 40 ตามลำดับ และมี email ซึ่งเก็บข้อมูลแบบ EmailField ซึ่งคล้ายๆกับ CharField แต่จะถูกลิมิตจำนวนตัวอักษรไว้ที่ 75 ตัวอักษรหากเราไม่กำหนด max_length เองและยังสามารถตรวจสอบได้ว่าค่าที่เรากรอกนั้นเป็น e-mail หรือไม่หากไม่ใช่ก็ต้องกรอกใหม่อีกเช่นกัน

    สุดท้าย Book เป็น Class ที่เก็บข้อมูล title ซึ่งเป็น CharField ที่กำหนดความยาวไว้ที่ 100 ตัวอักษร authors เก็บข้อมูลแบบ ManyToManyField ซึ่งเป็น Object ซึ่งเก็บข้อมูลของ Object จาก Author ได้หลายๆ Object เหตุผลเพราะหนังสือเล่นนึงสามารถมีผู้แต่งได้หลายคนดังนั้นจึงต้องมีการเก็บข้อมูลที่สามารถอ้างอิงถึง Author ได้หลายๆคน publisher เก็บข้อมูลแบบ ForeignKey ซึ่งจะเป็นการกำหนด Object ที่อ้างอิงถึงจาก Class Publisher โดยมีได้เพียงแค่ 1 ตัวเท่านั้น publication_date เก็บข้อมูลแบบ DateField ซึ่งเป็น Field ที่เก็บข้อมูลของวันที่เท่านั้นซึ่งเก็บในรูปแบบของ ปี-เดือน-วันที่ โดยปีที่ใช้เป็นแบบ ค.ศ. นั่นเอง

4.จากนั้นกลับไปแก้ไขไฟล์ settings.py ที่อยู่ในโฟลเดอร์ mysite โดยเข้าไปเพิ่ม 'books', ในส่วนของ INSTALLED_APPS จะได้รูปแบบดังนี้
INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'books',
)
5.จากนั้นลองพิม python manage.py validate ใน Terminal หากเราทำทุกอย่างถูกต้องได้ Output กลับมาเป็น 0 errors found แต่หากมี error ให้ลองเช็คใน models.py ว่ามีส่วนไหนที่ผิดหรือไม่จากนั้นแก้ไขจนเสร็จแล้วลองพิมคำสั่งอีกครั้งหากยังมี error ก็ให้แก้ไปเรื่อยๆจนกว่าจะได้เป็น 0 errors found

    ซึ่ง validate เป็นการเช็คว่า models ที่เราเขียนขึ้นมานั้นมีส่วนไหนที่ syntax และ logic ผิดหรือไม่หากไม่ผิดก็จะได้ 0 errors found แต่หากมีที่ผิดก็จะฟ้อง error ขึ้นมาซึ่งเป็นตัวช่วยในการตรวจสอบว่า models ที่เราเขียนนั้นมีความถูกต้องมากน้อยเพียงใดนั่นเอง

6.จากนั้นพิม python manage.py sqlall books เพื่อให้ Django สร้างแบบฟอร์มของ Table สำหรับการใช้ในการสร้าง Database ขึ้นมาซึ่งจะได้หน้าตาประมาณนี้
BEGIN;
CREATE TABLE "books_publisher" (
    "id" integer NOT NULL PRIMARY KEY,
    "name" varchar(30) NOT NULL,
    "address" varchar(50) NOT NULL,
    "city" varchar(60) NOT NULL,
    "state_province" varchar(30) NOT NULL,
    "country" varchar(50) NOT NULL,
    "website" varchar(200) NOT NULL
)
;
CREATE TABLE "books_author" (
    "id" integer NOT NULL PRIMARY KEY,
    "first_name" varchar(30) NOT NULL,
    "last_name" varchar(40) NOT NULL,
    "email" varchar(75) NOT NULL
)
;
CREATE TABLE "books_book_authors" (
    "id" integer NOT NULL PRIMARY KEY,
    "book_id" integer NOT NULL,
    "author_id" integer NOT NULL REFERENCES "books_author" ("id"),
    UNIQUE ("book_id", "author_id")
)
;
CREATE TABLE "books_book" (
    "id" integer NOT NULL PRIMARY KEY,
    "title" varchar(100) NOT NULL,
    "publisher_id" integer NOT NULL REFERENCES "books_publisher" ("id"),
    "publication_date" date NOT NULL
)
;
CREATE INDEX "books_book_authors_id" ON "books_book_authors" ("book_id");
CREATE INDEX "books_book_authors_id" ON "books_book_authors" ("author_id");
CREATE INDEX "books_book_id" ON "books_book" ("publisher_id");

COMMIT;  
ซึ่งการใช้ sqlall books ยังไม่ใช่การสร้าง Database แต่เป็นเพียงการสร้างฟอร์มซึ่งเราสามารถนำโค้ดข้างบนไปใส่ใน Database เองก็ได้หรือจะทำตามข้อต่อไปนี้

7.พิม python manage.py syncdb ใน Terminal เพื่อให้ Django สร้างไฟล์ Database ออกมาให้เราเอง

7.1 เมื่อพิมไปแล้วระหว่างนั้นจะมีการถามให้เราสร้าง Super user ไหมให้เราตกลงจากนั้นใส่ username, password, e-mail ของตนเองเข้าไป

8.ทดลองการสร้างข้อมูลใส่ใน Database ผ่าน shell
>>> from books.models import Publisher
>>> p1 = Publisher(name=’Apress’, address=’2855 Telegraph Avenue’,
...            city=’Berkeley’, state_province=’CA’, country=’U.S.A.’,
...            website=’http://www.apress.com/’)
>>> p1.save()
>>> p2 = Publisher(name="O’Reilly", address=’10 Fawcett St.’,
...            city=’Cambridge’, state_province=’MA’, country=’U.S.A.’,
...            website=’http://www.oreilly.com/’)
>>> p2.save()
>>> publisher_list = Publisher.objects.all()
>>> publisher_list
[<Publisher: Publisher object>, <Publisher: Publisher object>]
จากตัวอย่างเป็นการสร้าง Object จาก Publisher โดยหากเราไม่เรียกคำสั่ง p1.save() และ p2.save() เมื่อเราปิด Shell ไปนั้นใน Database ของเราก็จะยังไม่มี p1 และ p2 อยู่ในนั้นถือเป็นข้อกำหนด แต่ถ้าหากเราต้องการให้สร้างแล้วบันทึกลงใน Database ทันทีไม่ต้องมาพิม save() เองอีกรอบก็สามารถทำได้ด้วยคำสั่งนี้
>>> p1 = Publisher.objects.create(name=’Apress’,
...            address=’2855 Telegraph Avenue’,
...            city=’Berkeley’, state_province=’CA’, country=’U.S.A.’,
...            website=’http://www.apress.com/’)
เมื่อพิมคำสั่งนี้จะเป็นการสร้าง object ขึ้นพร้อมกับ save ลง Database ของเราทันที

9.จากข้อ 8 ที่ผ่านมาตอนเรียก publisher_list เพื่อแสดงข้อมูลนั้นปรากฎว่าได้ผลดังนี้
[<Publisher: Publisher object>, <Publisher: Publisher object>]
    ซึ่งเราไม่สามารถรู้ได้เลยว่า object ที่แสดงอยู่อันไหนเป็นอันไหนเพราะเหมือนกันหมดดังนั้นเพื่อการแสดงผลที่ถูกต้องให้เราเข้าไปแก้ไขไฟล์ models.py ใน books อีกครั้งโดยการเพิ่ม method __unicode__() เข้าไปจะได้ผลดังนี้
from django.db import models

class Publisher(models.Model):
    name = models.CharField(max_length=30)
    address = models.CharField(max_length=50)
    city = models.CharField(max_length=60)
    state_province = models.CharField(max_length=30)
    country = models.CharField(max_length=50)
    website = models.URLField()
   
    def __unicode__(self):
        return self.name

class Author(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=40)
    email = models.EmailField()

    def __unicode__(self):
        return u'%s %s' % (self.first_name, self.last_name)

class Book(models.Model):
    title = models.CharField(max_length=100)
    authors = models.ManyToManyField(Author)
    publisher = models.ForeignKey(Publisher)
    publication_date = models.DateField()

    def __unicode__(self):
        return self.title
    โดย __unicode__(self) ใน Publisher เป็นการส่ง name กลับไปแสดงผล ดังนั้นเมื่อเรียก publisher_list อีกครั้งจะแสดงผลได้แบบนี้
[<Publisher: Apress>, <Publisher: O’Reilly>]
    หากเราเรียกของ Author ก็จะได้เป็นชื่อ first_name last_name ไปแสดงผลแทน ส่วนของ book ก็จะได้เป็น title ไปแสดงผลเช่นกัน

    โดยข้อแม้ของการใช้ __unicode__(self) คือข้อมูลที่ return ไปต้องเป็นรูปแบบ unicode เท่านั้นเช่น String เป็นต้นหากส่ง int ไปจะเกิด Error ขึ้น

10.การแก้ไขเปลี่ยนแปลงค่าใน Database ผ่าน shell หากเราต้องการเปลี่ยนแปลงค่าใน p1 ที่สร้างไว้ในข้อ 8 เราสามารถใช้คำสั่งนี้ในการเปลี่ยนค่าได้เลย
>>> p1.name = ’Apress Publishing’
>>> p1.save()
11.การ filter objects ใน shell
>>> Publisher.objects.filter(name=’Apress’)
[<Publisher: Apress>]
>>> Publisher.objects.filter(country="U.S.A.", state_province="CA")
[<Publisher: Apress>]
>>> Publisher.objects.filter(name__contains="press")
[<Publisher: Apress>]
    จากการ filter ทั้ง 3 แบบนั้นเราสามารถใส่เงื่อนไขเพื่อเลือก object ออกมาแสดงได้ตามต้องการโดยแบบที่ 1 และ 2 นั้นจะดึงเฉพาะ object ที่มีเงื่อนไขตามในวงเล็บเท่านั้น ใส่เงื่อนไขกี่ตัวก็ได้แต่ในแบบที่ 3 จะแตกต่างจากแบบที่ 1 และ 2 ตรงที่จะดึงเอา object ออกมาในกรณีที่ name มี press อยู่ในนั้นหมายความว่าถ้ามี 2 object ที่เป็น Apress และ Bpress ก็จะได้คำตอบออกมาเป็นทั้ง Apress และ Bpress นั่นเอง

12.การดึงค่า object ออกมาเพียง 1 ตัว
>>> Publisher.objects.get(name="Apress")
<Publisher: Apress>
>>> Publisher.objects.get(country="U.S.A.")
Traceback (most recent call last):
         ...
MultipleObjectsReturned: get() returned more than one Publisher --
it returned 2! Lookup parameters were {’country’: ’U.S.A.’}
>>> Publisher.objects.get(name="Penguin")
Traceback (most recent call last):
         ...
DoesNotExist: Publisher matching query does not exist.
    การใช้ get นั้นในแบบที่ 1 และ 3 จะแตกต่างกันที่หาก get แล้วมี object ที่ name ตรงกันก็จะได้ object นั้นออกมาในแบบแรก แต่ในแบบที่ 3 จะเห็นว่าเกิด error ขึ้นเนื่องจากไม่สามารถ get object ออกมาได้เนื่องจากไม่มี object ไหนที่ name เป็น Penguin เลยแต่ในแบบที่ 2 เกิด error เพราะ get object ออกมาได้ แต่ได้มา 2 ตัวซึ่งเงื่อนไขของ get คือต้องได้กลับมาเพียง 1 ตัวดังนั้นจึงเกิด error ขึ้น

13.การ sort object ที่นำออกมาแสดงผล
>>> Publisher.objects.order_by("name")
[<Publisher: Apress>, <Publisher: O’Reilly>]
>>> Publisher.objects.order_by("address")
[<Publisher: O’Reilly>, <Publisher: Apress>]
>>> Publisher.objects.order_by("state_province")
[<Publisher: Apress>, <Publisher: O’Reilly>]
>>> Publisher.objects.order_by("state_province", "address")
[<Publisher: Apress>, <Publisher: O’Reilly>]
>>> Publisher.objects.order_by("-name")
[<Publisher: O’Reilly>, <Publisher: Apress>]
    ในแบบที่ 1, 2 และ 3 นั้นคือการเรียกตามตัวแปรที่เรากำหนดจากก่อนไปหลังของตัวแปรที่อยู่ในวงเล็บของ order_by() แบบที่ 4 ก็เช่นกันเพียงแต่เรียง 2 ครั้งโดยเรียงตาม state_province เป็นหลัก ในแบบที่ 5 นั้นหากเราใส่ - ไปข้างหน้า name แบบนั้นก็จะทำให้เรียงลำดับกลับกันจากเดิม ก่อนไปหลัง เป็น หลังไปก่อน กลับด้านกันนั่นเอง

13.1 เราสามารถ sort ลำดับของ object ทุกรอบได้โดยที่ไม่ต้องมาพิมคำสั่ง order_by ทุกครั้งด้วยการใช้ Class Meta โดยการเพิ่ม Class นี้ไว้เป็น Class ย่อยของตัวที่เราต้องให้ sort ดังนั้นลองใส่ไปใน Class Publisher
class Meta:
    ordering = [’name’]
ซึ่งทุก Class ใน models นั้นสามารถใส่ Meta เข้าไปได้ทั้งหมดและ Meta นั้นทำได้มากกว่าเพียงการ sort อีกด้วยในตัวอย่างจะเป็นการ sort ตามชื่อ โดยเรายังสามารถใส่เป็น -name ก็จะให้ค่าแบบที่เราพิมใน shell แบบที่ 5 อีกด้วยซึ่งเมื่อเรียก Publisher.objects.all() ก็จะแสดง List ที่ sort ลำดับของ object มาให้ตามใน ordering ที่เราเขียนใน Meta ไว้

14.การลบ object ออกจาก Database
>>> p = Publisher.objects.get(name="O’Reilly")
>>> p.delete()
>>> Publisher.objects.all()
[<Publisher: Apress Publishing>]
>>> Publisher.objects.filter(country=’USA’).delete()
>>> Publisher.objects.all().delete()
>>> Publisher.objects.all()
[]
    การลบเมื่อเราเลือก object ที่ต้องการลบได้แล้วนั้นก็ใช่คำสั่ง delete() ลบได้เลยแบบในช่วงแรกซึ่ง p.delete() จะลบออกจาก Database ทันทีหลังจากรันคำสั่ง และถ้าหากเราไม่ต้องการลบทีละตัวก็สามารถใช้การ filter หรือ เรียกทั้งหมดมาจาก all แล้วลบพร้อมกันทั้งหมดก็ได้เช่นกัน

    จาก Chapter 5 นั้นเราได้เรียนรู้การใช้ Models ผ่าน Django book ซึ่ง models นั้นช่วยให้อำนวยความสะดวกในการแก้ไข เพิ่ม ลบ ไฟล์ใน Database ได้อย่างง่ายดายซึ่งถือเป็นข้อดีและยังช่วยลดเวลาในการจัดการกับ Database ของเราลงไปอีกด้วย

ภาพตัวอย่างโค้ดใน models ที่แก้ไขจนจบ

Read more ...

วันอังคารที่ 21 มกราคม พ.ศ. 2557

Django Book Chapter 4 : Templates

    จาก Chapter 3 เราได้เรียนรู้ในเรื่องของการใช้ Views และ Urls ไปแล้วแต่ใน Views ของเรานั้นจะมีโค้ด HTML ผสมอยู่บ้างบางส่วนแต่หากหน้าเว็บของเราจำเป็นต้องมีโค้ด HTML ยาวๆไฟล์ Views ของเราก็จะอ่านยากแล้วทำให้ยากต่อการแก้ไขหรือทบทวนสิ่งที่เขียนไว้ได้ จึงเกิด Templates ขึ้นมาเพื่อแยกส่วนระหว่างโค้ด HTML และโค้ด Python ออกจากกันเพื่อให้ง่ายต่อการอ่านและสามารถนำโค้ด HTML นั้นกลับมาใช้ใหม่ได้อีกด้วย

1.ทดลองการใช้ Templates ใน shell ดังต่อไปนี้

1.1 ตัวอย่างที่ 1
>>> from django import template
>>> t = template.Template(’My name is {{ name }}.’)
>>> c = template.Context({’name’: ’Adrian’})
>>> print t.render(c)
My name is Adrian.
>>> c = template.Context({’name’: ’Fred’})
>>> print t.render(c)
My name is Fred.
    จากตัวอย่างนี้จะเป็นการ import API template ของ Django มาใช้ โดยให้ t เป็น template ที่เราสร้างขึ้นแบบง่ายๆ จะสังเกตเห็นว่า name นั้นมี {{ }} ครอบอยู่ ซึ่งนั่นเป็น Object ที่เราต้อง Context ไปใส่ใน Template เพื่อใช้ในการแสดงผล ต่อมาจะเห็นว่า c เป็นตัวแปรที่กำหนดค่า name ให้เป็น Adrian เก็บไว้จากนั้นจึงนำ c ไป render ใส่ t หรือก็คือการนำ name ที่เก็บไว้ใน c ไปใส่ให้ name ใน t ที่รอค่ามาใส่อยู่นั่นเองจึึงทำให้ผลเมื่อ Print ออกมาเป็น My name is Adrian. อย่างที่บอกไว้ข้างต้นนั้นว่า Template สามารถนำมาใช้ใหม่ได้ หากตัวแปร c ของเรานั้นไม่ได้ Context เป็นชื่อ Adrian แต่เปลี่ยนเป็น Fred เมื่อเรานำมา render ใหม่จากค่าที่เป็น Adrian ก็จะเปลี่ยนแปลงเป็น Fred ตามค่าของตัวแปร name ใน Context ที่เรา render เข้าไป

1.2 ตัวอย่างที่ 2
>>> from django.template import Template
>>> t = Template(’{% notatag %}’)
Traceback (most recent call last):
    File "<stdin>", line 1, in ?
    ...
django.template.TemplateSyntaxError: Invalid block tag: ’notatag’
    จากตัวอย่างนี้จะเป็นการใช้ tag ใน template ซึ่ง tag ที่นำมาใช้นั้นไม่มีอยู่จึงเกิด Error ขึ้นแบบนี้ ซึ่งการเกิด Error แบบนี้เป็นได้หลายกรณีเช่น เป็น tag ที่ไม่มีอยู่จริง, เป็น filters ที่ไม่มีอยู่จริง, ลืมใส่ tag ปิด (tag บางตัวต้องมี tag ปิดด้วย) เป็นต้น

1.3 ตัวอย่างที่ 3
>>> from django.template import Template, Context
>>> raw_template = """<p>Dear {{ person_name }},</p>
...
... <p>Thanks for placing an order from {{ company }}. It’s scheduled to
... ship on {{ ship_date|date:"F j, Y" }}.</p>
...
... {% if ordered_warranty %}
... <p>Your warranty information will be included in the packaging.</p>
... {% else %}
... <p>You didn’t order a warranty, so you’re on your own when
... the products inevitably stop working.</p>
... {% endif %}
...
... <p>Sincerely,<br />{{ company }}</p>"""
>>> t = Template(raw_template)
>>> import datetime
>>> c = Context({’person_name’: ’John Smith’,
...            ’company’: ’Outdoor Equipment’,
...            ’ship_date’: datetime.date(2009, 4, 2),
...            ’ordered_warranty’: False})
>>> t.render(c)
u"<p>Dear John Smith,</p>\n\n<p>Thanks for placing an order from Outdoor
Equipment. It’s scheduled to\nship on April 2, 2009.</p>\n\n\n<p>You
didn’t order a warranty, so you’re on your own when\nthe products
inevitably stop working.</p>\n\n\n<p>Sincerely,<br />Outdoor Equipment
</p>"
    จากตัวอย่างสังเกตว่าจะมี {{ ship_date|date:"F j, Y" }} ซึ่งใน tag นี้มี filter ที่ชื่อ date ด้วยซึ่งเอาไว้จัดรูปแบบในการแสดงผลของวันจากตัวอย่างจะเห็นว่าผลลัพธ์ที่ออกมาจะเป็น เดือน วัน, ปี และยังมี tag if ซึ่งใช้หลักการเดียวกับการเขียน if else ในโปรแกรมทั่วไปซึ่งตัวแปรที่ใช้เช็ค True False คือ ordered_warranty นั่นเองซึ่งหากเป็น True ก็จะใส่ <p>Your warranty information will be included in the packaging.</p> ลงในผลลัพธ์ที่ได้จากการ render แต่ถ้าเป็น False ก็จะใส่ <p>You didn’t order a warranty, so you’re on your own when the products inevitably stop working.</p> ลงในผลลัพธ์แทน

1.4 ตัวอย่างที่ 4
>>> from django.template import Template, Context
>>> class Person(object):
...          def __init__(self, first_name, last_name):
...              self.first_name, self.last_name = first_name, last_name
>>> t = Template(’Hello, {{ person.first_name }} {{ person.last_name }}.’)
>>> c = Context({’person’: Person(’John’, ’Smith’)})
>>> t.render(c)
u’Hello, John Smith.’
    จากตัวอย่างนี้แสดงให้เห็นว่าหาก Object ที่เรา Context ไปยัง Template เป็น Object ใหญ่ที่มีตัวแปรต่างๆอยู่ภายใน เราสามารถดึงค่าเหล่านั้นมาใช้ได้โดยการพิมชื่อของ Object ตามด้วย "." แล้วตามด้วยชื่อตัวแปรที่ต้องการดังตัวอย่างข้างบนนี้

1.5 ตัวอย่างที่ 5
>>> from django.template import Template, Context
>>> t = Template(’{{ var }} -- {{ var.upper }} -- {{ var.isdigit }}’)
>>> t.render(Context({’var’: ’hello’}))
u’hello -- HELLO -- False’
>>> t.render(Context({’var’: ’123’}))
u’123 -- 123 -- True’
    จากตัวอย่างนี้นอกจากจะสามารถดึงตัวแปรที่อยู่ภายใน Object ได้แล้วจากตัวอย่างที่ 1.4 Templates ก็ยังสามารถเรียกใช้ Method ของ Object นั้นๆได้อีกด้วยเช่นกันเห็นได้จากตัวอย่างนี้

1.6 ตัวอย่างที่ 6
>>> from django.template import Template, Context
>>> t = Template(’Your name is {{ name }}.’)
>>> t.render(Context())
u’Your name is .’
>>> t.render(Context({’var’: ’hello’}))
u’Your name is .’
>>> t.render(Context({’NAME’: ’hello’}))
u’Your name is .’
>>> t.render(Context({’Name’: ’hello’}))
u’Your name is .’
    จากตัวอย่างหากเรา Context ตัวแปรที่ไม่มีอยู่ใน Templates หรือ มีแต่ตัวพิมเล็กใหญ่ไม่ตรงกันนั้นก็จะทำให้ Templates มองว่าตัวแปรที่ Context ไปไม่ใช่ตัว Templates ต้องการซึ่งเดิมๆแล้วนั้นหากตัวแปรที่รอ Context มานั้นหากไม่มีค่าส่งมาให้ Django ก็จะใส่เป็น Empty String ให้เอง

2. เรียนรู้เกี่ยวกับการใช้ Tag

2.1 Tag if else
{% if today_is_weekend %}
<p>Welcome to the weekend!</p>
{% endif %}
    แบบที่ 1 มี Tag if อย่างเดียวหากเข้าเงื่อนไขหรือเป็น True ก็จะนำสิ่งที่อยู่ใน if ใส่เข้าไปในผลลัพธ์จากการ render ด้วย
{% if today_is_weekend %}
<p>Welcome to the weekend!</p>
{% else %}
<p>Get back to work.</p>
{% endif %}
    แบบที่ 2 มี Tag if else ซึ่งก็ใช้หลักการเดียวกับการเขียนโปรแกรมทั่วไปหากไม่เงื่อนไข if ก็จะทำใน else โดยเรายังสามารถใส่ if ซ้อนไว้ใน else ได้อีกด้วยแต่ไม่มีรูปแบบของการเขียน elif

โดยทั้ง 2 แบบนั้นต้องใส่ tag endif เข้าไปด้วยเสมอหากไม่ใส่ก็จะเกิด TemplateSyntaxError.

สิ่งที่น่ารู้เกี่ยวกับ Tag if else ใน Templates ของ Django คือ หากตัวแปรที่เอามาเช็ค if else นั้นมีคุณสมบัติ ตามข้างล่างนี้จะได้ผลเป็น False เสมอ
    - เป็น empty list
    - เป็น empty tuple
    - เป็น empty dictionary
    - เป็น empty string
    - มีค่าเป็น 0
    - เป็น None Object

2.2 Tag for
{% for athlete in athlete_list %}
    <h1>{{ athlete.name }}</h1>
    <ul>
    {% for sport in athlete.sports_played %}
        <li>{{ sport }}</li>
    {% endfor %}
    </ul>
{% endfor %}
    การใช้ for loop ใน Templates ก็สามารถทำได้เช่นกันโดยสามารถใช้วนลูปเข้าถึงตัวแปรทั้งหมดภายใน list ได้สามารถใช้ for ซ้อนไว้ข้างใน for ก็ได้เช่นกัน หรือจะใส่ if else ใน for หรือเอา for ไว้ใน if else ก็สามารถทำได้เช่นกัน
{% for athlete in athlete_list %}
    <p>{{ athlete.name }}</p>
{% empty %}
    <p>There are no athletes. Only computer programmers.</p>
{% endfor %}
    อีกหนึ่งตัวอย่างที่น่าสนใจการมีการเข้าลูป for แต่ตัวที่จะเอามาวนลูปนั้นดันเป็นตัวแปรที่เป็น empty list เราก็สามารถใส่ tag empty ไว้หากเป็น empty list ก็จะใส่ข้อมูลที่อยู่หลัง tag empty ลงไปแทน

นอกจากนี้ for ยังมีสิ่งที่น่ารู้คือ for ใน Templates นั้นจะมีตัวแปรที่สร้างขึ้นตอนที่อยู่ใน for สามารถดึงมาใช้ได้ซึ่งก็คือตัวแปรที่ชื่อ forloop และเมื่อจบลูปตัวแปรนี้ก็จะถูกทำลายทิ้งไป ซึ่งวิธีใช้ก็ต้องใส่ Tag ด้วยดังนี้ {{ forloop }} ซึ่ง forloop นั้นมี attributes ดังนี้
    - forloop.counter จะได้ค่าออกมาเป็นเลขที่บอกจำนวนรอบของ loop ที่กำลังวนอยู่ในขณะนั้น โดยจะเริ่มนับจำนวนรอบที่ 1
    - forloop.counter0 คุณสมบัติเหมือน forloop.counter ทุกประการ แต่สิ่งที่แตกต่างจาก forloop.counter คือจะเริ่มนับจำนวนรอบที่ 0
    - forloop.revcounter จะได้ค่าออกมาเป็นจำนวนของรอบคงเหลือที่ต้องวนลูป ในขณะนั้น โดยจะเริ่มนับจากจำนวนรอบทั้งหมดลดลงไปเรื่อยๆ จนถึง 1
    - forloop.revcounter0 คุณสมบัติเหมือน forloop.revcounter ทุกประการแต่ตอนเริ่มนับจะเริ่มจากจำนวนรอบทั้งหมด - 1 และลดลงเรื่อยๆจนเหลือ 0
    - forloop.first จะได้ผลลัพธ์ออกมาเป็นค่าความจริง True ก็ต่อเมื่อรอบที่วนลูปอยู่นั้นเป็นรอบแรก รอบอื่นๆจะได้ False ทั้งหมด
    - forloop.last จะได้ผลลัพธ์ออกมาเป็นค่าความจริง True ก็ต่อเมื่อรอบที่วนลูปอยู่นั้นเป็นรอบสุดท้าย รอบอื่นๆจะได้ False ทั้งหมด
    - forloop.parentloop จะดึงตัวแปรของ forloop ของลูปชั้นบนออกมาซึ่งมี attributes เหมือนกันทุกประการวิธีเรียก เช่น forloop.parentloop.last เป็นต้น

ตัวอย่างการใช้ forloop
{% for object in objects %}
    {% if forloop.first %}<li class="first">{% else %}<li>{% endif %}
    {{ object }}
    </li>
{% endfor %}
ตัวอย่างการใช้ forloop.parentloop
{% for country in countries %}
    <table>
    {% for city in country.city_list %}
        <tr>
        <td>Country #{{ forloop.parentloop.counter }}</td>
        <td>City #{{ forloop.counter }}</td>
        <td>{{ city }}</td>
        </tr>
    {% endfor %}
    </table>
{% endfor %}
2.3 Tag ifequal ifnotequal (ใช้รูปแบบเดียวกันต่างกันที่เช็คว่าเท่ากันไหม หรือ ไม่เท่ากัน)
{% ifequal user currentuser %}
    <h1>Welcome!</h1>
{% endifequal %}
    แบบที่ 1 ใช้เปรียบเทียบระหว่างตัวแปรกับตัวแปร จากตัวอย่างนี้เป็นการเทียบว่า user = currentuser หรือไม่ ถ้าเท่ากันก็จะใส่ <h1>Welcome!</h1> เข้าไปในผลลัพธ์
{% ifequal section ’sitenews’ %}
    <h1>Site News</h1>
{% else %}
    <h1>No News Here</h1>
{% endifequal %}
    แบบที่ 2 Tag ifequal และ ifnotequal ก็รองรับการใช้ Tag else ได้เช่นกับเดียวกับ Tag if แบบปกติ นอกจากนั้น ifequal และ ifnotequal ยังสามารถใช้เทียบระหว่างตัวแปร กับ ข้อมูลที่ต้องการเช็คตรงๆเลยก็ได้เช่นกันแต่มีข้อจำกัดอยู่ว่ารูปแบบนี้สามารถใช้ตัวแปรเทียบได้แค่ 4 รูปแบบนี้เท่านั้นคือ
    - 1
    - 1.23
    - ’foo’
    - "foo"

 3. หลักจากศึกษาวิธีการใช้และรูปแบบการเขียน Templates กันไปแล้วมาเริ่มทดลองใช้ Templates กันเลยดีกว่า

3.1 สร้างโฟลเดอร์สำหรับการเก็บ Templates ขึ้นมาก่อนจะไว้ที่ไหนก็ได้แต่แนะนำให้ไว้ที่เดียวกับที่มีไฟล์ manage.py เพราะง่ายต่อการเข้าถึงและเห็นชัดเจนเมื่อสร้างแล้วจะได้รูปแบบประมาณนี้


3.2 เมื่อสร้างโฟลเดอร์แล้วเราต้องบอกให้ Django รู้ก่อนว่าโฟลเดอร์นี้นะเราจะใช้เก็บ Templates ทั้งหมดของเราไว้ ดังนั้นให้เข้าไปแก้ไขไฟล์ settings.py ในโฟลเดอร์ mysite โดยเพิ่มข้อความดังนี้
TEMPLATE_DIRS = (
    '/home/django/mysite/templates',
)
เพื่อเป็นการระบุที่อยู่ของ Templates ให้ Django รู้แต่การกำหนดรูปแบบนี้ไม่สมควรใช้เนื่องจากหากมีเปลี่ยนเครื่องก็จะทำให้ Directory ที่ลงไว้ใน settings ไม่ตรงกับเครื่องนั้นแล้วทำให้หาไฟล์ Templates ไม่เจอในที่สุดดังนั้นวิธีแก้เปลี่ยนไปใช้รูปแบบของที่อยู่ดังนี้
TEMPLATE_DIRS = (
    os.path.join(BASE_DIR, 'Templates').replace('\\','/'),
)
 BASE_DIR เป็นตัวแปรที่มีอยู่แล้วใน settings ซึ่งจะดึงที่อยู่ ณ ปัจจุบันมาให้เราใช้ได้เลย

3.3 เข้าไปยังโฟลเดอร์ Templates ที่เราสร้างขึ้นในข้อ 3.1 แล้วสร้างไฟล์ชื่อ current_datetime.html แล้วใส่โค้ดในไฟล์ HTML ดังนี้
<html><body>It is now {{ current_date }}.</body></html>
3.4 เข้าไปแก้ไขไฟล์ views.py ในโฟลเดอร์ mysite โดยเพิ่มโค้ดและแก้ไขดังนี้
from django.template.loader import get_template
from django.template import Context

def current_datetime(request):
    now = datetime.datetime.now()
    t = get_template('current_datetime.html')
    html = t.render(Context({'current_date': now}))
    return HttpResponse(html)
เมื่อแก้ไขเสร็จแล้วลองเข้าไปที่ http://localhost:8000/time/ ดูจะพบว่าหน้าเว็บของเราจะแสดงเวลา ณ ปัจจุบันออกมาเหมือนกับฟังก์ชั่นที่เราเขียนไว้ก่อนหน้าใน Chapter 3


แต่ฟังก์ชั่นที่เราแก้ไขเมื่อสักครู่เราสามารถทำให้สั้นลงได้อีกด้วยการใช้ render_to_response ดังนี้
from django.shortcuts import render_to_response

def current_datetime(request):
    now = datetime.datetime.now()
    return render_to_response('current_datetime.html', {'current_date': now})
ก็จะให้ผลเหมือนกันแต่โค้ดสั้นลงเนื่องจาก Django มี API ที่ชื่อ render_to_response อยู่แล้วซึ่งเรา import มาจาก django.shortcuts แล้วนำมาใช้ได้เลย แต่ก็ยังมีวิธีที่สั้นลงไปอีกโดยการใช้ locals() แทนการเขียนตัวแปรที่ใช้ Context ทั้งหมดดังนี้
def current_datetime(request):
    current_date = datetime.datetime.now()
    return render_to_response('current_datetime.html', locals())
โดยหลักการของ locals() คือการนำ local variable ทั้งหมดที่มีในฟังก์ชั่นนี้ส่งไปให้ Templates ทั้งหมด ดังนั้นการตั้งชื่อตัวแปรจึงสำคัญเพราะตัวแปรที่เราจะแทนค่าใน Templates หากไม่มีชื่อตรงกับใน local variable ก็จะไม่มีตัวแปรไป render และที่สำคัญอีกอย่างคือการส่งตัวแปร local variable ไปทั้งหมดนั่นหมายถึงการส่ง request ไปให้ Templates ด้วยเช่นกันดังนั้นหากจะใช้วิธีลัดแบบ locals() นั้นต้องระมัดระวังเป็นพิเศษและการใช้ locals() นี้มีข้อดีเพียงแค่เขียนสั้นลงเท่านั้น

3.5 สร้างโฟลเดอร์ includes โดยเข้าไปเก็บไว้ในโฟลเดอร์ Templates ที่สร้างในข้อ 3.1 ให้เป็น Sub Directory และสร้าง Templates ชื่อ nav.html มีโค้ดภายในดังนี้
<div id="nav">
You are in: {{ current_section }}
</div>
    จากนั้นเข้าไปแก้ไข Templates current_datetime.html โดยเพิ่ม Tag includes เข้าไปจะได้ดังนี้
<html>
<body>
{% include "includes/nav.html" %}
It is now {{ current_date }}.
</body>
</html>
    เมื่อใส่ Tag includes เข้าไปสังเกตจะเห็นว่า ที่อยู่เป็น includes/nav.html ซึ่งหากใน views ของเรานั้นอยากใช้ Temmplates ที่อยู่ในโฟลเดอร์ includes ก็สามารถพิมที่อยู่ของไฟล์แบบเดียวกับในตัวอย่างนี้ได้เลย และเนื่องจากเราดึง Templates อื่นเข้ามาใช้ใน Templates current_datetime.html ด้วยดังนั้นตัวแปรที่ nav.html ต้องการให้ Context ไปเราก็ต้อง Context ไปให้ด้วยเช่นกันดังนั้นเข้าไปแก้ไขไฟล์ Views โดยเพิ่มโค้ดเข้าไปดังนี้
def current_datetime(request):
    current_date = datetime.datetime.now()
    current_section = "/time/"
    return render_to_response('current_datetime.html', locals())
โดยเบื้องต้นกำหนด current_section ให้เป็น /time/ เพื่อนำไปแสดงผลหากเราไม่ Context current_section ไปหน้าเว็บส่วนที่ต้อง Context ก็จะกลายเป็นช่องว่างๆไป

ตัวอย่างเมื่อ Context current_section ไป


 ตัวอย่างเมื่อไม่ Context current_section ไป


    จาก Chapter 4 นี้ Templates ช่วยให้เราแยกโค้ด Python และ โค้ด HTML ออกจากกันได้อย่างสมบูรณ์พร้อมทั้งสามารถนำ Templates มาใช้ได้เรื่อยๆไม่ต้องเขียนใหม่ซ้ำๆการใช้ includes ทำให้เราสามารถกำหนดรูปแบบของ Header และ Footer ของเว็บโดยที่ไม่จำเป็นต้องนั่งพิมทั้ง Header และ Footer ในทุกๆ Templates ที่เราสร้างการใช้ Tag และ Filters ใน Templates เพื่ออำนวยความสะดวกในการนำ Object มา render แล้วนำไปแสดงผล
Read more ...

Django Book Chapter 3 : Views and URLconfs

    จาก Chapter 2 นั้นเราได้สร้างโปรเจคที่ชื่อ mysite ไว้แล้วดังนั้นจะเริ่ม ต้นเรียนรู้การใช้งาน Views และ Urls เบื้องต้นโดยการสร้างเพจที่แสดงคำว่า Hello world ขึ้น

1.สร้างไฟล์ชื่อ views.py ไว้ในโฟลเดอร์ชื่อ mysite ที่อยู่ภายในของโปรเจค mysite ของเราและเขียนโค้ดภายในไว้ดังนี้
from django.http import HttpResponse
def hello(request):
    return HttpResponse("Hello world")
    ซึ่งโค้ดที่เขียนลงไปนั้นคือการสร้างฟังก์ชั่นที่ชื่อ hello ขึ้นมามี Argument 1 ตัวชื่อ request ซึ่งจะเป็น Objects ที่เก็บข้อมูล Web ที่ส่ง request มายัง Views นี้โดยเมื่อเรียกฟังก์ชั่นนี้จะตอบสนองแบบ http ส่งเป็นข้อความว่า Hello world กลับไปและใน views.py นี้ได้มีการ import API มาจาก Django ชื่อ HttpResponse
    จากนั้นหากลอง runserver แล้วเข้าไปยังหน้าเว็บของเรานั้นก็จะยังไม่พบคำว่า Hello world แต่จะยังคงขึ้น It worked! แบบใน Chapter 2 เนื่องจาก Django ยังไม่รู้ว่าเมื่อใดที่เราจะต้องเรียกฟังก์ชั่น hello นี้ขึ้นมาใช้

ภาพตัวอย่างที่อยู่ของไฟล์ views.py


2.จากในข้อ 1 หากเราต้องการให้ Django รู้ว่าเราต้องจะเรียกใช้ฟังก์ชั่น hello ที่อยู่ใน views เมื่อใดก็ต้องใช้ Urls เป็นตัวกำหนดดังนั้นเปิดไฟล์ urls.py ที่อยู่ที่เดียวกับที่เราสร้าง views.py ไปในข้อ 1 แล้วเพิ่ม from mysite.views import hello เข้าไปและเพิ่ม ('^hello/$', hello), เข้าไปใน urlpatterns จะได้รูปแบบดังนี้


    โดยเราได้ import ฟังก์ชั่น hello ใน views.py เข้ามาเพื่อใช้ใน urlpatterns โดย ('^hello/$', hello), เป็นการบอกว่าหากมีการเรียก urls เป็น http://localhost:8000/hello/ ก็จะให้เรียกฟังก์ชั่น hello เป็นตัวจัดการว่าจะส่งอะไรกลับไปแสดงผล โดย hello ส่วนแรกจะเป็นการระบุรูปแบบของ urls โดยมี regular expressions "^" เป็นตัวบอกว่าเริ่ม และ "$" เป็นตัวบอกว่าจบ ส่วน hello ตัวที่ 2 ใช้บอกว่าหากมีชื่อ urls แบบ hello ตัวแรกนั้นจะเรียกฟังก์ชั่นอะไรมาจัดการซึ่งในตัวอย่างนี้ก็เรียกฟังก์ชั่น hello มาจัดการนั่นเอง ทดลองเข้าไปที่ http://localhost:8000/hello/ ก็จะแสดงผลออกมาเป็น hello world ดังนี้


3.หากเราลองเรียก http://localhost:8000/ จะแสดงหน้า 404 Error เหตุผลเพราะเราไม่ได้ตั้งค่าที่ urlpatterns ใน Urls.py ว่าหากมีการเรียกหน้า http://localhost:8000/ จะให้ฟังก์ชั่นอะไรมาจัดการเมื่อไม่มีฟังก์ชั่นไหนมาจัดการก็เลยทำให้เกิด Error ขึ้นอย่างที่เห็น

ภาพตัวอย่างหน้า 404 Error


4.รูปแบบการจัดการ request ของ Django นั้นเมื่อมี request เข้ามานั้น จะอ่านค่า ในไฟล์ setting.py หัวข้อ ROOT_URLCONF ว่ามีค่าเป็นอะไรในเบื้องต้นโปรเจคที่เราสร้างนั้นจะกำหนดไว้เป็นดังนี้ ROOT_URLCONF = 'mysite.urls' ซึ่งก็คือการกำหนดให้อ่านค่าจากไฟล์ urls.py ที่เราแก้ไขในข้อ 2 นั่นเองจากนั้นก็เข้าไปจัดการตามสิ่งที่เราเขียนไว้ใน views และ urls จะได้ค่ากลับมาเป็น HttpResponse เมื่อได้มา Django ก็ทำการแปลงให้เป็นโค้ด HTML กลับไปแสดงผลที่ Browser นั่นเอง

5.หน้าเว็บนั้นหลักๆมีอยู่ 2 ประเภทนั่นคือรูปแบบ Static และ Dynamic ซึ่งตัวอย่างของ Static นั้นก็คือ Hello world ที่เราทำไปแล้วนั่นเอง โดย Static คือไม่ว่าเราจะเรียกหน้านี้ที่เวลาใดๆ ก็จะยังคงให้ข้อมูลกลับมาเหมือนกันทุกครั้ง แต่หากเป็นแบบ Dynamic ข้อมูลที่ได้จะมีการเปลี่ยนแปลงไปตามเวลาที่เรียกใช้ตามตัวอย่างต่อไปนี้

5.1 แก้ไขไฟล์ views.py โดยเพิ่มโค้ดเข้าไปดังนี้
import datetime
def current_datetime(request):
    now = datetime.datetime.now()
    html = "<html><body>It is now %s.</body></html>" % now
    return HttpResponse(html)
เมื่อเพิ่มเสร็จแล้วจะได้รูปแบบดังนี้


    จากโค้ดจะเป็นการ import API ที่ชื่อ datetime เพิ่มเข้ามาเพื่อใช้ในฟังก์ชั่น current_datetime ซึ่ง ฟังก์ชั่นนี้เมื่อมีการเรียกจะนำค่าวันที่เวลา ณ ตอนที่เรียกนั้นส่งกลับไปใน HttpResponse ให้ Django ไปจัดการต่อเอง

5.2 แก้ไขไฟล์ urls.py โดยเพิ่ม current_datetime เข้าไปในบรรทัดที่ import views เข้ามา และเพิ่ม ('^time/$', current_datetime), เข้าไปใน urlpatterns จะได้ดังรูปนี้


5.3 ทดลองเข้าไปที่ http://localhost:8000/time/ จะแสดงเวลาปัจจุบันให้เราแต่ หากสังเกตดีๆจะพบว่าเวลาที่ได้จะมีความเพี้ยนซึ่งเกิดจาก Time Zone ที่เราอยู่ไม่ตรงกับค่าที่ Django ตั้งมาให้เป็นมาตราฐาน ซึ่งคือ  UTC ดังนั้นวิธีแก้ไขให้เราเปิดไฟล์ settings.py แล้วมองหาหัวข้อ TIME_ZONE จากนั้นแก้ไขเป็น Asia/Bangkok ตามรูป


 ภาพตัวอย่างตอนที่ยังไม่แก้ไข Time Zone


ภาพตัวอย่างหลังจากแก้ไข Time Zone


6.นอกจากหน้าเว็บที่สามารถทำเป็นแบบ Dynamic ได้แล้วนั้น Urls เองก็สามารถทำให้เป็นรูปแบบของ Dynamic ได้ด้วยเช่นกัน หากเราต้องการใช้รูปแบบ Urls ดังนี้
urlpatterns = patterns('',
    ('^time/$', current_datetime),
    ('^time/plus/1/$', one_hour_ahead),
    ('^time/plus/2/$', two_hours_ahead),
    ('^time/plus/3/$', three_hours_ahead),
    ('^time/plus/4/$', four_hours_ahead),
)
เราสามารถแก้ไขให้เป็นรูปแบบดังนี้
urlpatterns = patterns('',
    # ...
    (r'^time/plus/\d+/$', hours_ahead),
    # ...
)
ซึ่ง \d+ เป็นการระบุว่าสามารถใส่เป็นเลขอะไรก็ได้กี่หลักก็ได้ แต่หากเราต้องการกำหนดให้ใส่เลขไม่เกิน 2 หลักก็ให้ใช้ดังนี้ \d{1,2} ซึ่งเป็นอีกหนึ่งใน regular expressions ดังนั้นแก้ไขไฟล์ urls.py ดังนี้


และเพิ่มฟังก์ชั่น hours_ahead ใน views.py ดังนี้
def hours_ahead(request, offset):
    try:
        offset = int(offset)
    except ValueError:
        raise Http404()
    dt = datetime.datetime.now() + datetime.timedelta(hours=offset)
    html = "<html><body>In %s hour(s), it will be %s.</body></html>" % (offset, dt)
    return HttpResponse(html)
    โดยฟังก์ชั่นนี้มี Arguments 2 ตัวคือ request และ offset ซึ่ง offset นั้นจะเอาค่าของเลขที่เราใส่ใน Urls มาใช้ โดยฟังก์ชั่นนี้ใช้ API datetime ในดึงเวลามาแล้วบวกเพิ่มไปด้วย datetime.timedelta ไปเป็นจำนวนชั่วโมงตามค่าของ offset ที่รับมา จะสังเกตได้ว่าช่วงแรกนั้นมีการใช้ try & except ด้วยเนื่องจากค่าที่เราใช้ในการคำนวณเวลาต้องเป็นตัวเลขเท่านั้นหากมีการใส่ค่าใน urls มาเป็นตัวอักษรก็จะแปลงค่าจาก String ที่ได้มาจาก Urls ไปเป็น int ไม่ได้แล้วเกิด ValueError ขึ้นเพื่อป้องกันความผิดพลาดตรงนี้เลยมีการใส่ try & except เข้าไปเพื่อไม่ให้ Error ส่วนนี้สร้างปัญหาให้กับ Web Application ของเราได้ โดยหากเกิด ValueError ก็จะเปลี่ยนเพจจากการแสดงผลตามปกติไปเป็นหน้า 404 Error แทน

ภาพตัวอย่างเมื่อใส่ค่า 12 ลงใน Urls


ภาพตัวอย่างเมื่อใส่ค่า 100 ลงใน Urls จะเกิด Error เนื่องจากรูปแบบของ Urls


ภาพตัวอย่างเมื่อใส่ตัวอักษรลงใน Urls


    จาก Chapter 3 นี้เราสามารถทำให้ทั้ง Urls และ Views ของเรานั้นอยู่ในรูปแบบของ Dynamic ได้ด้วยการใช้ regular expression เป็นตัวช่วยในส่วนของ Urls และ API ของ Python และ Django มาช่วยใน Views อีกทั้งการเขียนฟังก์ชั่นในการแสดงผลควรจะมีการคิดถึงกรณีที่ผู้ใช้ใส่ค่ามามาผิดรูปแบบจากที่ต้องการเช่นใส่ ตัวอักษรมาให้แต่ฟังก์ชั่นเราต้องการตัวเลข ในส่วนนี้เป็นส่วนที่ต้องคำนึงถึงในการพัฒนา Web Application ขึ้นด้วย


Read more ...

วันจันทร์ที่ 20 มกราคม พ.ศ. 2557

Django Book Chapter 2 : Getting Started

    เริ่มต้นนั้น Django ต้องใช้ภาษา Python เป็นตัวหลักในการทำงานดังนั้นต้องลง Python ก่อนโดยสามารถหาไฟล์ที่ใช้ในการลงกับวิธีการลงได้ที่ http://www.python.org/download/ โดยแนะนำให้ลงเวอร์ชั่น 2.X สำหรับคนเริ่มต้นเรียนรู้ภาษา Python โดยผู้เขียนใช้เวอร์ชั่น 2.7.3

    จากนั้นจึงลง Django แนะนำให้ลงเวอร์ชั่นล่าสุด ซึ่งสามารถหาไฟล์กับวิธีการลงได้ที่นี่ https://www.djangoproject.com/download/

    เริ่มสร้าง Project โดย Directory ที่สร้างนั้นจะเป็น root ซึ่งแล้วแต่เราเลยว่าจะสร้างไว้ที่ไหนก็ได้ซึ่งโดยทั่วไปปกติจะวางไว้ที่ /var/www แต่สำหรับ Django นั้นจะมอง root เป็นที่ที่เก็บโฟลเดอร์ของโปรเจคไว้ เหตุผลที่ไม่วางไว้ใน /var/www เพราะบุคคลอื่นจะสามารถดู Code ที่เราเขียนไว้ผ่านทาง Web Browser ได้หากเราวาง Code ไว้ในนั้น เมื่อเลือก Directory ที่จะใช้เก็บโปรเจคได้แล้วให้เปิด Terminal เข้าไปยัง Directory ที่ต้องการจากนั้นพิม django-admin.py startproject mysite จะเป็นการสร้างโปรเจคและมีรูปแบบของโฟลเดอร์ดังนี้
mysite/
    manage.py
    mysite/
        __init__.py
        settings.py
        urls.py
        wsgi.py
    จากนั้นพิมคำสั่ง python manage.py runserver โดย port ที่ใช้เป็นค่าเริ่มต้นจะเป็น 8000 ซึ่งจะรัน server ที่ localhost ของเครื่องเรา แต่หากต้องการเปลี่ยนไป port อื่นก็สามารถทำได้ โดยพิม python manage.py runserver 8080 ตอนจะรัน server ดังนั้น port ก็จะไปใช้ port 8080 และหากต้องการให้เครื่องอื่นที่อยู่ใน Network เดียวกับเราสามารถเข้ามาเปิด Web Application ของเรานั้นต้องพิม python manage.py runserver 0.0.0.0:8080 ตอนจะรัน server โดยเครื่องอื่นให้พิม Urls ใน Browser เป็น ip ของเครื่องที่รัน server อยู่ตามด้วยพอร์ท เช่นเครื่องที่รัน server มี ip เป็น 192.168.1.100 ก็ให้พิมใน Browser ว่า http://192.168.1.100:8000/ เป็นต้น

ภาพตัวอย่างจาก Terminal


    จากนั้นลองเปิดเข้าไปใน Browser ของแล้วพิม http://127.0.0.1:8000 จะแสดงหน้าเว็บดังรูปนี้ ซึ่ง 127.0.0.1 คือ ip ของ localhost นั่นเอง โดยนอกจากการพิม http://127.0.0.1:8000 แล้วนั้นเรายังสามารถพิมว่า http://localhost:8000/ ก็ได้ผลแบบเดียวเช่นกัน


Read more ...