Escrevendo campos de model customizados

Novo no Django 1.0: Please, see the release notes

Introdução

A documentação de referência do model explica como usar as classes de campos padrão do Django – CharField, DateField, etc. Para muitos propósitos, estas classes são todas necessárias. Algumas vezes, contudo, a versão do Django não atenderá precisamente as suas exigências, ou você desejará usar um campo que é completamente diferente daqueles entregues com o Django.

Os tipos de campos nativos do Django não cobrem todas as possibilidades de tipos de colunas – somente os tipos comuns, como VARCHAR e INTEGER. Para os tipos de campos mais obscuros, com polígonos geográficos ou mesmo tipos criados pelo usuário como tipos personalizados do PostgreSQL, você pode definir sua própria subclasse Field do Django.

Alternativamente, você pode ter um objeto do Python complexo que pode de alguma forma ser serializado para caber dentro da coluna padrão do bando de dados. Este é outro caso onde uma subclasse de Field ajudará você a usar seu objeto com seus models.

Nosso objeto exemplo

Criar campos personalizados requer um pouco de atenção nos detalhes. Para tornar as coisas mais fáceis de seguir, nós usaremos um exmplo consistente através deste documento: envolvendo um objeto Python representando a oferta de cartas numa mão de Bridge. Não se preocupe, você não precisa saber como jogar Bridge para seguir este exemplo. Você somente precisa saber que 52 cartas são oferecidas igualmente para 4 jogadores, que são tradicionalmente chamados de north, east, south e west. Nossa classe se parece com essa:

class Hand(object):
    def __init__(self, north, east, south, west):
        # Input parameters are lists of cards ('Ah', '9s', etc)
        self.north = north
        self.east = east
        self.south = south
        self.west = west

    # ... (outros métodos possivelmente úteis omitidos) ...

Esta é somente uma classe Python, com nada específico do Django nela. Nós gostariámos de ter a possibilidade de fazer coisas como essa nos nossos models (nós assumimos que o atributo hand no model é uma instância de Hand):

example = MyModel.objects.get(pk=1)
print example.hand.north

new_hand = Hand(north, east, south, west)
example.hand = new_hand
example.save()

Nós atribuímos e recebemos do atributo hand no nosso model assim como em qualquer outra classe Python. O truque é dizer ao Django como guardar e carregar um objeto.

A fim de usar a classe Hand em nossos models, nós não temos que mudar esta classe em nada. Este é o ideal, pois significa que você pode facilmente escrever um suporte de model para classes existentes onde você não pode mudar o código fonte.

Note

Você pode estar somente querendo obter vantagem dos tipos de campos personalizados de banco de dados e lidar com dados como tipos padrões do Python nos seus models; string, ou floats, por exemplo. Este caso é semelhante ou nosso exemplo Hand e vamos notar as diferenças a medida que avançamos.

Teoria de fundo

Armazenamento de banco de dados

A forma mais simples de pensar um campo de model é que ele fornece uma forma de obter um objeto Python normal -- string, boolean, datetime, ou algo mais complexo como Hand -- e convertê-lo de e para um formato que é útil quando se trabalha com o banco de dados (e serialização, mas, como veremos depois, que cai quase que naturalmente quando você tem o lado do banco de dados sob controle ).

Campos num model de alguma forma devem ser convertidos para caber num tipo de coluna de banco de dados existente. Diferentes bancos de dados oferencem diferentes conjuntos de tipos de colunas válidas, mas a regra se mantem a mesma: esses são somente tipos com os quais você tem que trabalhar. Qualquer coisa que você queira armazenar num banco de dados deve combinar com algum desses tipos.

Normalmente, você quer escrever um campo do Django para um tipo de coluna de banco de dados em particular, ou existe uma maneira bastante simples para converter seus dados para, digamos, uma string.

Para o nosso exemplo Hand, nós poderíamos converter os dados das cartas para uma string de 104 caracteres concatenando todas as cartas juntas numa ordem pre-determinada -- digamos, todas as cartas north primeiro, então as cartas east, south e west. Então os objetos Hand podem ser salvos em colunas de texto ou caracteres no banco de dados.

O que uma classee de campo faz?

Todos os campos do Django (e quando dizemos campos neste documento, nós sempre nos referimos a campos dos models e não campos de formulário) são subclasses de django.db.models.Field. A maior parte das informações que o Django grava sobre um campo é comum a todos os campos -- nome, texto de ajuda, unicidade e assim em diante. O armazenamento de toda essa informação é feito pelo Field. Nós entraremos nos detalhes precisos do que o Field pode fazer depois; por agora, é suficiente dizer que tudo descende de Field e então personalizar peças chave do comportamento da classe.

É importante notar que uma classe de campos do Django não é o que é armazenado nos atributos do seu model. Os atributos do model contêm objetos Python normais. As classes campo que você define num model são na verdade armazenadas na classe Meta quando a classe model é criada (os detalhes de como isso é feito não são importantes aqui). Isso é porque as classes campo não são necessárias quando você estiver criando e modificando atributos. Em vez disso, elas fornecem o mecanismo pra conversão entre o valor do atributo e o que é armazenado no banco de dados ou enviado para o serializer.

Mantenha isso em mente quando estiver criando seus próprios campos. A subclasse Field do Djando que você escrever, fornece mecanismos para conversão entre suas instâncias do Python e os valores do banco de dados ou serializer de várias formas (há diferenças entre armazenar um valor e usar um valor para pesquisas, por exemplo). Se isso soa um pouco complicado, não se preocupe -- ele se tornará claro nos exemplos abaixo. Basta lembrar que muitas vezes você vai acabar criando duas classes quando você quer um campo personalizado:

  • A primeira classe é o objeto Python que seus usuários irão manipular. Eles a atribuirão para o atributo do model, eles irão lê-lo para fins de exibição, coisas desse tipo. Esta é a classe Hand no nosso exemplo.
  • A segunda classe é a subclasse de Field. Esta é a classe que sabe como converter sua primeira classe em ambos sentidos entre sua fora de armazenamento permanente e na forma do Python.

Escrevendo uma subclasse de campo

Quando estiver brincando com sua subclasse de Field, primeiro reflita um pouco sobre a classe Field existente, sua nova classe é muito semelhante a ela. Você consegue estender um campo existente do Django e poupar um pouco de trabalho? Se não, você deve estender a classe :class:`~django.db.models.Field`d, a partir da qual todas descendem.

Inicializar o seu novo campo é uma questão de separar os argumentos que são específicos para seu caso dos argumentos comuns e passando este último para o método __init__() da classe Field (ou sua classe pai).

No nosso exemplo, nós chamaremos nosso campo de HandField. (É uma boa idéia chamar sua subclasse Field de <AlgumaCoisa>Field, isso a torna fácil de identificar como uma subclasse de Field.) Ele não se comporta como qualquer campo existente, sendo assim, nós estenderemos a Field diretamente:

from django.db import models

class HandField(models.Field):
    def __init__(self, *args, **kwargs):
        kwargs['max_length'] = 104
        super(HandField, self).__init__(*args, **kwargs)

Nosso HandField aceita a maioria das opções padrão (veja a lista abaixo), mas nós asseguramos que ela tenha um comprimento fixo, já que somente precisa manter 52 valores de cartas mais seus ternos; 104 caracteres no total.

Note

Muitos campos de models do Django aceitam opções com as quais não fazem nada. Por exemplo, você pode passar ambos editable e auto_now para um django.db.models.DateField e ele simplesmente irá ignorar o parâmetro editable (auto_now sendo definidos implica em editable=False). Nenhum erro será mostrado nesse caso.

Este comportamento simplica as classes de campo, pois elas não precisam verificar por opções que não são necessárias. Elas somente passam todas as opções para a classe pai e então não as usam depois. Cabe a você decidir se você deseja que os campos sejam mais estritos quanto as opções que eles podem selecionar, ou usar a forma mais simples, um comportamento mais permissivo dos campos atuais.

O método __init__() recebe os seguintes parâmetros:

Todas as opções sem uma exmplicação na lista acima, tem o mesmo significado que nos campos normais do Django. Veja a documentação de campos para exemplos e detalhes.

A metaclasse SubfieldBase

Como indicammos na introdução, subclasses de campo são frequentemente necessárias por duas razões: tanto para se obter vantagem sobre um tipo de coluna de anco de dados personalizada, quanto para manipular tipos complexos do Python. Obviamente, uma combinação dos dois também é possível. Se você está trabalhando somente com tipos de colunas de banco de dados personalizados e seus campos de model aparecem no Python como tipos do Python padrão direto do banco de dados, você não precisa se preocupar com essa seção.

Se você estiver manipulando tipos personalizados do Python, como a nossa classe Hand, nós precisamos ter certeza de que quando o Django inicializar uma instância de seu model e atributir um valor do banco de dados para nosso atributo personalizado, nós converteremos esse valor para dentro do objeto Python apropriado. Os detalhes de como isso acontece internamente são um pouco complexos, mas o código que você precisa escrever na sua classe Field é simples: assegure-se de que sua subclasse use uma metaclasse especial:

class django.db.models.SubfieldBase

Por exemplo:

class HandField(models.Field):
    __metaclass__ = models.SubfieldBase

    def __init__(self, *args, **kwargs):
        # ...

Isso assegura que o método to_python(), documentado abaixo, sempre será chamado quando o atributo for inicializado.

Método úteis

Uma vez que você tenha criado sua subclasse de Field e configurado o __metaclass__, você pode considerar sobrescrever alguns métodos padrão, dependendo do comportamento do seu campo. A lista de métodos abaixo está numa ordem descrescente aproximada de importância, então comece de cima.

Tipos de banco de dados personalizados

db_type(self)

Retorna o tipo da coluna do banco de dados para Field, tendo em conta a configuração atual DATABASE_ENGINE.

Digamos que você criou um tipo personalizado do PostgreSQL chamado mytype. Você pode usar este campo com o Django através de uma extensão de Field e implementando o método db_type(), tipo assim:

from django.db import models

class MytypeField(models.Field):
    def db_type(self):
        return 'mytype'

Uma vez que você tenha MytypeField, você pode usá-lo em qualquer model, assim como qualquer outro tipo Field:

class Person(models.Model):
    name = models.CharField(max_length=80)
    gender = models.CharField(max_length=1)
    something_else = MytypeField()

Se você pretende construir uma aplicação agnóstica de banco de dados, você deve considerar diferenças nos tipos de campos de banco de dados. Por exemplo, o tipo de coluna data/hora no PostgreSQL é chamado timestamp, enquanto que a mesma coluna no MySQL é chamado de datetime. A forma mais simples de lidar com isso dentro do método db_type() é importando o módulo de configurações do Django e verificando o DATABASE_ENGINE. Por exemplo:

class MyDateField(models.Field):
    def db_type(self):
        from django.conf import settings
        if settings.DATABASE_ENGINE == 'mysql':
            return 'datetime'
        else:
            return 'timestamp'

O método db_type() é somente chamado pelo Django quando o framework constrói a cláusula CREATE_TABLE para sua aplicação -- isto é, quando você criar suas tabelas. Ele não é chamado em qualquer outro momento, assim ele pode executar um código pouco complexo, tal como a verificação do DATABASE_ENGINE do exemplo acima.

Alguns typos de colunas aceitam parâmetros, como o CHAR(25), onde o parâmetro 25 representa o comprimento máximo da coluna. Em casos como este, é mais flexível se o parâmetro for especificado no model ao invês de ser embutido no código do método db_type(). Por exemplo, não faria muito sentido ter o CharMaxlength25Field, mostrado aqui:

# Este é um exemplo estranho de parâmetros embutidos no código.
class CharMaxlength25Field(models.Field):
    def db_type(self):
        return 'char(25)'

# No model:
class MyModel(models.Model):
    # ...
    my_field = CharMaxlength25Field()

A melhor forma de fazer isso, seria fazer o parâmetro especificável em tempo de execução -- i.e., quando a classe for instânciada. Para fazer isso, basta implementar o django.db.models.Field.__init__(), tipo assim:

# Este é um exemplo muito mais flexível.
class BetterCharField(models.Field):
    def __init__(self, max_length, *args, **kwargs):
        self.max_length = max_length
        super(BetterCharField, self).__init__(*args, **kwargs)

    def db_type(self):
        return 'char(%s)' % self.max_length

# No model:
class MyModel(models.Model):
    # ...
    my_field = BetterCharField(25)

Finalmente, se sua coluna requer um SQL de instalação verdadeiramente complexo, retorne None do método db_type(). Isso fará o criador de código SQL do Django pular este campo. Você será então responsável por criar a coluna na tabela certa de alguma outra forma, mas isso lhe dá uma forma de de dizer ao Django para sair do caminho.

Convertendo valores de banco de dados para objetos do Python

to_python(self, value)

Converte um valor retornado do seu banco de dados (ou serializer) para um objeto Python.

A implementação padrão simplesmente retorna value, para o caso comum em que o backend de banco de dados já retorna o dado no formato correto (como uma string Python, por exemplo).

Se sua classe Field personalizada lida com estrutura de dados que snao mais complexas que strings, datas, inteiros ou floats, então você precisará sobrescrever esse métod. Como uma regra geral, o método deve tomar cuidado com qualquer um dos seguintes argumentos:

  • Uma instância do tipo correto (e.g., Hand no nosso exemplo em andamento).
  • Uma string (e.g., de um deserializer).
  • Qualquer coisa que o banco de dados retorna para o tipo de coluna que você está usando.

Na sua classe HandField, nós armazenamos os dados como um campo VARCHAR no banco de dados, então nós precisamos ser capazes de processar strings e instâncias de Hand no to_python():

import re

class HandField(models.Field):
    # ...

    def to_python(self, value):
        if isinstance(value, Hand):
            return value

        # O caso de string.
        p1 = re.compile('.{26}')
        p2 = re.compile('..')
        args = [p2.findall(x) for x in p1.findall(value)]
        return Hand(*args)

Perceba que nós sempre retornams uma instância de Hand deste método. Este é o tipo de objeto Python que desejamos armazenar no atributo do model.

Lembre-se: Se seu campo personalizado precisa que o método to_python() seja chamado quando ele for criado, você deve estar usando A metaclasse SubfieldBase mencionada anteriomente. Caso contrário to_python() não será chamado automaticamente.

Convertendo objetos Python para valores de banco de dados

get_db_prep_value(self, value)

Este é o contrário de to_python() quando trabalha com o backend de banco de dados (como o oposto de serialização). O parâmetro value é o valor atual do atributo do model (um campo não tem referência para o model que o armazena, então ele não pode receber o valor por si só), e o método deve retornar dados no formato que possa ser usado como um parâmetro numa consulta ao banco de dados.

Por exemplo:

class HandField(models.Field):
    # ...

    def get_db_prep_value(self, value):
        return ''.join([''.join(l) for l in (value.north,
                value.east, value.south, value.west)])
get_db_prep_save(self, value)

O mesmo mostrado acima, mas chamado quando o valor do campo deve ser salvo no banco de dados. Como a implementação padrão apenas chama get_db_prep_value, você não precisa implementar este método a menos que seu campo personalizad precise de uma conversão especial ao ser salvo, o que não é o mesmo quando a conversão é usada para parâmetros de consultas normais (que é implementada por get_db_prep_value).

Processando valores antes de salvar

pre_save(self, model_instance, add)

Este método é chamado pouco antes do get_db_prep_save() e deve retornar o valor do atributo apropriado do model_instance para este campo. O nome do atributo está no self.attname (ele é configurado pelo Field). Se o model estiver sendo salvo no banco de dados pela primeira vez, o parâmetro add será True, caso contrário, ele será False.

Você somente precisa sobrescrever este método se desejar processar o valor de alguma forma, pouco antes de armazená-lo. Por exemplo, o DateTimeField do Django usa esse método para configurar o atributo corretamente no caso do auto_now ou auto_now_add.

Se você sovrescrever este método, você deve retornar o valor do atributo no final. Você deve também atualizar o atributo do model se você fizer qualquer alteração no valor, desta forma o código mantem as referências para o model, e ele sempre verá o valor correto.

Preparando valores para o uso em pesquisas no banco de dados

get_db_prep_lookup(self, lookup_type, value)

Prepara o value passando para o banco de dados quando usado numa pesquisa (um constraint WHERE no SQL). O lookup_type será um dos filtros válidos do Django para pesquisa: exact, iexact, contains, icontains, gt, gte, lt, lte, in, startswith, istartswith, endswith, iendswith, range, year, month, day, isnull, search, regex, e iregex.

Se método deve estar preparado para lidar com todos estes valores de lookup_type e deve lançar tanto um ValueError se o value tiver algum tipo de erro (uma lista quando você estava esperando um objeto, por exemplo) ou um TypeError se seu campo não suporta tal tipo de pesquisa. Para muitos campos, você pode começar manipulando os tipos de pesquisa que precisam de tratamento especial para seu campo e passar o resto para o método get_db_prep_lookup() da classe pai.

Se você precisa implementar o get_db_prep_save(), você normalmente precisará implementar get_db_prep_lookup(). Se você não o fizer, get_db_prep_value será chamado pela implementação padrnao, para gerenciar as pesquisas exact, gt, gte, lt, lte, in e range.

Você pode também querer implementar este método para limitar os tipos de pesquisa que podem ser usados com seu tipo de campo.

Note que, para range e in, get_db_prep_lookup receberá uma lista de objetos (presumidamente o tipo certo) e terá de convertê-los em uma lista de coisas do tipo certo para passar para o banco de dados. Na maioria das vezes, você poderá reusar get_db_prep_value(), ou pelo menos um fator comum a algumas partes.

Por exemplo, o seguinte código implementa get_db_prep_lookup para limitar os tipos de pesquisa aceitos para exact e in:

class HandField(models.Field):
    # ...

    def get_db_prep_lookup(self, lookup_type, value):
        # Nós somente manipulamos 'exact' e 'in'. Todos os outros são erros.
        if lookup_type == 'exact':
            return [self.get_db_prep_value(value)]
        elif lookup_type == 'in':
            return [self.get_db_prep_value(v) for v in value]
        else:
            raise TypeError('Lookup type %r not supported.' % lookup_type)

Especificando o campo de formulário para o campo de model

formfield(self, form_class=forms.CharField, **kwargs)

Retorna o campo de formulário padrão para se usar quando este campo é mostrado num model. Este método é chamado pelo helper ModelForm.

Tudo no dicionário kwargs é passado diretamente para o método Field__init__() do campo. Normalmente, tudo que você precisa fazer é configurar um bom padrão para o argumento form_class e então delegar mais manipulação para a classe pai. Isso pode exigir que você escreva um campo de formulário personalizado (ou mesmo um widget de formulário). Veja a documentação de formulários para mais informação sobre isso, e dê uma olhada no código localflavor para ter mais exemplos de widgets personalizados.

Continuand nosso exemplo, nós podemos escrever o método formfield() como:

class HandField(models.Field):
    # ...

    def formfield(self, **kwargs):
        # Esta é uma forma bastante normal para configurar alguns padrões
        # enquanto deixa o chamador sobrescrever eles.
        defaults = {'form_class': MyFormField}
        defaults.update(kwargs)
        return super(HandField, self).formfield(**defaults)

Isso assume que nós imporamos uma classe de campo MyFormField (que tem seu próprio widget padrão). Este documento não cobre os detalhes de se escrever campos de formulário personalizados.

Emulando tipos de campos nativo

get_internal_type(self)

Retorna uma string dando o nome da subclasse de Field que nós estamos emulando a nível de banco de dados. Isso é utilizado para determinar o tipo de coluna de banco de dados para casos simples.

Se você criou um método db_type(), você não precisa se preocupar com o get_internal_type() -- não será muito usado. Algumas vezes, embora, o armazenamento de banco de dados é de um tipo semelhante a algum outro campo, assim você pode usar a lógica de outro campo para criar a coluna certa.

Por exemplo:

class HandField(models.Field):
    # ...

    def get_internal_type(self):
        return 'CharField'

Não importa que backend de banco de dados nós usemos, isso significará que o syncdb e outros comandos SQL criam o tipo certo de coluna para armazenar uma string.

Se get_internal_type() retorna uma string que não é conhecida pelo Django para backend do banco de dados que você está usando -- isto é, ele não aparece em django.db.backends.<db_name>.creation.DATA_TYPES -- a string continuará sendo usada pelo serializer, mas o método padrão db_type() retornará None. Veja a documentação de db_type() por razões do porquê isso pode ser útil. Colocando uma string descritiva como o tipo de campo para o serializer é uma boa idéia se você estiver sempre usando a saída do serializer em algum outro lugar, fora do Django.

Convertendo dados do campo por serialização

value_to_string(self, obj)

Este método é usado pelo serializer para converter o campo numa string de saída. Chamando Field._get_val_from_obj(obj)() é a melhor forma de obter o valor para serializar. Por exemplo, desde que nosso HandField use strings para seu armazenamento de dados de qualquer forma, nós podemos reusar algumas conversões de código existente:

class HandField(models.Field):
    # ...

    def value_to_string(self, obj):
        value = self._get_val_from_obj(obj)
        return self.get_db_prep_value(value)

Alguns conselhos gerais

Escrevendo um campo personalizado pode ser um processo complicado, particularmente se você estiver fazendo uma conversões complexas entre seus tipos Python e seu banco de dados e formatos serializados. Há algumas dicas para tornar as coisas mais suaves:

  1. Procure por campos existentes do Django (no django/db/models/fields/__init__.py) para inspiração. Tente encontrar um campo que seja similar ao que você deseja fazer e estenda-a um pouco, ao invês de criar uma totalmente do zero.
  2. Coloque um método __str__() ou __unicode__() na classe que está envolvendo como um campo. Há um monte de lugares onde comportamento padrão do código do campo é chamar force_unicode() sobre o valor. (Nos nossos exemplos neste documento, value poderia ser uma intância de Hand, não um HandField). Portanto se seu método __unicode__() converte automaticamente para uma string a partir de seu objeto Python, você pode poupar a si mesmo um monte de trabalho.

Escrevendo uma extensão de FileField

Além dos métodos acima, campos que lidam com arquivos possuem alguns poucos requesitos especiais que deve ser tomado nota. A maioria dos mecanismos fornecidos pelo FileField, como controlador de armazenamento de banco de dados e recebimento, podem permenacer imutáveis, deixando a subclasse para lidar com o desafio de suportar um tipo particular de arquivo.

O Django fornece uma classe File, que é usada como um proxy entre o conteúdo do arquivo e as operações. Este pode ser estendido para customizar como o arquivo é acessado, e quais métodos são disponíveis. Ele fica em django.db.models.fields.files, e seu comportamento padrão é explicado na documentação de arquivo.

Uma vez que uma subclasse de File é criada, a nova subclasse de FileField deve ser instruída a usá-la. Para fazer isso, simplesmente atribua a nova subclasse File para o atributo especial attr_class da subclasse FileField.

Umas poucas sugestões

Além dos detalhes acima, há umas poucas linhas guias que podem melhorar muito a eficiência e legibilidade do código dos campos.

  1. O fonte dos próprios campos do Django ImageField (em django/db/models/fields/files.py) é um grande exemplo de como a subclasse FileField pode suportar um tipo de arquivo em particular, e como incorpora todas as técnicas descritas acima.
  2. Cacheie os atributos dos arquivos sempre que possível. Uma vez que arquivos podem ser guardados em sistemas de armazenamento remotos, recebê-los pode custar um tempo extra, ou mesmo dinheiro, o que nem sempre é necessário. Uma vez que o arquivo é recebido para obter algum dado sobre seus conteúdos, cacheie o máximo de dados que for possível para reduzir o número de vezes que o arquivo deve ser recebido em chamadas subsequentes para esta informação.