วันอังคารที่ 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 ที่แก้ไขจนจบ

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

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