O Framework contenttypes¶
O Django incluí uma applicação contenttypes
que pode mapear todos os
modelos instalados no seu projeto feito com o Django, provendo uma interface
genérica de auto-nível para trabalhar com seus models.
Visão geral¶
O coração da aplicação contenttypes é o model
ContentType
, que reside em
django.contrib.contenttypes.models.ContentType
. As instâncias de
ContentType
representam e armazenam
informações sobre os models instalados no seu projeto, e novas instâncias de
ContentType
são automaticamente
criadas quando um novos models são instalados.
As instâncias do ContentType
possuem métodos para retornar as classes de models que elas representam e para
consultas de objetos para estes models.
O ContentType
também tem um
gerenciador customizado que adiciona métodos par
trabalhar com ContentType
e para
obtenção de instâncias de
ContentType
para um model em
particular.
Os relacionamentos entre seus models e
ContentType
pode também ser usados
para habilitar relacionamentos “genericos” entre uma instância de um de seus
models e instâncias de qualquer model que você tenha instalado.
Instalando o framework contenttypes¶
O framework contenttypes vem incluído por padrão no INSTALLED_APPS
quando é criado pelo django-admin.py startproject
, mas se você o tiver
removido ou se você manualmente setou seu INSTALLED_APPS
, você pode
habilitá-lo adicionando 'django.contrib.contenttypes'
no
INSTALLED_APPS
.
Geralmente é uma boa idéia ter o framework contenttypes instaldo; vários outras applicações nativas do Django necessitam dele:
- A aplicação admin o usa para logar a história de cada objeto adicionado ou modificado através da interface de administração.
- O
framework de autenticação
do Django o usa para amarrar as permissões de usuários para models específicos. - O sistema de comentários do Django (
django.contrib.comments
) o usa para “atachar” comentário em qualquer model instalado.
O model ContentType
¶
-
class
models.
ContentType
¶ Cada instância do
ContentType
possui três campos, que juntos, descrevem a unicidade de um model instalado:-
app_label
¶ O nome da applicação da qual o model faz parte. Este é o atributo
app_label
do model, e incluí somente a última parte do caminho de import do Python da aplicação; “django.contrib.contenttypes”, por exemplo, tem umapp_label
“contenttypes”.
-
model
¶ O nome da classe do model.
-
name
¶ O nome, legível por humanos, do model. Este é o atributo
verbose_name
do model.
-
Vamos olhar um exemplo, para entender como isso funciona. Se você já tem a
aplicação contenttypes instalada, e adicionou a aplicação sites
no INSTALLED_APPS
e executou o
manage.py syncdb
para instalá-lo, o model
django.contrib.sites.models.Site
será instalado dentro do seu banco de
dados. Imediatamente uma nova instância de
ContentType
será criada com os
seguintes valores:
app_label
será setado como'sites'
(a última parte do caminho Python “django.contrib.sites”).model
será setada como'site'
.name
será setada como'site'
.
Métodos das instâncias do ContentType
¶
-
class
models.
ContentType
Cada instância
ContentType
possui métodos que permitem você pegar através da instância doContentType
o model que ela representa, ou receber objetos daquele model:
-
models.ContentType.
get_object_for_this_type
(**kwargs)¶ Pega um conjunto de argumentos aparentes válidos para o model que
ContentType
representa, e faz um get() lookup sobre este model, retornando o objeto correspondente.
-
models.ContentType.
model_class
()¶ Retorna a classe de model representada por esta instância de
ContentType
.
Por exemplo, nós podemos procurar no
ContentType
pelo model
User
:
>>> from django.contrib.contenttypes.models import ContentType
>>> user_type = ContentType.objects.get(app_label="auth", model="user")
>>> user_type
<ContentType: user>
E então usá-lo para pesquisar por um User
particular, ou para ter acesso a
classe model do User
:
>>> 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()
e model_class()
habilitam dois casos extremamente importantes:
- Usando estes métodos, você pode escrever código genérico de auto-nível
que faz consultas sobre qualquer model instalado – ao invés de importar
e usar uma classe de model específica, você passar um
app_label
emodel
dentro de umContentType
em tempo de execução, e então trabalhar com a classe model ou receber objetos dela. - Você pode relacionar outro model com
ContentType
como uma forma de vinculá-lo a determinadas classes de model, e usar estes métodos para acessar essas classes.
Várias aplicações nativas do Django fazem uso desta última técnica. Por exemplo,
o sistema de permissões <django.contrib.auth.models.Permission
> no
framework de autenticação usa um model
Permission
com uma chave estrangeira para
ContentType
; isso permite que o
Permission
represente conceitos
como “pode adicionar entrada no blog” ou “pode apagar notícia”.
O ContentTypeManager
¶
-
class
models.
ContentTypeManager
¶ O
ContentType
também tem um gerenciador personalizado,ContentTypeManager
, que adiciona os seguintes métodos:-
clear_cache
()¶ Limpa um cache interno usado por instâncias do
ContentType
. Você provavelmente nunca precisará chamar este método você mesmo; O Django o chamará automaticamente quando for necessário.
-
get_for_model
(model)¶ Pega ambos, um model ou uma instância de um model, e retorna a instância
ContentType
representando aquele model.
-
O método get_for_model()
e especialmente
usual quando você sabe que precisa trabalhar com um ContentType
mas não quer ter o trabalho de
obter os metadados do model para executar um lookup manual:
>>> from django.contrib.auth.models import User
>>> user_type = ContentType.objects.get_for_model(User)
>>> user_type
<ContentType: user>
Relações genéricas¶
Adicionando uma chave estrangeira em um de seus models para
ContentType
permite seu model
vincular-se efetivamente em outro model, como no exemplo da classe
Permission
acima. Mas isto é possível ir
além e usar ContentType
para
habilitar um relacionamento verdadeiramente genérico (algumas vezes chamado
“polimórfico”) entre os models.
Um simples exemplo é um sistema de tags, que pode parecer com isto:
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
class TaggedItem(models.Model):
tag = models.SlugField()
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
def __unicode__(self):
return self.tag
Uma ForeignKey
normal pode somente
“apontar para” um outro model, que significa que se o model TaggedItem
usou
uma ForeignKey
ele teria de escolher
somente um model para armazenar as tags. A aplicação contenttypes provê um tipo
de campo especial – django.contrib.contenttypes.generic.GenericForeignKey
– que funciona
contornando isto e permitindo o relacionamento ser feito com qualquer model.
Há três configurações para uma
GenericForeignKey
:
Dê a seu model uma
ForeignKey
paraContentType
.Dê a seu model um campo que consiga armazenar um valor de chave primária dos models que você irá relacionar. (Para maioria dos models, isto significa um
IntegerField
ouPositiveIntegerField
.)Este campo deve ser do mesmo tipo da chave primária dos models envolvidos no relacionamento genérico. Por exemplo, se você usa
IntegerField
, você não será capaz de formar uma relação genérica com um model que utiliza umCharField
como uma chave primária.Dê a seu model uma
GenericForeignKey
, e passe os nomes dos campos descritos acima para ela. Se estes campos são nomeados “content_type” e “object_id”, você pode omitir isto – estes são nomes padrão dos campos queGenericForeignKey
irá procurar.
Isto habilitará uma API similar a que é usada por uma
ForeignKey
normal; cada TaggedItem
terá um campo content_type
que retorna o objeto ao qual está relacionado,
e você pode também atribuir ao campo ou usá-lo quando estiver criando uma
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>
Devido a forma como o
GenericForeignKey
é implementado,
você não pode usar campos estes campos diretamente com filtros (filter()
e exclude()
, por exemplo) via API de banco de dados. Eles não são campos
normal de objetos. Estes exemplos não irão funcionar:
# Isto falhará
>>> TaggedItem.objects.filter(content_object=guido)
# Isto também falhará
>>> TaggedItem.objects.get(content_object=guido)
Reverso de relações genéricas¶
Se você sabe quais models serão usados com mais frequência, você pode também adicionar um “reverso” de relações genéricas para habilitar uma API adicional. Por exemplo:
class Bookmark(models.Model):
url = models.URLField()
tags = generic.GenericRelation(TaggedItem)
Cada instância Bookmark
terá um atributo tags
, que pode ser usado para
receber seus TaggedItems
associados:
>>> b = Bookmark(url='http://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()
[<TaggedItem: django>, <TaggedItem: python>]
Assim como django.contrib.contenttypes.generic.GenericForeignKey
aceita
os nomes dos campos content-type e object-ID como argumentos, para construir uma
GenericRelation
; se o model que possui a chave estrangeira genérica está
usando nomes diferentes do padrão, para estes campos, você deve passar os nomes
dos campos quando estiver configurando uma GenericRelation
. Por exemplo, se
o model TaggedItem
referido acima usasse campos com nomes
content_type_fk
e object_primary_key
para criar suas chaves primárias
genéricas, então uma GenericRelation
teria de ser definida como:
tags = generic.GenericRelation(TaggedItem, content_type_field='content_type_fk', object_id_field='object_primary_key')
É claro, Se você não adicionar o reverso de relacionamento, você pode fazer os mesmos tipo de busca manualmente:
>>> b = Bookmark.objects.get(url='http://www.djangoproject.com/')
>>> bookmark_type = ContentType.objects.get_for_model(b)
>>> TaggedItem.objects.filter(content_type__pk=bookmark_type.id,
... object_id=b.id)
[<TaggedItem: django>, <TaggedItem: python>]
Note que se o model com uma
GenericForeignKey
que você está
referenciando usa um valor diferente do padrão para ct_field
ou fk_field
(e.g. a aplicação django.contrib.comments
usa ct_field="object_pk"
),
você precisará passar content_type_field
e object_id_field
para
GenericRelation
.:
comments = generic.GenericRelation(Comment, content_type_field="content_type", object_id_field="object_pk")
Note que se você deleta um objeto que tem uma
GenericRelation
, quaisquer objetos
que possuem uma GenericForeignKey
apontando para ele, serão deletados também. No exemplo acima, isto significa que
se um objeto Bookmark
foi deletado, qualquer objeto TaggedItem
relacionado com ele, será deletado ao mesmo tempo.
Relacionamentos genéricos em formulários e admin¶
O django.contrib.contenttypes.generic
provê ambos,
GenericInlineFormSet
e
GenericInlineModelAdmin
. Isso
permite o uso de relacionamentos genéricos nos formulários e no admin. Veja a
documentação do model formset e
admin para mais informações.
-
class
generic.
GenericInlineModelAdmin
¶ A classe
GenericInlineModelAdmin
herda todas as propriedades da classeInlineModelAdmin
. No entanto, ela adiciona alguns próprios para funcionar com relacionamento genérico.-
ct_field
¶ O nome do campo chave estrangeira
ContentType
para o model. O padrão écontent_type
.
-
ct_fk_field
¶ O nome do campo inteiro que representa o ID do objeto relacionado. O padrão é
object_id
.
-