You are hereBlogs > jittat's blog > มาสร้าง app สำหรับอัพโหลดรูปกัน (3)

มาสร้าง app สำหรับอัพโหลดรูปกัน (3)


By jittat - Posted on 23 September 2008

ต่อจากตอนที่แล้วนะครับ

ในตอนนี้เราจะหัดใช้ form ครับ ในตอนต้นเราจะเขียน form แบบธรรมดาเพื่อทดลองใช้ form และทดลองเก็บข้อมูลลงในโมเดล จากนั้นก่อนจะจบเราจะทดลองใช้ ModelForm ซึ่งทำให้การสร้างและใช้งานฟอร์มจากโมเดลทำได้ง่ายมากขึ้น

Django มีโมดูล forms ที่ทำให้การเขียน form ที่ต้องมีการตรวจสอบข้อมูลทำได้สะดวกครับ

การใช้งานหลัก ๆ คือเราไปสร้างคลาสที่เป็นลูกหลานของ forms.Form วัตถุของคลาสดังกล่าว เมื่อนำไปแสดงใน template จะเผยตัวออกเป็น html form ที่สวยงาม จากนั้นเมื่อผู้ใช้ป้อนข้อมูลเข้ามาผ่านทาง form นั้น เราก็สามารถเอาเชื่อมข้อมูลที่ผู้ใช้ส่งมาจาก request เข้ากับวัตถุของ form เพื่อนำไปตรวจสอบความถูกต้องได้ เมื่อตรวจเสร็จเราก็จะมีข้อมูลที่เก็บกวาดแล้วไว้ใช้ (เรียกว่า cleaned_data)

เรามาประกาศ form สำหรับรับรูปกัน เพิ่มการประกาศนี้ไว้ที่ต้นไฟล์ pictures/views.py ครับ

from django import forms

class PictureForm(forms.Form):
    title = forms.CharField(max_length=100)
    description = forms.CharField(widget=forms.Textarea)
    image = forms.ImageField()

สังเกตว่าคล้ายกับการประกาศโมเดลมากครับ ยกเว้นว่าไม่มี TextField แต่เราจะใช้ forms.CharField แทน แล้วระบุว่าในเวลาแสดงผลเป็น html ให้แสดงเป็น textarea โดยกำหนดค่า widget=forms.Textarea เจ้า widget นี่คือวัตถุที่ใช้จัดการแสดงผลแต่ละ field ใน template ครับ

มีฟอร์มแล้วก็ต้องส่งไปแสดงผลที่ template

เราจะให้ผู้ใช้ upload ได้ที่หน้าแรกเลย ดังนั้นไปแก้ฟังก์ชัน index กัน โดยเพิ่มให้มีการสร้างวัตถุของคลาส PictureForm ให้เป็นฟอร์มเปล่า ๆ แล้วโยนไปให้ template แสดงผล ผ่านทางตัวแปรชื่อ form

def index(request):
    pics = Picture.objects.all().order_by('title')
    form = PictureForm()
    return render_to_response("index.html",{ 'pictures': pics,
                                             'form': form })

จากนั้นไปแก้ template index.html จากคราวที่แล้วครับ

โดยเพิ่มส่วน form เข้าไปในตอนต้นครับ

<form action="/upload/" method="POST" enctype="multipart/form-data">
  {{ form.as_p }}
<input type="submit" value="Upload"/>
</form>

สังเกตว่าการเขียน tag form ก็เขียนตามปกติ (อย่าลืม enctype="multipart/form-data") แต่ว่าส่วนที่แสดงผลฟิลด์ต่าง ๆ ในฟอร์มเราสั่งให้แสดง form.as_p ได้เลย ลองเรียก python manage.py runserver ขึ้นมาดูหน้าตากันนะครับ สังเกตว่าฟอร์มจะเบี้ยว ๆ หน่อย ถ้าเข้าไปดูที่ html จะเห็นว่าจาก form.as_p จะได้ html ประมาณนี้ครับ

<p><label for="id_title">Title:</label> <input id="id_title" type="text" name="title" maxlength="100" /></p>
<p><label for="id_description">Description:</label> <textarea id="id_description" rows="10" cols="40" name="description"></textarea></p>
<p><label for="id_image">Image:</label> <input type="file" name="image" id="id_image" /></p>

ถ้าเราไม่ชอบเบี้ยว ๆ ก็แก้ให้แสดงเป็นตารางก็ได้ครับ แก้เป็น

<form action="/upload/" method="POST" enctype="multipart/form-data">
  <table>
  {{ form.as_table }}
  </table>
  <input type="submit" value="upload"/>
</form>

พอได้หน้า template แล้ว เราก็ไปเขียนตัวจัดการกับการ upload เลยครับ

เราจะเขียนฟังก์ชัน upload ให้จัดการกับ url upload/ ดังนั้นไปเพิ่มบรรทัดด้านล่างในไฟล์ urls.py

    (r'^upload/$','youpics.pictures.views.upload'),

ส่วนโค้ดของ upload อยู่ด้านล่างครับ

def upload(request):
    if request.method == 'POST':
        # ผูก form เข้ากับข้อมูลที่ได้รับมา
        form = PictureForm(request.POST, request.FILES)
        if form.is_valid():  # ตรวจสอบข้อมูลในฟอร์ม
            # เอาค่าที่เก็บกวาดแล้วมาใช้
            title = form.cleaned_data['title']
            desc = form.cleaned_data['description']
            imagefile = form.cleaned_data['image']

            # สร้างรูปใหม่ แล้วเก็บลงฐานข้อมูล
            new_picture = Picture()
            new_picture.title = title
            new_picture.description = desc
            new_picture.image.save(imagefile.name,imagefile)
            new_picture.save()
    return HttpResponseRedirect('/')

หลัก ๆ คือถ้าผู้ใช้ส่ง request มาแบบ POST (คือส่งฟอร์มมา) เราก็จะผูกวัตถุของ PictureForm เข้ากับข้อมูลที่ได้รับ อันนี้พิเศษหน่อยที่เราต้องมี request.FILES ด้วย เพราะว่าเรามีการส่งไฟล์มา

จากนั้นเรียกเมท็อด is_valid เพื่อตรวจสอบ/เก็บกวาดข้อมูลใน form ถ้าถูกแล้วเราจะได้ข้อมูลไว้ใช้เป็น dictionary ชื่อ cleaned_data (ในการตรวจสอบความถูกต้องนี้เราสามารถเขียนเพิ่มเติมเกี่ยวกับเงื่อนได้)

ตอนท้าย ๆ เป็นการสร้างวัตถุใหม่ในโมเดล แล้วเก็บลงฐานข้อมูลครับ เราเริ่มสร้างวัตถุของคลาส Picture แล้วก็กำหนดค่าให้กับแต่ละฟิลด์เลย ที่ลำบากหน่อยคือส่วนของการเก็บรูป เพราะว่าเราต้องเก็บลงไปในที่ที่ตั้งไว้ ในขณะค่าในตัวแปร image ที่ได้มาจะอิงกับข้อมูลที่ผู้ใช้ป้อน ซึ่งอาจจะเก็บอยู่ในไดเร็กทอรีไฟล์ชั่วคราว ดังนั้นจะเอามาใช้ก็ต้องสั่ง save เอาเองที่ฟิลด์นั้นครับ (ในเอกสารเก่าหน่อยจะให้สั่ง save_FIELD_file แต่มัน deprecated ไปแล้ว)

สุดท้ายเราสั่ง HttpResponseRedirect ให้ browser redirect กลับไปหน้าแรก จะสั่งฟังก์ชันนี้ได้อย่าลืมใส่บรรทัดด้านล่างไว้ตรงหัว views.py นะครับ

from django.http import HttpResponseRedirect

เท่านี้ก็เรียบร้อยครับ ทดลองเล่นได้ครับ

ที่เราทดลองมานี่เป็นการทำแบบอ้อม ๆ ครับเพื่อแสดงให้เห็นการใช้งาน Form อย่างง่ายครับ แต่จริง ๆ แล้ว ถ้าเราต้องการสร้างฟอร์มจากโมเดล เรามีคลาสช่วยเหลืออย่าง ModelForm ทำให้เขียนง่ายขึ้นมากครับ ด้านล่างเป็น views.py ในส่วนประกาศ PictureForm และฟังก์ชัน upload ที่เขียนโดยใช้ ModelForm ครับ

class PictureForm(ModelForm):
    class Meta:
        model = Picture  # สร้าง form จากโมเดล Picture

def upload(request):
    if request.method == 'POST':
        # ผูกฟอร์มเข้ากับข้อมูล
        form = PictureForm(request.POST, request.FILES)
        if form.is_valid():    # ทดสอบความถูกต้อง
            form.save()        # เก็บ
    return HttpResponseRedirect('/')

ส่วนที่หายไปก็คือการประกาศฟิลด์ตามโมเดล และส่วนการจัดการเรื่อง save ครับ

ที่นี้ก่อนจบมาเก็บกวาดกันต่ออีกสักนิดครับ

สังเกตว่าในโมเดลเรามีฟิลด์ description แต่ไม่ได้เอาไปแสดงผลเลย ถ้าต้องการแสดงก็แก้ส่วน template โดยเพิ่มให้แสดง {{ picture.description }} เข้าไปนะครับ ทีนี้ ถ้าเราพิมพ์หลาย ๆ บรรทัดแล้วเราต้องการให้มีการแปลงเครื่องหมายขึ้นบรรทัดใหม่ให้เป็น <br> ให้ใส่เป็น {{ picture.description|linebreaks }} แทน ส่วน |linebreaks เป็น filter ให้เราปรับแต่งการแสดงผลของตัวแปรได้ครับ

ปรับเอาตามชอบเลยนะครับ ตัวอย่างด้านล่างนี่แสดงรูป (ย่อเหลือความกว้าง 100) แล้วแสดงคำอธิบายข้าง ๆ ครับ

{% for picture in pictures %}
  <h3>{{ picture.title }}</h3>
  <table>
    <tr><td>
      <img src="{{ picture.image.url }}" width="100"/>
    </td><td>
      {{ picture.description|linebreaks }}
    </td></tr>
  </table>
{% endfor %}

ส่วนของ tutorial การเขียน app เพื่ออัพโหลดรูปคงจบไว้คร่าว ๆ แค่นี้ครับ (อาจมีตอนต่อไปอีกได้ เกี่ยวกับการทำ user และทำเรื่องการ follow)

เอกสารเพิ่มเติม

  • ดูรายละเอียดเกี่ยวกับ file upload
  • เอกสาร ModelForm