Develop with google app engine (Advance)

การพัฒนาด้วย Google App Engine

ในส่วนนี้เราจะฝึกหัดพัฒนาโปรแกรมประยุกต์บนเว็บโดยใช้ Google App Engine

เริ่มใช้ App Engine : หน้าเว็บแบบพลวัติอย่างง่าย

ในส่วนนี้เราจะเริ่มใช้ App Engine ในการพัฒนาโปรแกรมประยุกต์บนเว็บ เนื้อหาดัดแปลงมาจากหน้า Getting Started

เริ่มต้นติดตั้ง App Engine กันก่อน

เข้าไปที่หน้า Download จากนั้นดาวน์โหลดแฟ้ม 

google_appengine_1.1.0.zip

มาไว้ที่ใดที่หนึ่งในโฮมไดเร็กทรอรี

ที่โฮมไดเร็กทรอรี สร้างไดเร็กทรอรี 

lib

จากนั้นให้แตกแฟ้มที่ดาวน์โหลดมา ภายในไดเร็กทรอรี 

lib

โดยสั่ง

unzip พาธไปยัง/google_appengine_1.1.0.zip

ในไดเร็กทรอรี 

lib

ถ้าเราสั่ง 

ls ~/lib/google_appengine

เราจะเห็นแฟ้มของ app engine มากมาย ถ้าไม่เห็นแสดงว่าเกิดข้อผิดพลาดขึ้นบางที่ ให้กลับไปตรวจสอบ

โปรแกรมแรก

ในไดเร็กทรอรี

  • สร้างแฟ้ม 
    app.yaml

    ที่มีข้อมูลดังนี้

    application: kayyou
    version: 1
    runtime: python
    api_version: 1

    handlers:
    - url: /.*
      script: main.py

    ให้ระวังการเว้นย่อหน้าให้ดีด้วย

    แฟ้ม 

    app.yaml

    นี้จะระบุรายละเอียดของโปรแกรมของเรา เช่นว่าชื่ออะไร และจะให้ app engine เรียกโปรแกรมจากแฟ้มใดในการประมวลผล ในตัวอย่างข้างต้นเราระบุว่า ถ้ามีการร้องขอมาที่ URL อะไรก็ตาม ให้เรียกโปรแกรมในแฟ้ม 

    main.py

    เพื่อตอบสนอง

  • สร้างแฟ้ม 
    main.py

    ให้มีข้อมูลดังนี้

    print 'Content-Type: text/plain'
    print ''
    print 'Hello, world!'

    โปรแกรมดังกล่าวแสดงผลข้อมูลแบบ HTML พร้อมด้วยข้อมูลส่วนหัว (header) เพื่อตอบสนองการร้องขอหน้าเว็บ

  • เรียกเว็บเซิร์ฟเวอร์ที่มากับ App Engine ให้ทำงาน โดยเรียก
    ~/lib/google_appengine/dev_appserver.py .

    ภายในไดเร็กทรอรีที่เราสร้างแฟ้มทั้งสอง โปรแกรมดังกล่าวจะทำตัวเองเป็นเว็บเซิร์ฟเวอร์ที่จะเรียกโปรแกรมที่เราเขียนให้ทำงานอีกทีหนึ่ง

    อย่าลืมเครื่องหมาย 

    .

    ที่คำสั่งดังกล่าว เพื่อระบุว่าโปรแกรมของเราอยู่ที่ไดเร็กทรอรีปัจจุบัน รูปแบบทั่วไปของการเรียก 

    dev_appserver

    คือการสั่ง 

    dev_appserver.py <ไดเร็กทรอรีของโปรแกรมประยุกต์>

    ถ้าไม่มีข้อผิดพลาดเราสามารถเรียกดูหน้าที่เราแสดงได้ที่ URL http://localhost:8080/

โปรแกรมประมวลผลคำร้องขออย่างง่าย

เมื่อเราเรียก URL ใด ๆ สิ่งที่เกิดขึ้นก็คือจะมีการส่งคำร้องขอ (request) มาที่เว็บเซิร์ฟเวอร์ ในการเขียนเว็บทั่วไปที่ไม่มีการเปลี่ยนแปลง (เช่นเขียนโฮมเพจ) เราอาจเขียนแฟ้ม html ไว้แล้วระบุให้เว็บเซิร์ฟเวอร์คืนข้อมูลในแฟ้มดังกล่าวกลับไปให้บราวเซอร์ที่ร้องของ

อย่างไรก็ตาม หัวใจของการพัฒนาโปรแกรมประยุกต์บนเว็บก็คือการเข้าไปจัดการประมวลผลคำร้องเหล่านี้ โดยเราจะใช้ framework ที่เรียกว่า webapp ซึ่งมาพร้อมกับ App Engine ในการเขียน

โปรแกรมด้านล่างแสดง 

main.py

ที่ตอบสองต่อการร้องขอหน้าเว็บ ที่มี URL เป็น 

/
import wsgiref.handlers

from google.appengine.ext import webapp

class MainPage(webapp.RequestHandler):
    def get(self):
        self.response.headers['Content-Type'] = 'text/plain'
        self.response.out.write('Hello, webapp World!')

def main():
    application = webapp.WSGIApplication([('/', MainPage)],
                                         debug=True)
    wsgiref.handlers.CGIHandler().run(application)

if __name__ == "__main__":
    main()

เราจะเริ่มพิจารณาจากฟังก์ชัน 

main

:

def main():
application = webapp.WSGIApplication([('/', MainPage)],  # เป็นการทำ URL mapping
debug=True)
wsgiref.handlers.CGIHandler().run(application)

ฟังก์ชันดังกล่าวสร้างวัตถุของคลาส 

WSGIApplication

โดยระบุว่า ถ้ามีการเรียกไปที่ URL 

/

ให้วัตถุของคลาส 

MainPage

เป็นผู้ตอบสนอง จากนั้นเรียกให้โปรแกรมทำงานโดยสั่ง

wsgiref.handlers.CGIHandler().run(application)

คลาสที่ประมวลผลคำร้องขอจะต้องสืบทอดคุณสมบัติมาจากคลาส 

webapp.RequestHandler

ในตัวอย่างข้างต้น คือคลาส 

MainPage

คำร้องขอมีสองประเภทคือแบบ GET และแบบ POST ในการเรียกหน้าเว็บทั่วไปจะเป็นการร้องขอแบบ GET ส่วนแบบ POST มักเป็นแบบที่มีการส่งข้อมูลจากฟอร์มให้กับเซิร์ฟเวอร์ ซึ่งเราจะได้เรียนวิธีการเขียนต่อไปเมื่อเราศึกษาเรื่องการจัดการกับฟอร์ม

ดังนั้น เราจะเขียนเมท็อด 

get

ในคลาส 

MainPage

เพื่อประมวลผลคำร้องขอ ในเมท็อดดังกล่าวเราคืนข้อมูลของหน้าเว็บกลับไปโดยอ้างถึงคุณลักษณะ 

self.response

ซึ่งเป็นวัตถุที่แทนการตอบสนองของโปรแกรมที่เราเขียน

แสดงผลด้วย template

เราสามารถเขียนการตอบสนองที่เป็นข้อมูลข้อความในรูปแบบ HTML ลงในโปรแกรมได้ อย่างไรก็ตามการพัฒนาโปรแกรมในลักษณะดังกล่าวทำให้ส่วนของโปรแกรมที่ประมวลผล กับส่วนแสดงผลด้วย HTML นั้นปะปนกัน ทำให้การแก้ไขทำได้ยาก

ในหลาย ๆ ครั้ง เหตุผลเพิ่มเติมที่ทำให้เราต้องการแยกส่วนประมวลผลกับส่วนของการแสดงผลออกจากกัน เพราะว่าเรามักมีกลุ่มนักออกแบบที่พัฒนาหน้าเว็บเพื่อการแสดงผลที่สวยงามโดยเฉพาะแยกออกจากกลุ่มโปรแกรมเมอร์ที่พัฒนาส่วนการประมวลผล

สำหรับการดึงหน้าเว็บที่เขียนแยกไว้มาเป็นคำตอบนั้น App Engine ได้นำไลบรารีของการจัดการ template มาจาก python web framework ที่ชื่อว่า Django

เราทดลองโดยเขียนแฟ้ม 

index.html

ที่มีข้อมูลดังนี้

<h1>Hello, welcome to my world</h1>

ในโปรแกรม 

main.py

ที่แสดงผลแฟ้มดังกล่าวเมื่อมีการร้องขอ URL 

/

มีการปรับเปลี่ยนเล็กน้อย

ขั้นแรกเราต้อง 

import

ไลบรารีของเทมเพลตมาก่อน โดยเพิ่มบรรทัดด้านล่างนี้เข้าไปที่ตอนต้นโปรแกรม

from google.appengine.ext.webapp import template

และแก้เมท็อด 

get

ในคลาส 

MainPage

ดังนี้

class MainPage(webapp.RequestHandler):
    def get(self):
        self.response.out.write(template.render("index.html", {}))

สังเกตว่าแทนที่เราจะส่งข้อความ HTML ไปตรง ๆ เราจะเรียกเมท็อด 

template.render

แทน อาร์กิวเมนต์ที่สองของเมท็อดดังกล่าวเป็นช่องทางที่เราใช้เพื่อส่งข้อมูลให้กับส่วนแสดงผล ซึ่งเปรียบเสมือนโครงว่างเปล่าที่พร้อมจะรับข้อมูลไปแสดง นี่คือสาเหตุที่เราเรียกส่วนนี้ว่า ระบบ template

การส่งตัวแปรให้กับ template

ใน template เราสามารถแสดงค่าของตัวแปรที่ถูกส่งมาได้ โดยเขียนอยู่ในเครื่องหมายวงเล็บปีกกาคู่ เช่น “{{ ตัวแปร }}` พิจารณาตัวอย่างด้านล่างนี้

<h1>Hello</h1>
My name is {{ name }}.

เราสามารถส่งค่าให้กับตัวแปร 

name

ใน template ได้ตอนสั่ง 

template.render

เช่น

        self.response.out.write(template.render("index.html", {'name':'Ronaldo'}))

เราจะทดลองสร้างโปรแกรมประยุกต์บนเว็บที่แสดงคำคมแบบสุ่ม

ขั้นแรกเราออกแบบหน้าเว็บง่าย ๆ ก่อน โดยสร้างแฟ้ม 

quote.html

ดังนี้

<html>
  <body>
    <h1>คำคมของวันนี้</h1>
    {{ quote }} <br/>
    โดย {{ author }}
 </body>
 </html>

ในโปรแกรม 

main.py

ให้เพิ่ม 

import random

(เพราะว่าเราจะใช้คำสั่ง 

random.randint

สำหรับสุ่มเลข) จากนั้นแก้เมท็อด 

get

ให้เป็นดังนี้ (ใส่คำคมเอง)

   class MainPage(webapp.RequestHandler):
    def get(self):
        quotes = [("I disapprove of what you say, "
                   "but I will defend to the death "
                   "your right to say it.","Evelyn Beatrice Hall"),
                  ("Think for yourselves and "
                   "let others enjoy the privilege "
                   "to do so, too.","Voltaire"),
                  ("Nature knows no indecencies; "
                   "man invents them.","Mark Twain"),
                  ("If we don't believe in freedom of expression "
                   "for people we despise, "
                   "we don't believe in it at all.","Noam Chomsky")]

        qnum = random.randint(0,len(quotes)-1)
        quote,author = quotes[qnum]

        self.response.out.write(template.render("quote.html",
                                                {'quote' : quote,
                                                 'author' : author}))

ในลิสต์ของคำคม เราเก็บ tuple (คำคม,ผู้แต่ง) สังเกตว่าในคำสั่ง 

quote,author = quotes[qnum]

เราได้กำหนดค่าแบบขนาน

ปรับการแสดงผลด้วย filter

เราสามารถปรับเปลี่ยนรูปแบบการแสดงผลค่าของตัวแปรได้โดยใช้ filter รูปแบบการใช้ filter คือ 

{{ ตัวแปร|ฟิลเตอร์1|ฟิลเตอร์2|ฟิลเตอร์3 }}

ตัวอย่างของฟิลเตอร์ เช่น 

escape

และ 

linebreaks

ในการแสดงผลข้อมูลแบบข้อความที่ผู้ใช้ป้อนเข้ามานั้น ถ้าในข้อความของผู้ใช้มีเครื่องหมายที่ใช้ในเอกสาร HTML ข้อความดังกล่าวอาจทำให้การแสดงผลผิดพลาดได้ ในกรณีดังกล่าวเราจะต้องเปลี่ยนเครื่องหมายเช่น 

&lt;

เป็น 

&amp;lt;

เราสามารถใช้ฟิลเตอร์ 

escape

ในการเข้ารหัสสตริงได้ ในการแสดงผลโดยทั่วไป เพื่อความปลอดภัยเราควร escape ข้อมูลเสมอ

ในทำนองเดียวกัน กรณีที่ข้อมูลเป็นข้อความหลายบรรทัด เราจำเป็นต้องใส่ html tag 

&lt;br/&gt;

รวมทั้งใส่ tag 

&lt;p&gt;

ฟิลเตอร์ที่ใช้คือ 

linebreaks

ดังนั้นในตัวอย่างของ template ข้างต้น ถ้าเรารับข้อมูลคำคมจากผู้ใช้ เราควรเขียนเป็น

 <html>
   <body>
    <h1>คำคมของวันนี้</h1>
    {{ quote|escape|linebreaks }}<br/>
    โดย {{ author|escape }}
  </body>
 </html>
</div>
<div id="node-35">
<h1>ภาษา template</h1>

ใน template นอกจากจะแสดงค่าของตัวแปรแล้ว ยังมีโครงสร้างควบคุมอื่น ๆ ให้ใช้ (เรียกว่า tag) รายละเอียดที่สมบูรณ์สามารถดูได้จากเอกสารของ Django template เราจะสนใจเฉพาะบาง tag เท่านั้น

สมมติว่าเราต้องสุ่มแสดงรายการคำคมหลาย ๆ ชุด เราจะเปลี่ยนเมท็อด 

MainPage.get

เป็นดังนี้

       def get(self):
        all_quotes = [              # เปลี่ยนชื่อตัวแปร
                      # ... ลบออกเพื่อประหยัดเนื้อที่ ...
                     ]

        quotes = []
        for quote in all_quotes:
            if random.randint(1,5) &lt; 3:     # สุ่มเลือก
                quotes.append(quote)

        self.response.out.write(template.render("quote.html",
                                                {'quotes' : quotes}))

จากนั้นที่ใน template เราตรวจสอบว่าตัวแปร 

quotes

มีข้อมูลหรือไม่ ถ้ามีเราจะ 

for

ไล่ไปในรายการ 

quotes

ด้วยตัวแปร 

quote

สังเกตว่าข้อมูลแต่ละตัวใน 

quotes

เป็น tuple ในการอ้างถึงข้อมูลใน tuple เราจะเขียนเช่น quote[0] หรือ quote[1] ในภาษา template เราจะเขียน 

quote.0

หรือ 

quote.1

แทน

     <h1>รายการคำคม</h1>
      {% if quotes %}
        {% for quote in quotes %}
          {{ quote.0|escape|linebreaks }}<br/>
          โดย {{ quote.1|escape }}
          </hr>
        {% endfor %}
      {% else %}
        ไม่มีคำคม
      {% endif %}

โปรแกรมที่อ่านง่ายขึ้น: การใช้ dictionary

การส่งค่าในโปรแกรมที่แล้ว ทำให้ template เขียนแล้วอ่านไม่รู้เรื่อง แทนที่จะใช้ tuple เราสามารถใช้ dictionary ในการส่งข้อมูลเพื่อทำให้โปรแกรมอ่านง่ายขึ้นมาก

       def get(self):
        all_quotes = [
                      # ละไว้
                     ]

        quotes = []
        for text,author in all_quotes:
            if random.randint(1,5) &lt; 3:
                quotes.append({'text': text, 'author': author})  # ใช้ dictionary

        self.response.out.write(template.render("quote.html",
                                                {'quotes' : quotes}))

ใน template เราสามารถอ้าง 

quote.text

และ 

quote.author

ได้เลย

     <h1>รายการคำคม</h1>
      {% if quotes %}
        {% for quote in quotes %}
          {{ quote.text|escape|linebreaks }}<br/>
          โดย {{ quote.author|escape }}
          </hr>
        {% endfor %}
      {% else %}
        ไม่มีคำคม
      {% endif %}

รับข้อมูลด้วย form

ในส่วนนี้เราจะทดลองประมวลผลข้อมูลที่รับจากผู้ใช้ผ่านทางฟอร์ม

รูปแบบทั่วไปของฟอร์ม html ที่เราใช้คือ

<form action="ลิงก์" method="post">
  ...
  <input type="submit" value="ส่งข้อมูล"/>
<form>

เราจะทดลองทำโปรแกรมบวกเลขง่าย ๆ ให้เริ่มสร้างโปรแกรมใหม่

สร้าง template ที่รับค่าสองค่าจากผู้ใช้ เก็บไว้ในแฟ้ม 

index.html

ให้สังเกตการระบุข้อมูลของ tag 

from

เราระบุ URL สำหรับรับข้อมูลที่ 

/add

และเราใช้วิธีการส่งแบบ POST

     <form action="/add" method="post">
      First value: <input name="x" type="text"/></br>
      Second value: <input name="y" type="text"/></br>
      <input type="submit" value="ADD"/>
     </form>

จากนั้นเราไปสร้างแฟ้ม 

main.py

เพื่อประมวลผลข้อมูลที่อ่านได้ดังด้านล่าง

import random
import wsgiref.handlers

from google.appengine.ext.webapp import template
from google.appengine.ext import webapp

class MainPage(webapp.RequestHandler):
    def get(self):
        self.response.out.write(template.render("index.html", {}))

class AddPage(webapp.RequestHandler):
    def post(self):
        self.response.out.write('You ask ' +
                                self.request.get('x') + ' + ' +
                                self.request.get('y'))

def main():
    application = webapp.WSGIApplication([('/', MainPage),
                                          ('/add',AddPage)],
                                         debug=True)
    wsgiref.handlers.CGIHandler().run(application)

if __name__ == "__main__":
    main()

สิ่งที่เราเพิ่มขึ้นมาใน 

main.py

มีหลายส่วน ขั้นแรกเราไปเพิ่มคลาส 

AddPage

และไปลงทะเบียนว่าคลาสนี้จะรับการร้องขอที่ URL 

/add

ในส่วนของโปรแกรมด้านล่าง

    application = webapp.WSGIApplication([('/', MainPage),
                                          ('/add',AddPage)],  # เราระบุกับ webapp ว่าให้คลาส
                                                              # AddPage จัดการ url /add
                                         debug=True)

จากนั้นในคลาส 

AddPage

เราเขียนเมท็อด 

post

เพื่อรับข้อมูลที่ส่งมาจากฟอร์ม ในการอ่านค่าข้อมูลที่ส่งมาจากฟอร์มนั้น เราจะเรียกผ่านทางคุณลักษณะ 

self.request

โดยสั่งเมท็อด 

get('ชื่ออินพุต')

ดังเช่นในตัวอย่างข้างบนเราสั่ง 

self.request.get('x')

เพื่ออ่านค่าอินพุตช่องที่เราให้ชื่อว่า 

x

ที่เราระบุใน template html บรรทัดนี้:

      First value: <input name="x" type="text"/><br/>

เก็บข้อมูลด้วย datastore

ในส่วนนี้เราจะศึกษาวิธีการเก็บข้อมูลแบบถาวร โดย API ในส่วนนี้เรียกว่า datastore เราจะพัฒนาเว็บสำหรับรำพึงรำพัน ที่สามารถกดให้คะแนนได้

ในการพัฒนาโปรแกรมประยุกต์ด้วย App Engine จะแบ่งระบบออกเป็นสามส่วนใหญ่ ๆ คือ

  1. ส่วนของโมเดล (Model) ที่ใช้จัดการกับข้อมูลและตรรกเชิงธุรกิจของระบบ
  2. ส่วนของ template (Template) ซึ่งแสดงข้อมูลในหน้าเว็บที่สวยงาม
  3. ส่วนประมวลผลซึ่งเป็นส่วนที่ใช้จัดการประมวลผลข้อมูล, เลือก template และเตรียมข้อมูลให้ template นำไปแสดงผล (ส่วนนี้คือส่วนของ handler ที่เราเขียน)

ไลบรารี datastore จะจัดการในส่วนของโมเดล ข้อมูลแต่ละหน่วยที่เก็บใน datastore จะถูกเรียกว่า entity ซึ่ง entity แต่ละประเภท (kind) จะนิยามด้วยโมเดล เราสามารถมอง entity เป็นวัตถุของคลาสซึ่งก็คือโมเดลที่นิยาม entity นั้นนั่นเอง

ในเว็บรำพึงรำพันเราจะเก็บการรำพึงรำพัน ดังนั้นเราจะต้องนิยามโมเดลของการรำพึงรำพันก่อน จะนิยามโมเดลได้เราต้อง import db มาเสียก่อน ในแฟ้ม 

main.py

เราเพิ่มบรรทัดด้านล่างไว้ตอนต้นแฟ้ม

from google.appengine.ext import db

โมเดลของเราจะต้องเป็นคลาสที่สืบทอดมาจาก 

db.Model

และนิยามคุณลักษณะของ entity ในโมเดลนั้นลงไป ในขั้นแรกเราจะนิยามโมเดล Thought เพื่อเก็บคำรำพัน ดังนี้

class Thought(db.Model):
    body = db.StringProperty(multiline=True)

เราจะค่อย ๆ แก้ไขโมเดลดังกล่าวไปเรื่อย ๆ ตามความสามารถของเว็บที่เพิ่มขึ้น

การเพิ่มข้อมูลและการอ่านข้อมูลทั้งหมด

ในส่วนนี้เราจะแสดงวิธีการสร้าง entity ใหม่ใน datastore, การเรียกค้นข้อมูลทั้งหมด และแสดงตัวอย่างของความจำเป็นในการ escape ข้อมูลในการแสดงผล

การสร้างและจัดเก็บ entity ใหม่

เราเริ่มโดยการสร้างโปรแกรมที่แสดง template 

index.html

เมื่อเรียก url 

/

ใส่ฟอร์มใน template 

index.html

ดังนี้

<h1>Thought Board</h1>

<form action="/post" method="post">
  <textarea name="thought_body" rows="5" cols="30"</textarea><br/>
  <input type="submit" value="Say it!"/>
<form>

สังเกตว่าเราต้องเขียน request handler สำหรับ url 

/post

เราสร้าง

class ThoughtPost(webapp.RequestHandler):
    def post(self):
        if self.request.get('thought_body')!='':
            th = Thought(body=self.request.get('thought_body'))  # สร้างและกำหนดค่า
            th.put()                                             # จัดเก็บ
        self.redirect("/")            # ให้บราวเซอร์กลับไปหน้า

เมท็อดดังกล่าวรับค่า 

thought_body

จากฟอร์ม จากนั้นสร้างวัตถุของคลาส 

Thought

เราจะเรียกเมท็อด 

put()

ของวัตถุดังกล่าวเพื่อเก็บค่าลงใน datastore สังเกตว่าเรา redirect ผู้ใช้กลับไปยังหน้า 

/

จะใช้ handler ดังกล่าวได้ต้องลงทะเบียนตัวจัดการคำร้องขอ ใน 

main()

เราเขียน

def main():
    application = webapp.WSGIApplication([('/', MainPage),
                                          ('/post', ThoughtPost)],
                                         debug=True)
    wsgiref.handlers.CGIHandler().run(application)

จากนั้นให้ทดลองป้อนการรำพันในฟอร์มดังกล่าว หลังจากส่งฟอร์มแล้วโปรแกรมจะนำเรากลับมาที่ฟอร์มว่างเปล่าเช่นเดิม อย่างไรก็ตาม ถ้าเราเขียนถูกต้องข้อมูลของเราจะถูกจัดเก็บใน datastore แล้ว เราจะใช้ส่วนเว็บ Admin ที่ระบบพัฒนาของ App Engine ได้จัดเตรียมไว้ให้ตรวจสอบ/แก้ไขข้อมูลในโปรแกรมของเรา ให้เข้าไปที่ url

http://localhost:8080/_ah/admin/datastore

จากนั้นในหน้า Datastore Viewer ให้ป้อน Entity Kind เป็น Thought เราจะเห็นข้อมูลที่เราทดลองป้อนเข้าไปในนั้น

แสดงคำรำพันทั้งหมด(select all)

เราจะเพิ่มส่วนแสดงคำรำพัน ในส่วน handler ของ url 

/

เราจะอ่านข้อมูลคำรำพันทั้งหมดออกมาแล้วส่งไปแสดงใน template 

index.html

เราสามารถเรียกค้น entity ทั้งหมดของโมเดลออกมาได้โดยเรียกเมท็อด 

all()

ของโมเดล ในเมท็อด 

get

ด้านล่างเราจะสั่ง 

Thought.all()

เพื่ออ่านคำรำพันทั้งหมด ผลลัพธ์ที่ได้จะมีลักษณะเหมือนกลับลิสต์ของวัตถุคลาส Thought จากนั้นเราจะส่งให้ template แสดงผ่านทางตัวแปร 

thoughts
class MainPage(webapp.RequestHandler):
    def get(self):
        thoughts = Thought.all()                  # ค้น entity ประเภท Thought ทั้งหมด
        self.response.out.write(template.render("index.html",
                                                {'thoughts': thoughts }))

จากนั้นเราจะเพิ่มบรรทัดต่อไปนี้ใน 

index.html
{% for thought in thoughts %}
  {{ thought.body }}
  </hr>
{% endfor %}

ซึ่งวิ่งไปในรายการ 

thoughts

แล้วแสดง 

body

ของแต่ละวัตถุ

การแสดงผลข้อมูลแบบข้อความ

ให้ทดลองป้อนคำรำพันหลายบรรทัด รวมทั้งใช้เครื่องหมายเช่น < หรือ & หรือใส่ tag ในคำรำพัน

สังเกตว่าเมื่อแสดงผลออกมา เครื่องหมายที่ผู้ใช้ใส่ไปในข้อความจะไปปะปนกับรวมถึงสามารถเปลี่ยนแปลงการจัดการหน้าเว็บของเราได้

ดังนั้นอย่างที่เคยได้กล่าวไว้แล้ว ในการแสดงผลข้อความเราควรจัดการเข้ารหัสตัวอักษรดังกล่าวให้อยู่ในรูปแบบสำหรับการแสดงผลด้วย html เสียก่อน โดยใช้ filter ชื่อ 

escape

และ 

linebreaks

เพื่อเข้ารหัสตัวอักษรและเปลี่ยนเครื่องหมายขึ้นบรรทัดใหม่ให้เป็น 

&lt;br/&gt;

ดังนั้นในการแสดง 

thought.body

ใน template 

index.html

เราจะเขียนเป็น

  {{ thought.body|escape|linebreaks }}

เพิ่ม/ลด คะแนน(update): การอ้างถึงข้อมูลใน datastore (select by id)

เพิ่มคุณสมบัติในโมเดล

class Thought(db.Model):
    body = db.StringProperty(multiline=True)
    author = db.StringProperty()
    score = db.IntegerProperty()

ใน template 

index.html

เราต้องแสดงคะแนนและลิงก์สำหรับเพิ่ม/ลดคะแนน สังเกตว่าการเพิ่มและลดคะแนนนั้นเราต้องระบุด้วยว่าเราจะเพิ่มหรือลดคะแนนคำรำพันใด ดังนั้นการเรียกไปที่ url เช่น 

/plus

นั้นให้ข้อมูลไม่เพียงพอกับตัวประมวลผล ดังนั้นเราจำเป็นต้องมีวิธีบางอย่างในการระบุ entity ที่เราสนใจใน datastore

entity ใด ๆ ใน datastore จะมีคุณสมบัติ 

key

ซึ่งไม่ซ้ำกับ entity อื่น ๆ ในโปรแกรมประยุกต์ของเรา ปัญหาก็คือ 

key

นั้นค่อนข้างยาวและดูไม่รู้เรื่อง

นอกจาก 

key

แล้ว ทุก ๆ entity จะมี ID ที่เป็นตัวเลขไม่ซ้ำกัน นอกจากนี้สำหรับ entity ประเภทหนึ่ง ๆ ค่า ID จะไม่ซ้ำกัน เราอ้างถึง ID ของ entity ใด ๆ ผ่านทาง 

key

ของ entity นั้น โดยเรียกเช่น

ตัวแปร.key().id()

การเรียกเมท็อดดังกล่าวในภาษา template ทำโดยการเขียนเหมือนการเรียกคุณสมบัติ ดังนั้นแสดง ID ของวัตถุเขียนเป็น 

{{ ตัวแปร.key.id }}

ดังนั้นส่วนแสดงคะแนนและแสดงลิงก์สำหรับเพิ่ม/ลดคะแนนจะเป็นดังนี้

{% for thought in thoughts %}
  {{ thought.body|escape|linebreaks }}
  Author: {{ thought.author|escape }}</br>
  Score: {{ thought.score }}
  [<a href="/adjust/plus/{{ thought.key.id }}">good</a>]
  [<a href="/adjust/minus/{{ thought.key.id }}">bad</a>]
  </hr>
{% endfor %}

นอกจากนี้ในส่วนเพิ่ม Thought (เมท็อด 

post

ในคลาส 

ThoughtPost

) อย่าลืมกำหนดค่าเริ่มต้นของ score ให้เป็น 0 ด้วย ดังเช่น

     th = Thought(... ละไว้ ...,score=0)     # เพิ่มการกำหนดค่าเริ่มต้น

การลบข้อมูล (select by id and delete)

employee = Employee.get_by_id(int(id))
employee.delete()

URL mapping

โปรแกรมประมวลผลคำร้องขอจะต้อง "แกะ" คำสั่ง (เป็น plus หรือ minus) และหมายเลข ID จาก URL เพื่อนำมาใช้เป็นข้อมูลในการประมวลผล

ใน webapp เราสามารถกำหนดการแยก url ดังกล่าวลงไปเมื่อตอนลงทะเบียน handler ได้เลย ในส่วนของโปรแกรมประมวลผลการปรับคะแนนข้างต้น เราลงทะเบียน handler ดังนี้

def main():
    application = webapp.WSGIApplication([('/', MainPage),
                                          ('/post', ThoughtPost),
                                          (r'/adjust/(.*)/(.*)',ScoreAdjust)],  # ****
                                         debug=True)
    wsgiref.handlers.CGIHandler().run(application)

สังเกตว่าเราใส่ regular expression ที่มีการแกะตัวแปรสองตัวในส่วนลงทะเบียนของ 

ScoreAdjust

ใน Python สตริงที่ขึ้นต้นด้วยตัว 

r

เรียกว่า raw string เป็นสตริงที่จะไม่ตีความเครื่องหมาย 

\

เป็นเครื่องหมายพิเศษ มักใช้ตอนเขียน regular expression

ตัวแปรสองตัวที่แกะได้จะถูกส่งให้กับเมท็อดที่ประมวลผลคำร้องขอ สังเกตโปรแกรมด้านล่าง เราประกาศตัวแปร 

op

และ 

thought_id

เพื่อรับพารามิเตอร์ดังกล่าว

class ScoreAdjust(webapp.RequestHandler):
    def get(self,op,thought_id):           # takes 2 arguments
        th = Thought.get_by_id(int(thought_id))
        if op=='plus':
            th.score += 1
        elif op=='minus':
            th.score -= 1
        th.put()
        self.redirect("/")

ในโปรแกรมด้านบนเมท็อดที่เราใช้ในการเรียกค้น entity จาก ID คือ 

get_by_id

เนื้อหาเพิ่มเติม

Static files

ถ้าคุณมีแฟ้มที่ต้องการจะส่งให้กับผู้ใช้ เช่น รูป โปรแกรม dev_appserver โดยปกติจะไม่ส่งแฟ้มนั้นกลับไปยังบราวเซอร์ หรือพูดง่ายๆก็คือถ้าจะมีโหลด images,css,js จะต้องตั้งค่า handler map url ก่อน คุณสามารถตั้งค่าให้ app engine คืนแฟ้มเหล่านั้นได้โดยตั้ง handler ในแฟ้ม 

app.yaml

ตามตัวอย่างในหน้า Using Static Files

การค้นข้อมูลที่เก็บใน datastore

คุณสามารถสั่ง 

ประเภท.all()

เพื่ออ้างถึงทุก ๆ entity ในข้อมูลประเภทหนึ่ง ๆ ได้ จากนี้เราสามารถเลือกข้อมูลเป็นตัว ๆ จากนั้นได้ โดยใช้เมท็อด 

filter

ดูตัวอย่างและคำสั่งจาก Datastore API

คุณสามารถใช้เมท็อด 

count

เพื่อหาจำนวนข้อมูลที่ค้นได้ ตัวอย่างการใช้งานเช่น ถ้าเรามีโมเดล

class User(db.model):
  login = db.StringProperty()
  password = db.StringProperty()</pre>

เราสามารถตรวจว่า User ที่มี login="guest" มี password เท่ากับ hello หรือไม่ได้เช่น
  [cc lang="python"]
  login = "guest"
  password = "hello"
  users = User.all()
  users.filter("login = ",login)
  if users.count()==0:
    # can't find any user with login="guest"
    ...โปรแกรมของคุณ
  elif users[0].password==password:   # ใช้ entity แรกที่เจอ
    # correct password... do something
    ...โปรแกรมของคุณ
  else:
    # incorrect password
    ...โปรแกรมของคุณ</pre>

Session management

ถ้าต้องการรักษาสถานะของผู้ใช้ เช่นจัดการการ login ของ user เป็นต้น จะต้องใช้ session สามารถดาวน์โหลดไลบรารีจัดการดังกล่าวได้จาก appengine-utilities ในส่วน Session (ดูตัวอย่างเพิ่มเติมที่ หน้าตัวอย่าง)

File upload

ขั้นตอนคร่าว ๆ

  • เพิ่ม option 
    enctype="multipart/form-data"

    ลงใน form tag

  • อ่านข้อมูลของแฟ้มที่ upload ได้จาก
    • ข้อมูล: 
      self.request.POST.get('file').file.read()
    • ชื่อแฟ้ม: 
      self.request.POST.get('file').filename

ดูตัวอย่างได้จาก หน้านี้

Using User Service Google App Engine provides several useful services based on Google infrastructure, accessible by applications using libraries included with the SDK. One such service is the Users service, which lets your application integrate with Google user accounts. With the Users service, your users can use the Google accounts they already have to sign in to your application. ก็คือคุณสามารถเขียนติดต่อใช้งาน API google account ได้นั่นเอง

User Service Example

การใช้งานหน้า Admin ของ dev_appserver

URL ของหน้า Admin คือ http://localhost:8080/_ah/admin/datastore เราสามารถเข้าไปทดลองโปรแกรมภาษา Python ที่เรียกใช้ datastore โดยตรงได้จากหน้า Interactive Console ก่อนการเรียกใช้งานคลาสของเราได้ เราจะต้อง import เสียก่อน เช่น

from main import Thought

ในส่วนของ Datastore viewer ในหน้า Admin จะมีปัญหาเล็กน้อยในการสร้าง entitiy ใหม่ในกรณีที่ประเภทนั้นยังไม่มี entity มาก่อน เราจะต้องใช้หน้า Interactive Console ในการสร้าง entity แรกขึ้นมา

ตัวอย่างของโปรแกรมที่สร้าง entity 

Thought

ที่ไม่มีข้อมูลอะไร เช่น

from main import Thought
th = Thought()
th.put()

การใช้งาน datastore ถาวรใน dev_appserver

ถ้าคุณมีข้อมูลบางส่วนของโปรแกรมประยุกต์ที่ต้องการเก็บไว้ใน data store ในการเรียก dev_appserver ปกติจะสร้างแฟ้มที่เก็บ datastore ไว้ในส่วนเนื้อที่ชั่วคราว ทำให้เมื่อ boot เครื่องใหม่แล้วข้อมูลจะหายไป เราสามารถกำหนดแฟ้มข้อมูลที่แน่นอนสำหรับเก็บ datastore ได้ โดยเพิ่ม option 

--datastore_path=ชื่อแฟ้ม

เมื่อเรียก 

dev_appserver

ข้อมูลทั้งหมดที่เราเก็บด้วย datastore จะถูกเก็บในแฟ้มนี้และจะไม่หายไปโดยอัตโนมัติอีก

Related posts:

This entry was posted in python. Bookmark the permalink.