วันอังคารที่ 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 แล้วนำไปแสดงผล

ไม่มีความคิดเห็น:

แสดงความคิดเห็น