¿Cómo funcionan exactamente los tipos de contenido de Django?

148

Realmente estoy teniendo dificultades para comprender el concepto de los tipos de contenido de Django. Se siente muy hostil y, en última instancia, en contra de cómo Python tiende a hacer las cosas. Dicho esto, si voy a usar Django, entonces tengo que trabajar dentro de los límites del marco.

Así que vengo aquí preguntándome si alguien puede dar un ejemplo práctico del mundo real de cómo funciona un tipo de contenido y cómo lo implementaría. Casi todos los tutoriales (principalmente en blogs) que he revisado no hacen un gran trabajo realmente cubriendo el concepto. Parecen retomar donde quedó la documentación de Django (lo que parece no estar en ninguna parte).

Chris Shelton
fuente
55
Creo (alguien me corrige si estoy equivocado) que los tipos de contenido es algo así como un polimorfismo, se convertirá en una herramienta en sus manos una vez que su proyecto comience a tener modelos que pueden tener muchas formas diferentes. El ejemplo de etiqueta en la documentación es bastante sencillo, desea poder etiquetar elementos, pero no desea ser específico para qué tipo de elementos son, después de todo, una etiqueta puede admitir, publicaciones, páginas, usuarios, productos Con el uso de Tipos de contenido, puede crear relaciones con varias implementaciones diferentes sin tener que saber cuál es exactamente el modelo relacionado.
petkostas
1
Bien, entonces cuando me tropecé es que hicieron una clase llamada "TaggedItem" que no estaba claro para mí. No estaba seguro entonces si TaggedItem era una clase de "puente" de marcador de posición. Mi inclinación natural habría sido algo así como "Etiqueta" con una propiedad llamada "término".
Chris Shelton

Respuestas:

307

Entonces, ¿desea utilizar el marco de Tipos de contenido en su trabajo?

Comience por hacerse esta pregunta: "¿Es necesario que alguno de estos modelos esté relacionado de la misma manera con otros modelos y / o reutilizaré estas relaciones de manera imprevista más adelante?" La razón por la que hacemos esta pregunta es porque esto es lo que el marco de Tipos de contenido hace mejor: crea relaciones genéricas entre modelos. Bla, bla, vamos a sumergirnos en un código y ver a qué me refiero.

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

Bien, entonces tenemos una manera de crear teóricamente esta relación. Sin embargo, como programador de Python, su intelecto superior le dice que esto apesta y que puede hacerlo mejor. ¡Cinco altos!

¡Ingrese al marco de tipos de contenido!

Bueno, ahora vamos a echar un vistazo de cerca a nuestros modelos y modificarlos para que sean más "reutilizables" e intuitivos. Comencemos por deshacernos de las dos claves foráneas en nuestro Commentmodelo y reemplazarlas con 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()

¿Entonces qué pasó? Bueno, entramos y agregamos el código necesario para permitir una relación genérica con otros modelos. Observe cómo hay más que solo un GenericForeignKey, pero también un ForeignKeyto ContentTypey un PositiveIntegerFieldpara el object_id. Estos campos son para decirle a Django con qué tipo de objeto está relacionado y cuál es la identificación para ese objeto. En realidad, esto tiene sentido porque Django necesitará ambos para buscar estos objetos relacionados.

Bueno, eso no es muy parecido a Python ... ¡es un poco feo!

Probablemente esté buscando un código hermético, impecable e intuitivo que enorgullezca a Guido van Rossum . Te entiendo Echemos un vistazo al GenericRelationcampo para que podamos hacer una hermosa reverencia sobre esto.

# 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! Solo así puede trabajar con los Comentarios para estos dos modelos. De hecho, sigamos adelante y hagámoslo en nuestro shell (escriba python manage.py shelldesde el directorio de su proyecto 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!"]

Es así de simple.

¿Cuáles son las otras implicaciones prácticas de estas relaciones "genéricas"?

Las claves foráneas genéricas permiten relaciones menos intrusivas entre varias aplicaciones. Por ejemplo, supongamos que sacamos el modelo de comentarios en su propia aplicación llamada chatterly. Ahora queremos crear otra aplicación llamada noise_nimbusdonde las personas almacenan su música para compartirla con otros.

¿Qué pasa si queremos agregar comentarios a esas canciones? Bueno, podemos dibujar una relación genérica:

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

Espero que les haya resultado útil, ya que me hubiera encantado haber encontrado algo que me mostró la aplicación GenericForeignKeyy los GenericRelationcampos más realistas .

¿Es esto demasiado bueno para ser verdad?

Como con cualquier cosa en la vida, hay pros y contras. Cada vez que agrega más código y más abstracción, los procesos subyacentes se vuelven más pesados ​​y un poco más lentos. Agregar relaciones genéricas puede agregar un poco de amortiguador de rendimiento a pesar del hecho de que intentará almacenar en caché inteligente sus resultados. Con todo, todo se reduce a si la limpieza y la simplicidad superan los pequeños costos de rendimiento. Para mí, la respuesta es un millón de veces sí.

Hay más en el marco de Tipos de contenido de lo que he mostrado aquí. Hay un nivel completo de granularidad y un uso más detallado, pero para el individuo promedio, así es como lo usará 9 de cada 10 veces en mi opinión.

Relacionadores genéricos (?) ¡Cuidado!

Una advertencia bastante grande es que cuando utiliza un GenericRelation, si se elimina el modelo que tiene GenericRelationaplicado ( Picture), todos los Commentobjetos relacionados ( ) también se eliminarán. O al menos al momento de escribir esto.

Chris Shelton
fuente
11
Entonces, si uso GenericRelationin Posty Pictureluego no necesito usar object_id, ¿ content_typey content_objectin Comment?
avi
55
Sería bueno tener una descripción tan clara del marco de tipo de contenido en algún lugar de la documentación oficial de Django. En cuanto a mí, me di cuenta de lo que hace este marco solo después de leer este puerto. Gracias.
Prokher
2
un poco tarde ... pero he oído que al usar el marco de tipo de contenido, su aplicación podría no escalar correctamente. ¿Alguien puede decirme si esto es cierto o un engaño?
Karan Kumar el
1
Como con todo en programación, Karan, la respuesta siempre es "depende". Yo diría que use tipos de contenido. Es una especie de "compromiso" eludir algunos de los fundamentos rígidos de un sistema SQL orientado a tablas. ¡No optimices prematuramente tu aplicación! Django es el mejor en salir de tu camino para que puedas escribir la aplicación de próxima generación que siempre has querido: ¡usa sus funciones para tu ventaja!
Chris Shelton
2
Karan, hay algo de verdad en eso. Estoy trabajando en una aplicación que rastrea las notificaciones para los usuarios. Cada notificación tiene una relación GenericForeignKey con algún otro tipo de contenido que almacenamos. Cada vez que un usuario ve notificaciones, el ORM emite N consultas para obtener todo el contenido relacionado. Apenas ideal.
Travis Mehlinger
-2

Bien, la respuesta directa a su pregunta: (del código fuente de django) es: Análisis de tipos de medios según RFC 2616, sección 3.7.

¿Cuál es la forma de decir que lee / le permite modificar / pasa a lo largo del encabezado httpd 'Content-type' ?

Sin embargo, está pidiendo un ejemplo de uso más práctico. Tengo 2 sugerencias para ti:

1: examina este código

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: recuerde que django es python, y como tal ejerce el poder de la comunidad python. Hay 2 complementos RESTFul impresionantes para django. Entonces, si quieres ver qué tan profundo va todo el conejo, puedes echarle un vistazo.

Sugiero pasar por el tutorial django-rest-framework que abordará específicamente 'actuar sobre diferentes contenidos / tipos'. Nota: Es una práctica común usar el encabezado de tipo de contenido para 'versionar' las API relajantes .

Jeff Sheffield
fuente
1
¿Es eso a lo que se refiere? o al marco de tipos de
petkostas
1
Sí, me refería al marco de tipos de contenido. Puede que no haya hecho un trabajo lo suficientemente bueno transmitiéndome. Agradezco la respuesta independientemente. Por lo que vale, si esa fuera mi pregunta, la habrías sacado del parque =)
Chris Shelton