Django incluye un contenttypes aplicación que puede rastrear todos los modelos instalados en su proyecto impulsado por Django, proporcionando una interfaz genérica de alto nivel para trabajar con sus modelos.

Visión general

En el corazón de la aplicación contenttypes está el ContentType modelo, que vive en django.contrib.contenttypes.models.ContentType. Instancias de ContentType representar y almacenar información sobre los modelos instalados en su proyecto y nuevas instancias de ContentType se crean automáticamente cada vez que se instalan nuevos modelos.

Instancias de ContentType tienen métodos para devolver las clases de modelo que representan y para consultar objetos de esos modelos. ContentType también tiene un administrador personalizado que agrega métodos para trabajar con ContentType y para obtener instancias de ContentType para un modelo en particular.

Relaciones entre sus modelos y ContentType también se puede utilizar para habilitar relaciones “genéricas” entre una instancia de uno de sus modelos y las instancias de cualquier modelo que haya instalado.

Instalación del marco contenttypes

El marco de tipos de contenido se incluye en el INSTALLED_APPS lista creada por django-admin startproject, pero si lo eliminó o si configuró manualmente su INSTALLED_APPS lista, puede habilitarla agregando 'django.contrib.contenttypes' para usted INSTALLED_APPS configuración.

Por lo general, es una buena idea tener instalado el framework contenttypes; varias de las otras aplicaciones empaquetadas de Django lo requieren:

  • La aplicación de administración lo usa para registrar el historial de cada objeto agregado o cambiado a través de la interfaz de administración.
  • De Django authentication framework lo usa para vincular los permisos de usuario a modelos específicos.

los ContentType modelo

class ContentType

Cada instancia de ContentType tiene dos campos que, en conjunto, describen de forma única un modelo instalado:

app_label

El nombre de la aplicación de la que forma parte el modelo. Esto se toma de la app_label atributo del modelo, e incluye solo el último parte de la ruta de importación de Python de la aplicación; django.contrib.contenttypes, por ejemplo, se convierte en un app_label de contenttypes.

model

El nombre de la clase de modelo.

Además, la siguiente propiedad está disponible:

name

El nombre legible por humanos del tipo de contenido. Esto se toma de la verbose_name atributo del modelo.

Veamos un ejemplo para ver cómo funciona. Si ya tienes el contenttypes aplicación instalada y luego agregue the sites application para usted INSTALLED_APPS configurar y ejecutar manage.py migrate para instalarlo, el modelo django.contrib.sites.models.Site se instalará en su base de datos. Junto con él, una nueva instancia de ContentType se creará con los siguientes valores:

  • app_label se establecerá en 'sites' (la última parte de la ruta de Python django.contrib.sites).
  • model se establecerá en 'site'.

Métodos en ContentType instancias

Cada ContentType instancia tiene métodos que le permiten obtener de una ContentType instancia al modelo que representa, o para recuperar objetos de ese modelo:

ContentType.get_object_for_this_type(**kwargs)

Toma un conjunto de válidos buscar argumentos para el modelo el ContentType representa y hace a get() lookup en ese modelo, devolviendo el objeto correspondiente.

ContentType.model_class()

Devuelve la clase de modelo representada por este ContentType ejemplo.

Por ejemplo, podríamos buscar el ContentType Para el User modelo:

>>>from django.contrib.contenttypes.models import ContentType
>>> user_type = ContentType.objects.get(app_label='auth', model='user')>>> user_type
<ContentType: user>

Y luego utilícelo para realizar consultas sobre un particular User, o para acceder a la User clase modelo:

>>> user_type.model_class()<class'django.contrib.auth.models.User'>>>> user_type.get_object_for_this_type(username='Guido')<User: Guido>

Juntos, get_object_for_this_type() y model_class() habilitar dos casos de uso extremadamente importantes:

  1. Con estos métodos, puede escribir código genérico de alto nivel que realiza consultas en cualquier modelo instalado; en lugar de importar y usar una única clase de modelo específica, puede pasar una app_label y model en un ContentType buscar en tiempo de ejecución y luego trabajar con la clase modelo o recuperar objetos de ella.
  2. Puedes relacionar otro modelo con ContentType como una forma de vincular instancias de él a clases de modelo particulares, y use estos métodos para obtener acceso a esas clases de modelo.

Varias de las aplicaciones empaquetadas de Django hacen uso de esta última técnica. Por ejemplo, the permissions system en el marco de autenticación de Django usa un Permission modelo con una clave externa para ContentType; esto permite Permission representan conceptos como “puede agregar una entrada de blog” o “puede eliminar una noticia”.

los ContentTypeManager

class ContentTypeManager

ContentType también tiene un administrador personalizado, ContentTypeManager, que agrega los siguientes métodos:

clear_cache()

Borra una caché interna utilizada por ContentType para realizar un seguimiento de los modelos para los que ha creado ContentType instancias. Probablemente nunca necesitará llamar a este método usted mismo; Django lo llamará automáticamente cuando sea necesario.

get_for_id(id)

Buscar un ContentType por ID. Dado que este método utiliza la misma caché compartida que get_for_model(), se prefiere utilizar este método sobre el habitual ContentType.objects.get(pk=id)

get_for_model(model, for_concrete_model=True)

Toma una clase de modelo o una instancia de un modelo y devuelve el ContentType instancia que representa ese modelo. for_concrete_model=False permite buscar el ContentType de un modelo de proxy.

get_for_models(*models, for_concrete_models=True)

Toma un número variable de clases de modelo y devuelve un diccionario que asigna las clases de modelo a la ContentType instancias que los representan. for_concrete_models=False permite buscar el ContentType de modelos proxy.

get_by_natural_key(app_label, model)

Devuelve el ContentType instancia identificada de forma única por la etiqueta de aplicación y el nombre del modelo dados. El propósito principal de este método es permitir ContentType objetos a los que se hace referencia a través de un clave natural durante la deserialización.

los get_for_model() El método es especialmente útil cuando sabes que necesitas trabajar con un ContentType pero no quiero tomarse la molestia de obtener los metadatos del modelo para realizar una búsqueda manual:

>>>from django.contrib.auth.models import User
>>> ContentType.objects.get_for_model(User)<ContentType: user>

Relaciones genéricas

Agregar una clave externa de uno de sus propios modelos a ContentType permite que su modelo se vincule efectivamente a otra clase de modelo, como en el ejemplo de la Permission modelo anterior. Pero es posible ir un paso más allá y usar ContentType para permitir relaciones verdaderamente genéricas (a veces llamadas “polimórficas”) entre modelos.

Por ejemplo, podría usarse para un sistema de etiquetado como este:

from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models

classTaggedItem(models.Model):
    tag = models.SlugField()
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type','object_id')def__str__(self):return self.tag

Un normal ForeignKey sólo puede “apuntar a” otro modelo, lo que significa que si el TaggedItem modelo utilizó un ForeignKey tendría que elegir uno y solo un modelo para almacenar etiquetas. La aplicación contenttypes proporciona un tipo de campo especial (GenericForeignKey) que soluciona esto y permite que la relación sea con cualquier modelo:

class GenericForeignKey

Hay tres partes para configurar un GenericForeignKey:

  1. Dale a tu modelo un ForeignKey para ContentType. El nombre habitual de este campo es “content_type”.
  2. Proporcione a su modelo un campo que pueda almacenar valores de clave primaria de los modelos con los que se relacionará. Para la mayoría de los modelos, esto significa PositiveIntegerField. El nombre habitual de este campo es “object_id”.
  3. Dale a tu modelo un GenericForeignKeyy pásele los nombres de los dos campos descritos anteriormente. Si estos campos se denominan “content_type” y “object_id”, puede omitirlos; esos son los nombres de campo predeterminados. GenericForeignKey buscará.
for_concrete_model

Si False, el campo podrá hacer referencia a modelos de proxy. El valor predeterminado es True. Esto refleja el for_concrete_model argumento para get_for_model().

Compatibilidad del tipo de clave principal

El campo “object_id” no tiene que ser del mismo tipo que los campos de clave principal en los modelos relacionados, pero sus valores de clave principal deben ser coercibles al mismo tipo que el campo “object_id” por su get_db_prep_value() método.

Por ejemplo, si desea permitir relaciones genéricas a modelos con IntegerField o CharField campos de clave primaria, puede utilizar CharField para el campo “object_id” en su modelo, ya que los números enteros se pueden convertir en cadenas mediante get_db_prep_value().

Para una máxima flexibilidad, puede utilizar un TextField que no tiene una longitud máxima definida, sin embargo, esto puede incurrir en importantes penalizaciones de rendimiento según el backend de su base de datos.

No existe una solución única que se adapte a todos para determinar cuál es el mejor tipo de campo. Debe evaluar los modelos a los que espera apuntar y determinar qué solución será más efectiva para su caso de uso.

Serializar referencias a ContentType objetos

Si está serializando datos (por ejemplo, al generar fixtures) de un modelo que implementa relaciones genéricas, probablemente debería utilizar una clave natural para identificar de forma única ContentType objetos. Ver claves naturales y dumpdata --natural-foreign para más información.

Esto habilitará una API similar a la que se usa para un ForeignKey; cada TaggedItem tendrá un content_object campo que devuelve el objeto con el que está relacionado, y también puede asignar a ese campo o usarlo al crear un TaggedItem:

>>>from django.contrib.auth.models import User
>>> guido = User.objects.get(username='Guido')>>> t = TaggedItem(content_object=guido, tag='bdfl')>>> t.save()>>> t.content_object
<User: Guido>

Si se elimina el objeto relacionado, el content_type y object_id los campos permanecen configurados en sus valores originales y el GenericForeignKey devoluciones None:

>>> guido.delete()>>> t.content_object  # returns None

Debido a la forma GenericForeignKey está implementado, no puede utilizar dichos campos directamente con filtros (filter() y exclude(), por ejemplo) a través de la API de la base de datos. Porque un GenericForeignKey no es un objeto de campo normal, estos ejemplos no trabaja:

# This will fail>>> TaggedItem.objects.filter(content_object=guido)# This will also fail>>> TaggedItem.objects.get(content_object=guido)

Igualmente, GenericForeignKeys no aparece en ModelForms.

Relaciones genéricas inversas

class GenericRelation
related_query_name

La relación del objeto relacionado con este objeto no existe de forma predeterminada. Configuración related_query_name crea una relación entre el objeto relacionado y este. Esto permite consultar y filtrar desde el objeto relacionado.

Si sabe qué modelos utilizará con más frecuencia, también puede agregar una relación genérica “inversa” para habilitar una API adicional. Por ejemplo:

from django.contrib.contenttypes.fields import GenericRelation
from django.db import models

classBookmark(models.Model):
    url = models.URLField()
    tags = GenericRelation(TaggedItem)

Bookmark instancias cada una tendrá un tags atributo, que se puede utilizar para recuperar su asociado TaggedItems:

>>> b = Bookmark(url='https://www.djangoproject.com/')>>> b.save()>>> t1 = TaggedItem(content_object=b, tag='django')>>> t1.save()>>> t2 = TaggedItem(content_object=b, tag='python')>>> t2.save()>>> b.tags.all()<QuerySet [<TaggedItem: django>,<TaggedItem: python>]>

También puedes usar add(), create(), o set() para crear relaciones:

>>> t3 = TaggedItem(tag='Web development')>>> b.tags.add(t3, bulk=False)>>> b.tags.create(tag='Web framework')<TaggedItem: Web framework>>>> b.tags.all()<QuerySet [<TaggedItem: django>,<TaggedItem: python>,<TaggedItem: Web development>,<TaggedItem: Web framework>]>>>> b.tags.set([t1, t3])>>> b.tags.all()<QuerySet [<TaggedItem: django>,<TaggedItem: Web development>]>

los remove() La llamada eliminará de forma masiva los objetos de modelo especificados:

>>> b.tags.remove(t3)>>> b.tags.all()<QuerySet [<TaggedItem: django>]>>>> TaggedItem.objects.all()<QuerySet [<TaggedItem: django>]>

los clear() El método se puede utilizar para eliminar de forma masiva todos los objetos relacionados para una instancia:

>>> b.tags.clear()>>> b.tags.all()<QuerySet []>>>> TaggedItem.objects.all()<QuerySet []>

Definiendo GenericRelation con related_query_name set permite realizar consultas desde el objeto relacionado:

tags = GenericRelation(TaggedItem, related_query_name='bookmark')

Esto permite filtrar, ordenar y otras operaciones de consulta en Bookmark de TaggedItem:

>>># Get all tags belonging to bookmarks containing `django` in the url>>> TaggedItem.objects.filter(bookmark__url__contains='django')<QuerySet [<TaggedItem: django>,<TaggedItem: python>]>

Si no agrega el related_query_name, puede hacer los mismos tipos de búsquedas manualmente:

>>> bookmarks = Bookmark.objects.filter(url__contains='django')>>> bookmark_type = ContentType.objects.get_for_model(Bookmark)>>> TaggedItem.objects.filter(content_type__pk=bookmark_type.id, object_id__in=bookmarks)<QuerySet [<TaggedItem: django>,<TaggedItem: python>]>

Tal como GenericForeignKey acepta los nombres de los campos de tipo de contenido e ID de objeto como argumentos, también lo hace GenericRelation; Si el modelo que tiene la clave externa genérica utiliza nombres no predeterminados para esos campos, debe pasar los nombres de los campos al configurar un GenericRelation lo. Por ejemplo, si el TaggedItem modelo referido a los campos usados ​​anteriormente nombrados content_type_fk y object_primary_key para crear su clave externa genérica, luego un GenericRelation volver a él tendría que definirse así:

tags = GenericRelation(
    TaggedItem,
    content_type_field='content_type_fk',
    object_id_field='object_primary_key',)

Tenga en cuenta también que si elimina un objeto que tiene un GenericRelation, cualquier objeto que tenga un GenericForeignKey que apunte a ella también se eliminará. En el ejemplo anterior, esto significa que si un Bookmark objeto fueron eliminados, cualquier TaggedItem los objetos que apuntan a ella se eliminarán al mismo tiempo.

diferente a ForeignKey, GenericForeignKey no acepta un on_delete argumento para personalizar este comportamiento; si lo desea, puede evitar la eliminación en cascada al no usar GenericRelation, y se puede proporcionar un comportamiento alternativo a través del pre_delete señal.

Relaciones genéricas y agregación

API de agregación de bases de datos de Django trabaja con un GenericRelation. Por ejemplo, puede averiguar cuántas etiquetas tienen todos los marcadores:

>>> Bookmark.objects.aggregate(Count('tags'))'tags__count':3

Relación genérica en formas

los django.contrib.contenttypes.forms módulo proporciona:

  • BaseGenericInlineFormSet
  • Una fábrica de formset, generic_inlineformset_factory(), para usar con GenericForeignKey.
class BaseGenericInlineFormSet
generic_inlineformset_factory(model, form=ModelForm, formset=BaseGenericInlineFormSet, ct_field="content_type", fk_field="object_id", fields=None, exclude=None, extra=3, can_order=False, can_delete=True, max_num=None, formfield_callback=None, validate_max=False, for_concrete_model=True, min_num=None, validate_min=False, absolute_max=None, can_delete_extra=True)

Devuelve un GenericInlineFormSet utilizando modelformset_factory().

Debes proveer ct_field y fk_field si son diferentes de los predeterminados, content_type y object_id respectivamente. Otros parámetros son similares a los documentados en modelformset_factory() y inlineformset_factory().

los for_concrete_model El argumento corresponde al for_concrete_model argumento sobre GenericForeignKey.

Cambiado en Django 3.2:

los absolute_max y can_delete_extra Se agregaron argumentos.

Relaciones genéricas en administración

los django.contrib.contenttypes.admin el módulo proporciona GenericTabularInline y GenericStackedInline (subclases de GenericInlineModelAdmin)

Estas clases y funciones permiten el uso de relaciones genéricas en formularios y el administrador. Ver el modelo de conjunto de formularios y administración documentación para obtener más información.

class GenericInlineModelAdmin

los GenericInlineModelAdmin La clase hereda todas las propiedades de una InlineModelAdmin clase. Sin embargo, agrega algunos propios para trabajar con la relación genérica:

ct_field

El nombre de ContentType campo de clave externa en el modelo. Predeterminado a content_type.

ct_fk_field

El nombre del campo entero que representa el ID del objeto relacionado. Predeterminado a object_id.

class GenericTabularInline
class GenericStackedInline

Subclases de GenericInlineModelAdmin con diseños apilados y tabulares, respectivamente.