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çãodo 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
ContentTypepossui 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_labeldo 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_namedo 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_labelserá setado como'sites'(a última parte do caminho Python “django.contrib.sites”).modelserá setada como'site'.nameserá setada como'site'.
Métodos das instâncias do ContentType¶
-
class
models.ContentType Cada instância
ContentTypepossui métodos que permitem você pegar através da instância doContentTypeo 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
ContentTyperepresenta, 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_labelemodeldentro de umContentTypeem tempo de execução, e então trabalhar com a classe model ou receber objetos dela. - Você pode relacionar outro model com
ContentTypecomo 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
ContentTypetambé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
ContentTyperepresentando 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
ForeignKeyparaContentType.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
IntegerFieldouPositiveIntegerField.)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 umCharFieldcomo 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 queGenericForeignKeyirá 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
GenericInlineModelAdminherda 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
ContentTypepara 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.
-