การสร้าง project ด้วย django

9coding official
Jul 27, 2017 · 5 min read

Demo ของเราระบบ e-Exam

เพื่อให้ท่านผู้อ่านเห็นภาพได้ดีขึ้น และมีผลงานที่จับต้องได้หลังจากจบบทความนี้แล้ว เราจะมาทำโปรเจ็กต์ง่ายๆ กันซักตัวดีกว่าครับ ซึ่งจะเป็นระบบออกข้อสอบและตรวจข้อสอบแบบออนไลน์ (e-Exam) โดยมีขอบเขตงานคร่าวๆ ดังต่อไปนี้ครับ

Admin

- สามารถเพิ่ม แก้ไข หรือลบข้อสอบ (exam) ได้
- สามารถเพิ่ม แก้ไข หรือลบโจทย์คำถาม (quiz) ของแต่ละข้อสอบได้
- โจทย์คำถามสามารถเป็นได้ทั้งข้อความและรูปภาพ
- โจทย์คำถามประกอบไปด้วยตัวเลือกของคำตอบ (choice) สามารถเพิ่ม แก้ไข หรือลบคำตอบได้
- ตัวเลือกคำตอบไม่จำเป็นจะต้องเท่ากัน เช่น บางคำถามอาจจะมี 4 ตัวเลือก ส่วนอีกคำถามอาจจะมี 5 ตัวเลือกก็ได้
- คำถามหนึ่งๆ สามารถกำหนดตัวเลือกคำตอบที่ถูกได้เพียงข้อเดียวเท่านั้น

User

- เลือกข้อสอบที่จะทำได้
- อ่านโจทย์คำถาม และเลือกตัวเลือกคำตอบ
- เมื่อเสร็จแล้วกดปุ่ม “ส่งคำตอบ” ระบบจะคำนวณคะแนนที่ได้ และรายงานต่อยูสเซอร์คนนั้

มาเริ่มสร้างโปรเจ็กต์กันเลย!

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

$ cd vbox
$ Script\activate
$ (vbox) django-admin startproject eexam
$ (vbox) cd eexam
$ (vbox) python manage.py runserver

รู้จักกับแอพ (apps)

แอพในความหมายของจังโก้ ก็คือโมดูลย่อยๆ นั่นเอง ดังนั้นถ้าหนึ่งโปรเจ็กต์ของเราประกอบไปด้วยระบบย่อยต่างๆ ระบบเหล่านั้นจะเรียกว่าเป็น “แอพ” นั่นเอง ขอยกตัวอย่างเพื่อให้เข้าใจง่ายขึ้น เช่น ถ้าเราจะทำโปรเจ็กต์ระบบขายสินค้าออนไลน์ ก็จะประกอบด้วยแอพต่างๆ ได้แก่ สินค้า, สมาชิก, ตะกร้าสินค้า และการชำระเงิน เป็นต้น
เนื่องจากระบบของเราเป็นเพียงเดโม ที่ไม่ได้มีความซับซ้อนมากนัก ดังนั้นจึงสร้างแค่แอพเดียวก็เพียงพอ โดยขอตั้งชื่อว่า “myapp” ละกัน เพราะชื่อดูกลางๆดีครับ คำสั่งในการสร้างแอพของจังโก้มีรูปแบบดังนี้คือ python manage.py startapp [app name] ตามคำสั่งดังต่อไปนี้

$ (vbox) python manage.py startapp myapp

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

ขั้นตอนต่อมาของการสร้างแอพ คือการผูก หรือลงทะเบียนแอพใหม่ของเราเพื่อให้จังโก้รู้จัก วิธีการทำคือไปที่ eexam/settings.py แล้วเพิ่มชื่อแอพใหม่ของเราเข้าไปที่ INSTALLED_APPS ดังต่อไปนี้

INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'myapp',
)

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

รู้จักกับโมเดล (models)

โมเดล (models) คือโครงสร้างของกลุ่มข้อมูล ที่เกี่ยวข้องกับเรื่องที่เรากำลังสนใจอยู่ ยกตัวอย่างเช่น ถ้าเราสนใจเรื่องของรถยนต์ เจ้าตัวโมเดลของมันก็จะประกอบด้วยรถยนต์, อู่, ประวัติการซ่อมบำรุง เป็นต้น และข้อมูลที่อยู่ในโมเดลจะต้องมีความสัมพันธ์กันทางใดทางหนึ่งอีกด้วย

มาดูโมเดลในระบบ eexam ของเราจาก requirements ที่ได้มา เราสามารถจำแนกออกมาได้ 3 กลุ่ม (หรือ table) ดังต่อไปนี้ ข้อสอบ (exam), คำถาม (quiz) และตัวเลือก (choice) แล้วความสัมพันธ์ของสิ่งเหล่านี้ล่ะ มันเป็นอย่างไร? คำตอบง่ายมากนั่นก็คือ ข้อสอบหนึ่งแผ่นมีหลายคำถาม (one-to-many) และคำถามหนึ่งข้อก็มีหลายตัวเลือก (one-to-many) นั่นเอง ซึ่งเขียนเป็นไดอะแกรมได้ดังนี้

ต่อไปเราจะมาหา attribute ของแต่ละตัวโดยที่ Exam จะมี { ชื่อข้อสอบ, คำอธิบาย, วันที่สร้าง, วันที่ปรับปรุง }, Quiz จะมี { ข้อสอบ (FK), คำถาม, รูปภาพ } และสุดท้าย Choice จะมี { คำถาม (FK), ตัวเลือก, คำตอบ } เป็นต้น

เริ่มต้นสร้างโมเดลในจังโก้

หลังจากที่ออกแบบเรียบร้อย เราก็จะมาสร้างโมเดลในจังโก้กันนะครับ เริ่มต้นไปเปิดไฟล์ myapp/models.py จากนั้นเริ่มสร้างโมเดลของ Exam ลงไปดังต่อไปนี้

from django.db import modelsclass Exam(models.Model):
name = models.CharField(max_length=150)
description = models.TextField(null=True, blank=True)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
def __unicode__(self):
return self.name

โดยที่ name คือชื่อข้อสอบ กำหนดให้เป็นชนิดข้อมูลแบบข้อความ และมีขนาด 150 ตัวอักษร, ส่วน description มีชนิดเป็นเท็กซ์ (ข้อความขนาดใหญ่) และกำหนดให้รับค่าว่างได้, created คือวันที่สร้างข้อสอบ กำหนดให้เพิ่มวันที่ปัจจุบันลงโดยอัตโนมัติเมื่อเกิดการเพิ่มข้อสอบ, สุดท้าย updated คือวันที่ปรับปรุงข้อสอบ เรากำหนดให้ปรับปรุงเป็นวันเวลาปัจจุบันโดยอัตโนมัติ เมื่อมีการแก้ไขปรับปรุงข้อสอบ

สำหรับ __unicode__ คือเมธอดพิเศษ เอาไว้สำหรับกำหนดว่า ถ้ามีใครมาเรียกใช้ Exam แต่ไม่ระบุชื่อฟิลด์ ระบบจะดีฟอลต์ค่าเป็นฟิลด์อะไร? สำหรับตัวอย่างนี้หากเรียก Exam จะได้ชื่อข้อสอบออกไปโดยอัตโนมัติ จากนั้นก็มาสร้างโมเดลของ Quiz ดังต่อไปนี้

class Quiz(models.Model):
exam = models.ForeignKey(Exam)
name = models.CharField(max_length=200)
image = models.FileField(upload_to='upload', null=True, blank=True)
def __unicode__(self):
return self.name

เมื่อ Exam คือคลาสที่ Quiz อ้างเป็น ForeignKey, name คือชื่อคำถาม มีชนิดข้อมูลเป็นข้อความขนาด 200 ตัวอักษร และสุดท้าย image คือ ฟิลด์ที่เป็นรูปภาพ สามารถที่จะอัพโหลดรูปภาพได้ทันที โดยเรากำหนดให้เก็บรูปภาพไว้ในโฟลเดอร์ที่ชื่อ “upload” ซึ่งเราจะเปลี่ยนให้ไปเก็บไว้ที่ใดก็ได้ โดยเปลี่ยนที่ upload_to=”” ที่นี่
สุดท้ายท้ายสุดก็เหลือ Choice นั่นเอง ให้ท่านเพิ่มโค้ดเข้าไปดังต่อไปนี้

class Choice(models.Model):
quiz = models.ForeignKey(Quiz)
name = models.CharField(max_length=200)
corrected = models.BooleanField(default=False)
def __unicode__(self):
return self.name

เมื่อ Quiz คือคลาสที่ Choice อ้างอิงเพื่อเป็น ForeignKey, name คือชื่อของตัวเลือก มีชนิดข้อมูลเป็นข้อความขนาด 200 ตัวอักษร และสุดท้ายคือ corrected เป็นตัวบอกว่าตัวเลือกนี้เป็นคำตอบที่ถูก (True) หรือผิด (False) จึงกำหนดชนิดข้อมูลเป็นบูลีน และกำหนดค่าดีฟอลต์ให้เป็น False ไปก่อน

Migrations

เมื่อสร้างโมเดลเป็นที่เรียบร้อยแล้ว ขั้นตอนนี้จะเป็นการแปลง models ซึ่งก็คือพิมพ์เขียวของข้อมูลเรา ให้อยู่ในรูปของฐานข้อมูลที่เก็บข้อมูลต่างๆ ได้จริง ขั้นตอน migration ในจังโก้นั้นทำได้ง่ายแสนง่าย โดยใช้ 2 คำสั่งคือ python manage.py makemigrations เป็นการแปลงโมเดลของเราให้เป็น schema ของฐานข้อมูล และคำสั่งสุดท้ายคือ python manage.py migrate เป็นคำสั่งยืนยันการ migrate ตัว schema ทั้งหลายเหล่านี้ลงไปสู่ฐานข้อมูลนั่นเอง มาเริ่มกันเลยดีกว่า

$ (vbox) python manage.py makemigrations
Migrations for 'myapp':
0001_initial.py:
- Create model Choice
- Create model Exam
- Create model Quiz
- Add field quiz to choice

จากนั้นตามด้วย

$ (vbox) python manage.py migrate
Operations to perform:
Synchronize unmigrated apps: staticfiles, messages
Apply all migrations: admin, contenttypes, myapp, auth, sessions
Synchronizing apps without migrations:
Creating tables...
Running deferred SQL...
Installing custom SQL...
Running migrations:
Rendering model states... DONE
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying myapp.0001_initial... OK
Applying sessions.0001_initial... OK

เท่านี้โมเดลของท่านก็ไปอยู่ในฐานข้อมูลเรียบร้อยแล้ว เป็นอันจบขั้นตอนการ migrate ข้อมูล

การจัดการข้อมูลผ่าน Models

สำหรับข้อนี้จะเป็นตัวอย่างการใช้คำสั่ง เพื่อจัดการข้อมูลในโมเดลของเรานะครับ ปกติแล้วเวลาเราจะทำอะไรกับข้อมูล เราจะใช้คำสั่ง SQL ในการจัดการ เช่น select, insert, update และ delete เป็นต้น แต่ในเฟรมเวิร์คสมัยใหม่ (รวมถึงจังโก้ด้วย) เราจะจัดการข้อมูลผ่านคลาสที่เป็นโมเดลของมันอีกที โดยจะพยายามใช้ SQL ให้น้อยที่สุดครับ นั่นเพราะต้องการแยก database กับโปรแกรมออกจากกัน เมื่อเวลาเราเปลี่ยน database เป็นยี่ห้อใหม่ เช่นจาก mysql เป็น oracle มันก็จะไม่กระทบกับโปรแกรมของเราเลย ข้อดีอีกอย่างหนึ่งก็คือโค้ดของโปรแกรมเรามีความสะอาด และจัดการได้ง่ายขึ้นอีกด้วย

คราวนี้เรามาทดสอบการจัดการข้อมูลผ่านโมเดลที่จังโก้เตรียมไว้ให้เราบ้าง โดยเริ่มจากเราเรียก shell ของจังโก้ขึ้นมาก่อน โดยใช้คำสั่ง python manage.py shell ดังต่อไปนี้

$ (vbox) python manage.py shell
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>>

จะเห็น shell ของระบบพร้อมที่จะรับคำสั่งจากเราแล้ว โจทย์แรกของเรา คือการลอง select ข้อมูล Exam ดูซิว่ามีข้อมูลอะไรบ้าง? วิธีการเริ่มจาก import อ้างถึงตัว Exam เข้ามาก่อน

>>> from myapp.models import Exam
>>>

จากนั้นลอง select ข้อมูลใน Exam ดูโดยใช้คำสั่ง Exam.objects.all()

>>> Exam.objects.all()
[]
>>>

จะเห็นว่ามันขึ้น “[]” แสดงว่ายังไม่มีข้อมูล ถ้าอย่างนั้นเราก็จะมาเพิ่มข้อมูลเข้าไปใน Exam โดยใช้คำสั่งดังต่อไปนี้

>>> e = Exam(name='This is Exam1', description='Description1')
>>> e.save()
>>> e.id
1
>>> e.name
'This is Exam1'
>>> e.description
'Description1'
>>>

คำสั่งข้างบนเป็นการสร้าง object ของ Exam ใหม่ขึ้นมาหนึ่งตัว โดยตั้งชื่อตัวแปรว่า “e” โดยมีการกำหนดค่า name และ description ไว้เรียบร้อยแล้ว จากนั้นจึงใช้ e.save() เพื่อบันทึก object นี้ลงสู่ฐานข้อมูล และเมื่อเราใช้ e.id หรือ e.name หรือ e.description มันก็จะดึงข้อมูลที่บันทึกลงเทเบิ้ลมาให้เราดูนั่นเอง

คราวนี้เราจะมาแก้ไข description โดยเปลี่ยนข้อมูลให้เป็น “Description1 has been changed” ก็สามารถทำได้อย่างง่ายดายดังนี้

>>> e.description = 'Description1 has been changed'
>>> e.save()
>>> e.description
'Description1 has been changed'
>>>

เพิ่ม Exam เข้าไปอีกตัว

>>> e = Exam(name='This is Exam2', description='Description2') >>> e.save() >>>

จากนั้นเราจะลอง select ดูหน่อยว่าตอนนี้ข้อมูลเป็นยังไงบ้างแล้ว

>>> Exam.objects.all()
[<Exam: This is Exam1>, <Exam: This is Exam2>]
>>>

ลองนับข้อมูลดูว่ามีเท่าไร?

>>> Exam.objects.count()
2
>>>

ต่อมาเราจะมา select โดยใช้ ID กันบ้าง เรามาลองเอาข้อมูล Exam ที่มี ID=2 จะเขียนได้ดังนี้ครับ

>>> exam = Exam.objects.get(pk=2)
>>> exam.name
u'This is Exam2'
>>>

หรือ

>>> exam = Exam.objects.get(id=2)
>>> exam.name
u'This is Exam2'
>>> exam = Exam.objects.get(name='This is Exam2')
>>> exam.name
u'This is Exam2'

ข้อสังเกตคือ คำสั่ง get จะได้ object ออกมาแค่ตัวเดียวเท่านั้น ในกรณีที่ต้องการข้อมูลที่เป็นอาเรย์ลิสท์ เราจะใช้คำสั่ง filter ในการกรองข้อมูล ยกตัวอย่างต้องการกรองข้อมูลที่มีชื่อขึ้นต้นด้วยคำว่า “This” จะเขียนด้วยคำสั่ง filter ดังต่อไปนี้

>>> e = Exam(name='Some Exam3', description='Description3')
>>> e.save()
>>> e = Exam(name='The Exam4', description='Description4')
>>> e.save()
>>>
>>> Exam.objects.filter(name__startswith='This')
[<Exam: This is Exam1>, <Exam: This is Exam2>]
>>>
>>> Exam.objects.filter(name__startswith='This').count()
2
>>>

คราวนี้เราจะมาดูในเรื่องของ relationship กันบ้าง ถ้าเรามี Exam หนึ่งตัว และต้องการจะรู้ว่ามีคำถาม (Quiz) ทั้งหมดกี่ข้อ ปกติแล้วโจทย์แบบนี้เราจะเขียน SQL ที่มีการ join กันระหว่างสองเทเบิ้ลอย่างแน่นอน แต่ในจังโก้มันง่ายมากที่สามารถเข้าไปดูข้อมูลอื่นๆ ยกตัวอย่างเช่น ต้องการดูคำถามทั้งหมดที่อยู่ในข้อสอบ “Exam1” จะเขียนได้ดังนี้

>>> exam = Exam.objects.get(id=1)
>>> exam.quiz_set.all()
[]
>>>

จากโค้ดข้างบน ขั้นแรกเราต้อง select exam ข้อสอบที่เราต้องการมาก่อน อย่างเราต้องการ “Exam1” ก็ใช้ ID=1 ตามที่เราเรียนกันไปแล้ว ดังนั้นจากความสัมพันธ์ one-to-many การอ้างข้อมูลที่อยู่ในฝั่งของ many (เมื่อเราอยู่ฝั่งของ one) เราจะตามหลังด้วย “set” เช่น “quiz_set” หมายถึงข้อมูลคำถามหลายๆข้อในข้อสอบหนึ่งแผ่น เป็นต้น หรืออีกตัวอย่างหนึ่ง สมมติเรามีคำถาม (Quiz) ข้อหนึ่งๆ และต้องการที่จะดูตัวเลือก (Choice) ของคำถามนั้น ก็จะเขียนเป็น “choice_set” นั่นเอง

จากผลลัพธ์จะเห็นว่ายังไม่มีคำถามใดถูกเพิ่มลงไปในข้อสอบเลย เราจะมาเพิ่มข้อคำถามกันนะครับ วิธีการเพิ่มข้อมูลลงไปใน set ทำได้ไม่ยากโดยการใช้เมธอด create() จากตัวอย่างต่อไปนี้

>>> e = Exam.objects.get(id=1)
>>> e.quiz_set.all()
[]
>>> e.quiz_set.create(name='Quiz Number 1')
<Quiz: Quiz Number 1>
>>> e.quiz_set.create(name='Quiz Number 2')
<Quiz: Quiz Number 2>
>>> e.quiz_set.create(name='Quiz Number 3')
<Quiz: Quiz Number 3>
>>> e.quiz_set.all()
[<Quiz: Quiz Number 1>, <Quiz: Quiz Number 2>, <Quiz: Quiz Number 3>]
>>>

เรื่องสุดท้ายของการใช้งานคำสั่งที่เกี่ยวกับโมเดลก็คือการลบข้อมูล หลักของการลบข้อมูลใดๆ คือการค้นหาข้อมูลที่เราต้องการให้เจอเสียก่อน จากนั้นค่อยทำการลบมัน สำหรับคำสั่งของจังโก้สามารถรองรับการลบข้อมูลทั้งแบบตัวเดียว หรือลบข้อมูลแบบยกเซตก็ได้ ซึ่งได้แก่คำสั่ง delete() ดังต่อไปนี้

กรณีลบข้อมูลแบบตัวเดียว

>>> Exam.objects.all()
[<Exam: This is Exam1>, <Exam: This is Exam2>]
>>> e = Exam.objects.get(id=2)
>>> e.delete()
>>> Exam.objects.all()
[<Exam: This is Exam1>]
>>>

กรณีลบข้อมูลแบบยกเซต

>>> e = Exam.objects.get(id=1)
>>> e.quiz_set.all().delete()
>>> e.quiz_set.all()
[]
>>>

กรณีลบข้อมูลทั้งหมดในเทเบิ้ล

>>> Exam.objects.all().delete()
>>> Exam.objects.all()
[]
>>>

เสริม: การตั้งค่าเพื่อจัดการเกี่ยวกับรูปภาพและเทมเพลต

เราจะมาตั้งค่า config โปรเจ็กต์เพิ่มเติมเพื่อให้ระบบของเรารองรับการอัพโหลด แสดงผลรูปภาพและให้รองรับการทำเทมเพลต โดยเริ่มจากแก้ไข settings.py โดยให้แก้ไขค่าคอนฟิคตามตัวอย่างโค้ดดังต่อไปนี้

TEMPLATES = [
{
'DIRS': ['templates'],
...
},
]
...USE_TZ = TrueSTATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static'),
]
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

จากนั้นที่ไฟล์ urls.py ให้เพิ่มโค้ดสำหรับรองรับการแสดงผลรูปภาพดังต่อไปนี้

from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
...
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

สรุปท้ายบท

เป็นอย่างไรกันบ้างครับสำหรับตอนที่ 2 คงได้ออกแรงอ่านบทความและเขียนโปรแกรมกันพอสมควร (ตามคอนเซ็ปต์ของซีโร่สคูลครับ บางเนื้อหาอาจจะต้องใช้แรงเข้าแรก 555) โดยเนื้อหาหลักๆ จะพูดถึง requirement ของระบบ eexam ซึ่งเป็นเวิร์คชอปที่เราจะทำกันตลอดไปจากนี้ และได้พูดถึงแอพ (apps) ว่าเป็นโมดูลย่อยๆ ของโปรเจ็กต์เรา นอกจากนี้ยังได้พูดถึงหลักการของโมเดล เรายังได้เขียนโปรแกรมสร้างโมเดลขึ้นมา และทำการ migrate ข้อมูลต่างๆ ลงในฐานข้อมูลอีกด้วย ท้ายบทจะพูดถึงคำสั่งในการจัดการข้อมูลของโมเดล และเรายังได้ทดลองการใช้คำสั่งต่างๆอีกด้วย

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

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade