Django内容类型到底如何工作?

问题:Django内容类型到底如何工作?

我真的很难理解Django内容类型的概念。感觉非常骇人听闻,并且最终与Python趋向于做事相反。话虽如此,如果我要使用Django,则必须在框架范围内进行工作。

所以我来这里想知道是否有人可以给出有关内容类型如何工作以及如何实现的实际例子。我评论过的几乎所有教程(大部分在博客上)都无法真正涵盖这个概念。他们似乎从Django文档遗忘的地方接手(似乎无处可去)。

I’m really having a difficult time grasping the concept of Django’s content types. It feels very hackish and, ultimately, against how Python tends to do things. That being said, if I’m going to use Django then I have to work within the confines of the framework.

So I’m coming here wondering if anyone can give a practical real world example of how a content type works and how you would implement it. Almost all the tutorials (mostly on blogs) I have reviewed don’t do a great job really covering the concept. They seem to pick up where the Django documentation left off (what seems like nowhere).


回答 0

因此,您想在工作中使用内容类型框架吗?

首先问自己一个问题:“这些模型中的任何一个是否需要与其他模型以相同的方式关联,并且/或者在以后的工作中,我是否会以无法预料的方式重用这些关系?” 我们问这个问题的原因是因为这是Content Types框架最擅长的:它在模型之间创建通用关系。等等,让我们深入研究一些代码,看看我的意思。

# 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程序员,您的卓越才智告诉您这很糟糕,您可以做得更好。举手击掌!

进入内容类型框架!

好了,现在我们将仔细研究我们的模型,并对其进行重新设计以使其更加“可重用”和直观。让我们从摆脱Comment模型上的两个外键开始,并用替换它们GenericForeignKey

# 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,还有一个 ForeignKeyto ContentType和a PositiveIntegerField的问题object_id。这些字段用于告诉Django与之相关的对象类型以及该对象的ID。实际上,这是有道理的,因为Django需要同时查找这些相关对象。

好吧,这不是非常像Python的…有点丑陋!

您可能正在寻找能使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')

am!就像这样,您可以为这两个模型使用注释。实际上,让我们继续在shell中进行操作(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!"]

就这么简单。

这些“一般”关系的其他实际含义是什么?

通用外键可以减少各种应用程序之间的干扰。例如,假设我们将Comment模型拉到了自己的名为的应用中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)

我希望你们发现这对您有所帮助,因为我很想遇到一些向我展示了GenericForeignKeyand GenericRelation领域更实际应用的东西。

这好得令人难以置信吗?

与生活中的任何事物一样,也有利弊。每当您添加更多代码和更多抽象时,底层进程就会变得更重且更慢。尽管添加通用关系可能会尝试并智能缓存其结果,但可能会增加一点性能衰减器。总而言之,这取决于清洁度和简单性是否超过了较小的性能成本。对我来说,答案是一百万次。

内容类型框架比我在这里显示的要多。有一个整体的粒度级别和更详细的用法,但是对于一般个人而言,我认为这就是您将使用十分之九的方式。

通用关联器(?)当心!

一个相当大的警告是,当您使用时GenericRelation,如果删除了已GenericRelation应用(Picture)的模型,则所有相关(Comment)对象也将被删除。或至少在撰写本文时。

So you want to use the Content Types framework on your work?

Start by asking yourself this question: “Do any of these models need to be related in the same way to other models and/or will I be reusing these relationships in unforseen ways later down the road?” The reason why we ask this question is because this is what the Content Types framework does best: it creates generic relations between models. Blah blah, let’s dive into some code and see what I mean.

# 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)

Okay, so we do have a way to theoretically create this relationship. However, as a Python programmer, your superior intellect is telling you this sucks and you can do better. High five!

Enter the Content Types framework!

Well, now we’re going to take a close look at our models and rework them to be more “reusable” and intuitive. Let’s start by getting rid of the two foreign keys on our Comment model and replace them with a GenericForeignKey.

# 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()

So, what happened? Well, we went in and added the necessary code to allow for a generic relation to other models. Notice how there is more than just a GenericForeignKey, but also a ForeignKey to ContentType and a PositiveIntegerField for the object_id. These fields are for telling Django what type of object this is related to and what the id is for that object. In reality, this makes sense because Django will need both to lookup these related objects.

Well, that’s not very Python-like… its kinda ugly!

You are probably looking for air-tight, spotless, intuitive code that would make Guido van Rossum proud. I get you. Let’s look at the GenericRelation field so we can put a pretty bow on this.

# 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')

Bam! Just like that you can work with the Comments for these two models. In fact, let’s go ahead and do that in our shell (type python manage.py shell from your Django project directory).

>>> 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!"]

It’s that simple.

What are the other practical implications of these “generic” relations?

Generic foreign keys allow for less intrusive relations between various applications. For example, let’s say we pulled the Comment model out into it’s own app named chatterly. Now we want to create another application named noise_nimbus where people store their music to share with others.

What if we want to add comments to those songs? Well, we can just draw a generic relation:

# 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)

I hope you guys found this helpful as I would have loved to have come across something that showed me the more realistic application of GenericForeignKey and GenericRelation fields.

Is this too good to be true?

As with anything in life, there are pros and cons. Anytime you add more code and more abstraction, the underlying processes becomes heavier and a bit slower. Adding generic relations can add a little bit of a performance dampener despite the fact it will try and smart cache its results. All in all, it comes down to whether the cleanliness and simplicity outweighs the small performance costs. For me, the answer is a million times yes.

There is more to the Content Types framework than I have displayed here. There is a whole level of granularity and more verbose usage, but for the average individual, this is how you will be using it 9 out of 10 times in my opinion.

Generic relationizers(?) beware!

A rather large caveat is that when you use a GenericRelation, if the model which has the GenericRelation applied (Picture) is deleted, all related (Comment) objects will also be deleted. Or at least as of the time of this writing.


回答 1

好的,您的问题的直接答案是:(来自django源代码)是: 根据RFC 2616,第3.7节分析媒体类型。

这是流泪的说法,它读取/允许您修改/传递“ Content-type” 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是python,因此它具有python社区的功能。django有2个很棒的RESTFul插件。因此,如果您想了解兔子整体的深度,可以查看一下。

我建议阅读django-rest-framework教程,该教程将专门解决“作用于不同内容/类型”的问题。注意:通常的做法是使用content-type标头对RESTful API进行“版本化”

Ok well the direct answer to your question: ( from the django source code ) is: Media Types parsing according to RFC 2616, section 3.7.

Which is the tears way of saying that it reads/allows-you-to-modify/passes along the ‘Content-type’ httpd header.

However, you are asking for a more practice usage example. I have 2 suggestions for you:

1: examine this code

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: remember django is python, and as such it wields the power of the python community. There are 2 awesome RESTFul plugins to django. So if you want to see how deep the rabbit whole goes you can check out.

I suggest going through the django-rest-framework tutorial which will address ‘acting on different content/types’ specifically. Note: It is common practice to use the content-type header to ‘version’ restful API’s.