ประเภทเนื้อหา Django ทำงานอย่างไร


148

ฉันมีช่วงเวลาที่ยากลำบากในการเข้าใจแนวคิดของประเภทเนื้อหาของ Django มันรู้สึกแฮ็คมากและท้ายที่สุดแล้วกับวิธีที่ Python มีแนวโน้มที่จะทำสิ่งต่างๆ ที่ถูกกล่าวว่าถ้าฉันจะใช้ Django แล้วฉันจะต้องทำงานภายในขอบเขตของกรอบ

ดังนั้นฉันมาที่นี่สงสัยว่าถ้าใครสามารถยกตัวอย่างจริงในทางปฏิบัติเกี่ยวกับการทำงานของประเภทเนื้อหาและวิธีที่คุณจะใช้มัน แบบฝึกหัดเกือบทั้งหมด (ส่วนใหญ่บนบล็อก) ฉันได้ตรวจสอบแล้วว่าไม่ได้ทำงานที่ยอดเยี่ยมซึ่งครอบคลุมแนวคิด พวกเขาดูเหมือนจะหยิบเอกสารที่ Django ออก (สิ่งที่ดูเหมือนไม่มีที่ไหนเลย)


5
ฉันเชื่อว่า (บางคนแก้ไขให้ถูกต้องถ้าฉันผิด) ประเภทเนื้อหานั้นเป็นแบบ polymorphism มันจะกลายเป็นเครื่องมือในมือของคุณเมื่อโปรเจ็กต์ของคุณเริ่มมีโมเดลที่มีหลายรูปแบบ ตัวอย่างแท็กในเอกสารนั้นค่อนข้างตรงไปตรงมาคุณต้องการให้สามารถติดแท็กรายการได้ แต่คุณไม่ต้องการระบุว่าเป็นรายการประเภทใดหลังจากที่แท็กทั้งหมดสามารถรองรับโพสต์เพจผู้ใช้ ผลิตภัณฑ์ ด้วยการใช้ประเภทเนื้อหาคุณสามารถสร้างความสัมพันธ์กับการใช้งานที่แตกต่างหลากหลายโดยไม่ต้องรู้ว่าตัวแบบที่เกี่ยวข้องนั้นเป็นอะไรกันแน่
petkostas

1
โอเคดังนั้นที่ฉันได้ดีดตัวขึ้นก็คือพวกเขาสร้างชั้นเรียนที่ชื่อว่า "TaggedItem" ซึ่งไม่ชัดเจนสำหรับฉัน ฉันไม่แน่ใจว่าถ้า TaggedItem เป็นชั้นยึด "สะพาน" ความชอบตามธรรมชาติของฉันน่าจะเป็น "แท็ก" ที่มีคุณสมบัติชื่อ "คำว่า"
Chris Shelton

คำตอบ:


307

คุณต้องการใช้เฟรมเวิร์กประเภทเนื้อหาในงานของคุณหรือไม่?

เริ่มต้นด้วยการถามตัวเองด้วยคำถามนี้: "โมเดลใด ๆ เหล่านี้จำเป็นต้องเกี่ยวข้องในแบบเดียวกันกับโมเดลอื่น ๆ และ / หรือฉันจะนำความสัมพันธ์เหล่านี้กลับมาใช้ซ้ำในแบบที่ไม่เป็นไปตามถนนหรือไม่" เหตุผลที่เราถามคำถามนี้เป็นเพราะนี่คือสิ่งที่กรอบประเภทเนื้อหาดีที่สุด: มันสร้างความสัมพันธ์ทั่วไประหว่างแบบจำลอง Blah blah ลองดำดิ่งลงไปในโค้ดและดูว่าฉันหมายถึงอะไร

# ourapp.models
from django.conf import settings
from django.db import models

# Assign the User model in case it has been "swapped"
User = settings.AUTH_USER_MODEL

# Create your models here
class Post(models.Model):
  author = models.ForeignKey(User)
  title = models.CharField(max_length=75)
  slug = models.SlugField(unique=True)
  body = models.TextField(blank=True)

class Picture(models.Model):
  author = models.ForeignKey(User)
  image = models.ImageField()
  caption = models.TextField(blank=True)

class Comment(models.Model):
  author = models.ForeignKey(User)
  body = models.TextField(blank=True)
  post = models.ForeignKey(Post)
  picture = models.ForeignKey(Picture)

ตกลงดังนั้นเราจึงมีวิธีในการสร้างความสัมพันธ์นี้ในทางทฤษฎี อย่างไรก็ตามในฐานะโปรแกรมเมอร์ Python สติปัญญาระดับสูงของคุณกำลังบอกคุณเรื่องนี้และคุณสามารถทำได้ดีกว่า แตะมือ!

ใส่เฟรมเวิร์กประเภทเนื้อหา!

ทีนี้เราจะมาดูแบบจำลองของเราอย่างใกล้ชิดและนำมันกลับมาใช้ใหม่เพื่อให้สามารถ "นำกลับมาใช้ใหม่" และใช้งานง่ายยิ่งขึ้น ขอเริ่มต้นโดยการกำจัดของสองปุ่มต่างประเทศของเรารูปแบบและแทนที่พวกเขาด้วยCommentGenericForeignKey

# ourapp.models
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType

...

class Comment(models.Model):
  author = models.ForeignKey(User)
  body = models.TextField(blank=True)
  content_type = models.ForeignKey(ContentType)
  object_id = models.PositiveIntegerField()
  content_object = GenericForeignKey()

แล้วเกิดอะไรขึ้น? เราเข้าไปข้างในและเพิ่มรหัสที่จำเป็นเพื่ออนุญาตให้มีความสัมพันธ์ทั่วไปกับรุ่นอื่น ๆ แจ้งให้ทราบว่ามีมากกว่าเพียงGenericForeignKeyแต่ยัง ForeignKeyไปContentTypeและPositiveIntegerFieldสำหรับobject_idสำหรับฟิลด์เหล่านี้มีไว้เพื่อบอก Django ว่าวัตถุชนิดนี้เกี่ยวข้องกับอะไรและ id นั้นสำหรับวัตถุนั้น ในความเป็นจริงสิ่งนี้สมเหตุสมผลเนื่องจาก Django ต้องการทั้งสองเพื่อค้นหาวัตถุที่เกี่ยวข้องเหล่านี้

นั่นไม่เหมือนงูหลาม ... มันช่างน่าเกลียด!

คุณอาจกำลังมองหารหัสที่แน่นหนาไร้อากาศและใช้งานง่ายซึ่งจะทำให้Guido van Rossumภูมิใจ ฉันเข้าใจคุณ ลองดูที่GenericRelationทุ่งนาเพื่อที่เราจะได้น้อมคันนี้

# ourapp.models
from django.contrib.contenttypes.fields import GenericRelation

...

class Post(models.Model):
  author = models.ForeignKey(User)
  title = models.CharField(max_length=75)
  slug = models.SlugField(unique=True)
  body = models.TextField(blank=True)
  comments = GenericRelation('Comment')

class Picture(models.Model):
  author = models.ForeignKey(User)
  image = models.ImageField()
  caption = models.TextField(blank=True)
  comments = GenericRelation('Comment')

ปัง! เช่นเดียวกับที่คุณสามารถทำงานกับความคิดเห็นสำหรับทั้งสองรุ่น ในความเป็นจริงเราจะไปข้างหน้าและทำในเปลือกของเรา (พิมพ์python manage.py shellจากไดเรกทอรีโครงการ Django ของคุณ)

>>> from django.contrib.auth import get_user_model
>>> from ourapp.models import Picture, Post

# We use get_user_model() since we are referencing directly
User = get_user_model()

# Grab our own User object
>>> me = User.objects.get(username='myusername')

# Grab the first of our own pictures so we can comment on it
>>> pic = Picture.objects.get(author=me)

# Let's start making a comment for our own picture
>>> pic.comments.create(author=me, body="Man, I'm cool!")

# Let's go ahead and retrieve the comments for this picture now
>>> pic.comments.all()
[<Comment: "Man, I'm cool!">]

# Same for Post comments
>>> post = Post.objects.get(author=me)
>>> post.comments.create(author=me, body="So easy to comment now!")
>>> post.comments.all()
[<Comment: "So easy to comment now!"]

มันง่ายมาก

อะไรคือความหมายเชิงปฏิบัติอื่น ๆ ของความสัมพันธ์ "ทั่วไป" เหล่านี้?

คีย์ต่างประเทศทั่วไปช่วยให้มีความสัมพันธ์ที่ล่วงล้ำน้อยลงระหว่างแอปพลิเคชันต่างๆ ตัวอย่างเช่นสมมติว่าเราดึงรูปแบบการแสดงความคิดเห็นออกมาใน app chatterlyของตัวเองชื่อ ตอนนี้เราต้องการสร้างแอปพลิเคชันอื่นที่มีชื่อnoise_nimbusซึ่งผู้คนเก็บเพลงไว้เพื่อแบ่งปันกับผู้อื่น

หากเราต้องการเพิ่มความคิดเห็นให้กับเพลงเหล่านั้น ทีนี้เราสามารถวาดความสัมพันธ์ทั่วไป:

# noise_nimbus.models
from django.conf import settings
from django.contrib.contenttypes.fields import GenericRelation
from django.db import models

from chatterly.models import Comment

# For a third time, we take the time to ensure custom Auth isn't overlooked
User = settings.AUTH_USER_MODEL

# Create your models here
class Song(models.Model):
  '''
  A song which can be commented on.
  '''
  file = models.FileField()
  author = models.ForeignKey(User)
  title = models.CharField(max_length=75)
  slug = models.SlugField(unique=True)
  description = models.TextField(blank=True)
  comments = GenericRelation(Comment)

ฉันหวังว่าพวกคุณจะพบว่าสิ่งนี้มีประโยชน์เพราะฉันอยากจะพบสิ่งที่แสดงให้ฉันเห็นว่าแอปพลิเคชันGenericForeignKeyและGenericRelationฟิลด์ที่สมจริงยิ่งขึ้น

สิ่งนี้ดีเกินกว่าที่จะเป็นจริงหรือไม่?

เช่นเดียวกับสิ่งใดในชีวิตมีข้อดีและข้อเสีย เมื่อใดก็ตามที่คุณเพิ่มรหัสและนามธรรมมากขึ้นกระบวนการพื้นฐานจะหนักขึ้นและช้าลงเล็กน้อย การเพิ่มความสัมพันธ์ทั่วไปสามารถเพิ่มตัวหน่วงประสิทธิภาพเล็กน้อยแม้ว่าจะพยายามและแคชผลลัพธ์อย่างชาญฉลาด สรุปแล้วก็คือว่าความสะอาดและความเรียบง่ายมีค่ามากกว่าประสิทธิภาพการทำงานเล็กน้อยหรือไม่ สำหรับฉันคำตอบคือล้านครั้งใช่

มีกรอบเนื้อหาประเภทมากกว่าที่ฉันแสดงที่นี่ มีทั้งระดับละเอียดและการใช้อย่างละเอียดมากขึ้น แต่สำหรับคนทั่วไปนี่คือวิธีที่คุณจะใช้ 9 ใน 10 ครั้งในความคิดของฉัน

ความสัมพันธ์ทั่วไป (?) ระวัง!

ข้อแม้ที่ค่อนข้างใหญ่คือเมื่อคุณใช้ a GenericRelationหากรูปแบบที่มีการGenericRelationใช้ ( Picture) ถูกลบCommentวัตถุที่เกี่ยวข้องทั้งหมด ( ) จะถูกลบเช่นกัน หรืออย่างน้อย ณ เวลาที่เขียนนี้


11
ดังนั้นถ้าผมใช้GenericRelationในPostและPictureแล้วฉันไม่จำเป็นต้องใช้object_id, content_typeและcontent_objectในComment?
avi

5
มันจะเป็นการดีหากมีคำอธิบายที่ชัดเจนเกี่ยวกับกรอบเนื้อหาประเภทใดที่หนึ่งในเอกสารทางการของ Django สำหรับฉันฉันรู้ว่ากรอบนี้ทำหลังจากอ่านพอร์ตนี้เท่านั้น ขอบคุณ.
prokher

2
สายเล็กน้อย ... แต่ฉันได้ยินมาว่าการใช้เฟรมเวิร์กชนิดเนื้อหาแอปพลิเคชันของคุณอาจปรับขนาดได้ไม่เหมาะสม ใครช่วยบอกฉันหน่อยได้ไหมว่าเรื่องนี้เป็นเรื่องจริงหรือเรื่องหลอกลวง
Karan Kumar

1
เช่นเดียวกับทุกอย่างในการเขียนโปรแกรม Karan คำตอบคือ "ขึ้นอยู่กับมันเสมอ" ฉันจะบอกว่าใช้ประเภทเนื้อหา มันเป็น "การประนีประนอม" ของการเรียงลำดับเพื่อหลีกเลี่ยงพื้นฐานที่เข้มงวดบางส่วนของระบบ SQL ที่มุ่งเน้นตาราง อย่าเพิ่มประสิทธิภาพแอปของคุณก่อนกำหนด! Django ดีที่สุดในการออกไปให้พ้นทางเพื่อให้คุณสามารถเขียนแอปพลิเคชั่นรุ่นต่อไปที่คุณต้องการได้ตลอดเวลา: ใช้คุณสมบัติเพื่อประโยชน์ของคุณ!
Chris Shelton

2
Karan มีความจริงบางอย่าง ฉันกำลังทำงานกับแอปพลิเคชันที่ติดตามการแจ้งเตือนสำหรับผู้ใช้ การแจ้งเตือนแต่ละรายการมีความสัมพันธ์ GenericForeignKey กับเนื้อหาประเภทอื่น ๆ ที่เราจัดเก็บ ทุกครั้งที่ผู้ใช้ดูการแจ้งเตือน ORM จะออกแบบสอบถาม N เพื่อรับเนื้อหาที่เกี่ยวข้องทั้งหมด ไม่ค่อยเหมาะ
Travis Mehlinger

-2

ตกลงได้คำตอบสำหรับคำถามของคุณโดยตรง: (จากซอร์สโค้ด django) คือ: ประเภทสื่อวิเคราะห์ตาม RFC 2616, ส่วน 3.7

ซึ่งเป็นวิธีที่น้ำตาบอกว่ามันอ่าน / อนุญาตให้คุณแก้ไข / ผ่านตาม'ประเภทเนื้อหา'ส่วนหัว httpd

อย่างไรก็ตามคุณกำลังขอตัวอย่างการใช้งานที่มากขึ้น ฉันมี 2 ข้อเสนอแนะสำหรับคุณ:

1: ตรวจสอบรหัสนี้

def index(request):
   media_type='text/html'
   if request.META.has_key('CONTENT_TYPE'):
      media_type = request.META['CONTENT_TYPE'].split(';')[0]

   if media_type.lower() == 'application/json':
      return HttpResponse("""{ "ResponseCode": "Success"}""", content_type="application/json; charset=UTF-8")

   return HttpResponse("<h1>regular old joe</h1>");

2: จำไว้ว่า django เป็นไพ ธ อนและด้วยเหตุนี้มันจึงใช้พลังของชุมชนไพ ธ อน มี 2 ​​ปลั๊กอิน RESTFul ที่ยอดเยี่ยมสำหรับ django ดังนั้นถ้าคุณต้องการที่จะดูว่ากระต่ายนั้นลึกแค่ไหนคุณก็สามารถลองดูได้

ฉันขอแนะนำให้ทำตามแบบฝึกหัด django-rest-framework ซึ่งจะกล่าวถึง 'การแสดงเนื้อหา / ประเภทที่แตกต่าง' โดยเฉพาะ หมายเหตุ: มันเป็นเรื่องธรรมดาที่จะใช้ส่วนหัวเนื้อหาประเภทที่จะ'' รุ่นพักผ่อน API


1
นั่นคือสิ่งที่เขาพูดถึง? หรือกรอบเนื้อหาcontenttypes
petkostas

1
ใช่ฉันอ้างถึงเฟรมเวิร์กประเภทเนื้อหา ฉันอาจทำงานได้ไม่ดีพอที่จะสื่อความหมายกับตัวเอง ฉันขอขอบคุณการตอบสนองโดยไม่คำนึงถึง สำหรับสิ่งที่คุ้มค่าหากพบว่าคำถามของฉันคุณจะได้เคาะมันออกจากสวนสาธารณะ =)
คริสเชลตัน
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.