Escrevendo campos personalizados de modelos.

Introdução

A documentação referência de modelo explica como usar as classes de campo padrão do Django –CharField, DateField, etc. Para muitos propósitos, essas classes são tudo o que você precisará. Às vezes, porém, a versão Django não vai atender às suas necessidades específicas, ou você vai querer usar um campo que é totalmente diferente daqueles fornecidos com Django.

Os tipos de campos embutidos no Django não cobrem todas os possíveis tipos de colunas do banco de dados – somente os tipos comuns, tais como VARCHAR``e `ÌNTEGER. Para tipos de colunas mais obscuros, tal como polígonos geográficos ou ainda tipos criados pelo usuário como `PostgreSQL custom Types`__, é possível definir sua própria subclasse de Field do Django.

Por outro lado, pode haver um tipo complexo de objeto no Python que pode de alguma maneira ser serializado para que se encaixe em um tipo de coluna padrão no banco de dados. Este é um outro caso onde uma subclasse de Field pode ajudar a utilizar o objeto com o “models”.

Nosso objeto de exemplo

Criar campos personalizados requer um pouco de atenção. Para que seja mais fácil de acompanhar, usaremos um exemplo consistente através desta documentação: um objeto Python que represente a coleção de cartas de uma mão de Bridge. Não se preocupe, não é necessário saber como jogar Bridge para entender o exemplo. É necessário saber que 52 cartas são distribuídas igualmente para 4 jogadores, os quais são tradicionalmente chamados north (norte), east (leste), south (sul) and west (oeste). Nossa classe se parece com isso:

class Hand(object):
    """A hand of cards (bridge style)"""

    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

    # ... (other possibly useful methods omitted) ...

Esta é somente uma classe Python comum, não tem nada específico do Django. Gostaríamos de ser capazes de fazer coisas como essa no nosso modelo (assumimos que o atributo hand do modelo é 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()

Atribuimos ou lemos o valor do atributo hand no nosso modelo como em qualquer outra classe Python. Este truque é para dizer ao Django como manipular a gravação e a leitura como um objeto.

Para utilizar a classe Hand em nossos modelos, nós não precisamos alterar essa classe em nada. Isso é ideal, porque quer dizer que podemos facilmente escrever modelos de suporte para classes existentes cujo o código fonte não pode ser alterado.

Nota

Você pode estar querendo tirar vantagem dos tipos customizados de colunas de banco de dados e lidar com o dado como um tipo de dado Python padrão nos seus modelos; Este caso é similar ao nosso exemplo Hand e iremos explicitar qualquer diferença enquanto continuamos.

A teoria por trás.

Armazenamento no Banco de Dados.

A maneira mais simples de pensar em um campo de modelo, é que ele fornece uma maneira de converter um objeto Python normal –string, boolean, datetime, ou alguma coisa mais complexa como o Hand– para ou de um formato que seja útil quando interagir com o banco de dados (e serialização, mas, como veremos mais tarde isso parece bastante natural uma vez que você tenha o lado do banco de dados sob controle).

Campos em um modelo devem ser de alguma maneira convertidos para que se encaixem em um tipo de coluna de banco de dados já existente. Bancos de dados diferentes provêem diferentes tipos de colunas, mas a regra ainda é a mesma: aqueles são os únicos dados que se tem para trabalhar. Qualquer coisa que se queira guardar em um banco de dados deve se encaixar em um daqueles tipos.

Normalmente, ou você está escrevendo um campo Django para lidar com um tipo de coluna de banco de dados em particular, ou existe uma maneira bastante simples de converter seu dado para, por exemplo, uma string.

Para o nosso exemplo Hand`, podemos converter o dado carta para uma string de 104 caracteres concatenando todos as cartas juntas em uma ordem pré-determinada --por exemplo, primeiro todos os *north*, depois os *east*, *south* e *west*. Então os objetos ``Hand podem ser salvos em colunas do tipo texto ou caracter em um banco de dados.

O que uma classe campo faz?

Todos os campos do Django (e quando dizemos campo neste documento, sempre queremos dizer campos do modelo e não campos de forms) são subclasses de django.db.models.Field. A maioria das informações que o Django registra sobre um campo é comum a todos os campos –name, help text, uniqueness e por diante. Armazenar toda essa informação é papel de Field. Iremos entrar em detalhes precisos do que o Field pode fazer mais tarde; por agora, basta dizer que tudo descende de Field e então customizamos partes chaves do comportamento da classe.

É importante dizer que a classe de campo do Django não é o que é armazenado no seus atributos do modelo. Os atributos dos modelos contém objetos Python normais. As classes de campo que são definidas em um modelo são na verdade armazenadas dentro da classe Meta quando a classe é criada (os detalhes precisos de como isso é feito são importantes aqui). Isso é porque as classes de campos não são necessárias enquanto se está modificando atributos. Na verdade, eles fornecem o maquinário para conversão entre o valor do atributo e o que é armazenado no banco de dados ou enviado para o serializador.

Tenha isso em mente ao criar seus próprios campos. A subclasse Field do Django que você escreve provê o maquinário para conversão entre suas instancias Python e os valores do banco de dados/serializador de várias maneiras (existe uma diferença entre armazenar um valor e usar um valor para lookups por exemplo). Se isso lhe soa um pouco confuso, não se preocupe – se tornará claro no exemplo abaixo. Mas lembre que muitas vezes acabará por criar duas classes quando quiser um campo personalizado:

  • A primeira classe é o objeto Python que os usuários irão manipular. Irão assinalar isso a um atributo de model, lerão para mostrar, coisas assim. Esta é a classe Hand em nosso exemplo.

  • A segunda classe é a subclasse Field. Essa é a classe que sabe como converter sua classe entre sua forma de armazenamento permanente e a forma do Python e vice-versa.

Escrevendo uma subclasse de campo

Quando estiver panejando sua subclasse de Field, primeiro de uma com qual classe já existente Field seu campo se parece mais. É possível fazer uma subclasse de campo Django já existente e economizar algum trabalho ? Caso não, você deve construir uma subclasse de Field, da qual tudo descende.

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

No nosso exemplo, iremos chamar nosso campo de HandField. (é uma boa idéia chamar sua subclasse Field <AgumaCoisa>Field, então ela fica facilmente identificável como uma subclasse de Field) . Nosso campo não se comporta como nenhum outro campo existente, então iremos herdar diretamente de Field:

from django.db import models

class HandField(models.Field):

    description = "A hand of cards (bridge style)"

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

Nosso HandField aceita a maioria das opções dos campos padrão (veja a lista abaixo), mas asseguramos que ele tenha um comprimento fixo, já que só precisa de 52 valores de carta mais seus naipes; 104 caracteres no total.

Nota

Muitos dos modelos do Django aceitam opções que estes nunca usam pra nada. Por exemplo, é possível passar ambos os editable e auto_now para um django.db.models.DateField e o parâmetro editable será ignorado (auto_now sendo definido implica em editable=False). Nenhum erro é gerado neste caso.

Este comportamento simplifica as classes de campos, porque não precisam checar opções que não são necessárias. Apenas passam todas as opções para a classe mãe e estes não são usados mais tarde. Você decide se quer que seus campos sejam mais rigorosos sobre as opções que eles selecionam, ou usar o mais simples, um comportamento mais permissivo dos campos existentes.

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

Todas as opções sem uma explicação na lista acima tem o mesmo significado que os campos normais do Django. veja a field documentation para exemplos e detalhes.

A descontrução do Campo

O contraponto de escrever seu método __init__()``é escrever o método ``deconstruct(). Este método diz ao Django como pegar uma instância do seu novo campo e reduzi-la para uma forma “serializada” em particular, e quais argumentos passar para __init__() para recriá-lo.

Se não foram adicionadas opções extras ao campo que foi herdado, então não há necessidade de reescrever o método deconstruct(). Por outro lado, se você mudar os argumentos passados no __init__() (como em HandField), será preciso complementar os argumentos sendo passados.

O contrato da deconstruct()``é simples; ele retorna uma tupla de quatro item: o atributo "name" do campo, o path completo  de importação da classe do campo, os argumentos posicionais (como lista), e os argumentos nomeados (como dicionários).  Note que é diferente do método ``deconstruct() para classes customizadas o qual retorna uma tupla de 3 elementos

Como autor do campo customizado, não é necessário se preocupar sobre os dois primeiros valores; a classe Field básica tem todo o código para o atributo “name” e “import path” funcionar. Entretanto, devemos nos preocupar com o argumentos posicionais e os nomeados, já que estes são as alterações que fizemos

Por exemplo, na nossa classe “HandField” sempre somos forçados a definir max_length em ” __init __ () ”. O método “deconstruct()” da classe base “Field” ierá tentar retornar os argumentos da palavra-chave; assim, podemos lançar a partir dos argumentos palavras-chaves para facilitar a leitura

from django.db import models

class HandField(models.Field):

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

    def deconstruct(self):
        name, path, args, kwargs = super(HandField, self).deconstruct()
        del kwargs["max_length"]
        return name, path, args, kwargs

Se você adicionar um novo argumento palavra-chave, você mesmo precisa codificar e colocar o valor dentro da variável “kwargs”.

from django.db import models

class CommaSepField(models.Field):
    "Implements comma-separated storage of lists"

    def __init__(self, separator=",", *args, **kwargs):
        self.separator = separator
        super(CommaSepField, self).__init__(*args, **kwargs)

    def deconstruct(self):
        name, path, args, kwargs = super(CommaSepField, self).deconstruct()
        # Only include kwarg if it's not the default
        if self.separator != ",":
            kwargs['separator'] = self.separator
        return name, path, args, kwargs

Exemplos mais complexos estão além do escopo deste documento, mas lembre - para qualquer configuração da sua instância de Field, o deconstruct() tem que retornar argumentos que se possa passar para o __init__ para reconstruir aquele estado.

Preste atenção extra se foram definidos novos valores padrão para argumentos na superclasse do Field, tem que ter certeza de que estes são sempre incluídos, ao invés de desaparecer se recebem o valor antigo.

E mais, tente evitar retornar valores como argumentos posicionais; onde for possível, retorne valores como argumentos “nomeados” para compatibilidade máxima no futuro. Claro, se os nomes são trocados mais frequentemente que as posições na lista do construtor, talvez seja preferível o posicional, mas tenha em mente que as pessoas estarão reconstruindo seu campo a partir da verão serializada por algum tempo (talvez anos), dependendo de quanto tempo suas migrações sobrevivam.

É possível ver os resultados de desconstrução olhando as migrações que incluem o campo, e é possível testar a desconstrução com “unit tests” somente desconstruindo e reconstruindo o campo:

name, path, args, kwargs = my_field_instance.deconstruct()
new_instance = MyField(*args, **kwargs)
self.assertEqual(my_field_instance.some_attribute, new_instance.some_attribute)

Changing a custom field’s base class

You can’t change the base class of a custom field because Django won’t detect the change and make a migration for it. For example, if you start with:

class CustomCharField(models.CharField):
    ...

and then decide that you want to use TextField instead, you can’t change the subclass like this:

class CustomCharField(models.TextField):
    ...

Instead, you must create a new custom field class and update your models to reference it:

class CustomCharField(models.CharField):
    ...

class CustomTextField(models.TextField):
    ...

As discussed in removing fields, you must retain the original CustomCharField class as long as you have migrations that reference it.

Documentando o seu campo

Como sempre, é devido documentar o tipo do campo, então os usuários irão saber o que é. Além disso prover uma “docstring” para ele, que é útil para desenvolvedores, é possível permitir que usuários da aplicação admin vejam uma pequena descrição do tipo do campo através do django.contrib.admindocs . Para tal simplesmente forneça um texto descritivo no atributo description da classe do seu campo personalizado. NO evento acima, a descrição mostrada pela aplicação admindocs para o HandField será ‘A hand of cards (bridge style)’.

No display do django.contrib.admindocs, a descrição do campo é interpolada com field.__dict__ o qual habilita a descrição incorporar argumentos do campo. Por exemplo, a descrição para CharField é:

description = _("String (up to %(max_length)s)")

Métodos úteis

Uma vez criada a subclasse de Field, você deve considerar sobrescrever um poucos métodos, dependendo do comportamento do seu campo. A lista de métodos abaixo está mais ou menos em uma ordem decrescente de importância, então comece do topo.

Tipos de banco de dados personalizados

Vamos dizer que você tenha criado um tipo personalizado para o PostgreSQL chamado mytype. Você pode herdar de Field e implementar o método db_type(), como a:

from django.db import models

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

Uma vez que tenha MyTypeField, pode usá-lo em qualquer modelo, tal como qualquer outro tipo Field:

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

Se o seu objetivo é criar uma aplicação agnóstica quanto ao banco de dados, deve levar em conta as diferenças nos tipos de colunas no banco de dados. Por exemplo, a tipo date/time para o PostgreSQL é chamado``timestamp`` enquanto que para o MySQL é chamado datetime. O jeito mais simples de lidar com isso é no método db_type() verificar o atributo connection.settings_dict['ENGINE'].

Por exemplo:

class MyDateField(models.Field):
    def db_type(self, connection):
        if connection.settings_dict['ENGINE'] == 'django.db.backends.mysql':
            return 'datetime'
        else:
            return 'timestamp'

O método db_type() é chamado pelo Django quando o framework constrói o comando CREATE TABLE para sua aplicação – que é, quando as tabelas são criadas pela primeira vez. É chamado também quando está construindo a cláusula WHERE – quer dizer, quando dados são recebidos usando métodos de QuerySet como get(), filter(), e exclude() e tem o campo do modelo como argumento. O método não é chamado em nenhum outro momento, isso permite que se execute códigos relativamente complexos, tal como na verificação em connection.settings_dict no código acima.

Alguns tipos de coluna do banco de dados aceitam parâmetros, como CHAR(25), onde o parâmetro 25 representa o tamanho máximo da coluna. Em casos como este, é mais flexível se o parâmetro for especificado no modelo ao invés de ser hard-coded no método db_type(). Por exemplo, não faria muito sentido ter CharMaxlength25Field, mostrado aqui:

# This is a silly example of hard-coded parameters.
class CharMaxlength25Field(models.Field):
    def db_type(self, connection):
        return 'char(25)'

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

O melhor jeito de fazer isso seria fazer o parâmetro ser especificado em tempo de execução – isto é, quando a classe é instanciada. Para fazer isso, basta implementar Field.__init__(), como em:

# This is a much more flexible example.
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, connection):
        return 'char(%s)' % self.max_length

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

Finalmente, se sua coluna requer que defina um SQL realmente complexo, retorne None do db_type(). Isso irá fazer que o código de criação de SQL do Django ignore este campo. Então você é o responsável por criar a coluna na tabela correta de alguma outra maneira, claro, isso é uma maneira de dizer ao Django para sair do caminho.

Convertendo valores para objetos Python

Changed in Django 1.8:

Historicamente, o Django fornece uma metaclassa chamada SubfieldBase que sempre chama to_python() na atribuição. Isso não funciona bem com transformação, agregções, ou queries de valores customizadaos, então teve que ser trocado com o from_db_value().

Se sua classe customizada de Field lida com estruturas de dados mais complexas que strings, datas, inteiros, or floats, então talvez precise sobrescrever o from_db_value() e to_python().

Se presente para a subclasse do campo, from_db_value() será chamado em todas as circunstâncias quando dados são lidos do banco de dados, incluindo em agregacões e chamadas do values() .

o to_python() é chamado pela deserialização e durante o método clean() usado nos forms.

Como regra geral, ``to_python()``deve lidar tranquilamente com qualquer dos seguintes argumentos:

  • Uma instância do tipo correto (ex., ``Hand``no nosso exemplo corrente).

  • Uma string

  • None``(se o campo permitir ``null=True)

Na nossa classe HandField, estamos armazendo os dados como campo VARCHAR no banco de dados, então temos que ser capazes de processar strings e None no from_db_value(). No to_python(), precisamos também lidar com instâncias de Hand:

import re

from django.core.exceptions import ValidationError
from django.db import models
from django.utils.translation import ugettext_lazy as _

def parse_hand(hand_string):
    """Takes a string of cards and splits into a full hand."""
    p1 = re.compile('.{26}')
    p2 = re.compile('..')
    args = [p2.findall(x) for x in p1.findall(hand_string)]
    if len(args) != 4:
        raise ValidationError(_("Invalid input for a Hand instance"))
    return Hand(*args)

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

    def from_db_value(self, value, expression, connection, context):
        if value is None:
            return value
        return parse_hand(value)

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

        if value is None:
            return value

        return parse_hand(value)

Perceba que sempre retornamos uma instância destes métodos. Este é o tipo do objeto Python que queremos armazenar no atributo do modelo.

Para to_python(), se qualquer coisa der errado durante a conversão do valor, deve-se gerar a exceção ValidationError.

Convertendo objetos Python para valores em queries

Uma vez que usar um banco de dados requer conversão em ambos os sentidos, se sobrescrever o to_python() é necessário sobrescrever também get_prep_value() para converter os objetos Python de novo em valores de query.

Por exemplo:

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

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

Aviso

Caso seu campo personalizado use os tipos CHAR, VARCHAR or TEXT parao MySQL, assegure que o get_prep_value() sempre retorne um tipo string. O MySQL responde a pesquisa de maneira variável e inesperada quando uma query é executada com estes tipos e o valor fornecido é um inteiro, o que pode resultar em queries qeu contenham objetos inesperados nos seus resultados. Este problema não pode ocorrer se sempre retornar um tipo string do get_prep_value().

Convertendo valores de queries para valores de banco de dados

Alguns tipos de dados (por exemplo datas) precisam estar em um formato específico antes deles poderem ser usados por um “backend” de banco de dados. O get_db_prep_value() é um método onde estas conversões devem ser feitas. A conexão específica que será usada para a query é passada pelo parâmetro connection. Isso lhe possibilita usar a lógica de conversão espeçifica para um backend se isso for necessário.

Por exemplo, o Django usa o seguinte método para isso BinaryField:

def get_db_prep_value(self, value, connection, prepared=False):
    value = super(BinaryField, self).get_db_prep_value(value, connection, prepared)
    if value is not None:
        return connection.Database.Binary(value)
    return value

No caso do seu campo personalizado precisar de uma conversão especial quando for salvo e isso não é o mesmo que a conversão usada para parâmetros de query, você pode sobrescrever get_db_prep_save().

Processando valores antes de salvar

Caso queira processar o valor logo antes de salvar, use pre_save(). Por exemplo, o DateTimeField do Django usa este método para atribuir o atributo correto no caso de auto_now ou auto_now_add.

Caso reescreva este método, é devido retornar o valor do atributo no fim. Deve-se também atualizar o atributo do modelo se houve mudanças no valor para que o código que mantenha referencias ao modelo sempre veja o valor correto.

Preparando valores para uso em filtros de banco de dados

Assim como conversões de valores, preparar o valor para um filtro de banco de dados é um processo de duas fazes

get_prep_lookup() executa a primeira fase da preparação da pesquisa: conversão de tipo e validação de dados

Prepare o valor para passá-lo para o banco de daods quando usado em uma pesquisa (uma cláusula WHERE no SQL). O parâmetro lookup_type será um dos filtros válidos do Django sendo filtros: exact, iexact, contains, icontains, gt, gte, lt, lte, in, startswith, istartswith, endswith, iendswith, range, year, month, day, isnull, search, regex, and iregex.

Se você estiver usando filtros personlizados, o lookup_type pode ser qualquer lookup_name usado pelos filtros personalizados do projeto .

Seu método deve estar preparado para lidar com todos estes valores de lookup_type e deve emitir um ValueError se o value é do tipo errado (uma lista quando deveria ser um objeto, por exemplo) ou um TypeError se o seu campo não aceita aquele tipo de filtro. Para muitos campos, você pode começar por tratar os tipos de filtros que recisam de uma tratamento especil para seu campo e passar o resto para o método get_db_prep_lookup() da classe pai.

se você precisou implementar o get_db_prep_save(), normalmente você precisará implementar o get_prep_lookup(). Se não, get_prep_value() será chamado pela implementação padrão, para lidar com os filtros exact, gt, gte, lt, lte, in and range.

Talvez você queira implementar este método para limitar os tipos de filtros que possam ser usados com seu campo personalizado.

Note que, para os filtros range``e `ìn, o get_grep_lookup receberá uma lista de objetos (presumidamente do tipo certo) e será preciso converte-los em uma lista de strings do tipo correto para passar para o banco de dados. Na maior parte do tempo, é possível reutilizar get_prep_value(), ou no mínimo fatorar algumas partes comuns.

Por exemplo, o código seguinte implementa o get_prep_lookup para limitar os tipos de filtros aceitos para exact e ìn:

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

    def get_prep_lookup(self, lookup_type, value):
        # We only handle 'exact' and 'in'. All others are errors.
        if lookup_type == 'exact':
            return self.get_prep_value(value)
        elif lookup_type == 'in':
            return [self.get_prep_value(v) for v in value]
        else:
            raise TypeError('Lookup type %r not supported.' % lookup_type)

Para tratar conversões de dados específicos de um banco de dado requeridos por um filtro, reescreva o get_db_prep_lookup().

Especificando o campo de um formulário para o campo de um modelo.

Para personalizar o campo de formulário usado pelo ModelForm, você pode reescrever o formfield().

A classe do campo de formulário pode ser especificada via o form_class e os argumentos de choices_form_class; o último é especificado se o campo tiver opções (choices) especificadas, do contrário o primeiro. Se estes argumentos não são fornecidos, será usada a CharField ou TypedChoiceField.

Todos os dicionários kwargs é passado diretamente para o método __init__()` do campo do formulário. Normalmente, tudo o que você precisa fazer é definir um bom padrão para o argumento do form_class (e talvez choices_form_class) e delegar futuras manipulações para a classe pai. Isso talvez requeira que seja escrito um campo de form personalizado (e mesmo o widget de formulário). Veja o forms documentation para informações sobre isso.

Continuando nosso exemplo em andamento, podemos escrever o método formfield() como:

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

    def formfield(self, **kwargs):
        # This is a fairly standard way to set up some defaults
        # while letting the caller override them.
        defaults = {'form_class': MyFormField}
        defaults.update(kwargs)
        return super(HandField, self).formfield(**defaults)

Isso assume que importamos uma classe de campo MyModelField (o qual tem seu próprio widget padrão). Este documento não cobre os detalhes de escrever um campo de formulário personalizado.

Emulando tipos de campos internos.

Se você criou um método db_type(), não precisa se preocupar sobre get_internal_type() – isso não será usado muito. As vezes, porém, seu armazenamento no banco de dados é similar em tipo a outro campo, então você pode usar a lógica deste outro campo para criar a coluna correta.

Por exemplo:

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

    def get_internal_type(self):
        return 'CharField'

Não importa qual backend de banco de dados que esteja sendo usado, isso quer dizer que o migrate e outros comandos SQL criam o tipo de coluna correto para armazenar uma string.

Se o get_internal_type() retorna uma string que não é conhecida para o Django para o backend de banco de dados que está sendo usado – que é, ele não aparece no django.db.backends.<db_name>.base.DatabaseWrapper.data_types – a string ainda será usada pelo serializador, mas o método padrão db_type() retornará None. Veja a documentação do db_type() pelas razões pelas quais este talvez seja útil.

Convertendo dado do campo para serialização

Para customizar a maneira como os valores são serializados por um serializador, sobrescreva o value_to_string(). Usando o value_from_object() ;e o melhor jeito de pegar os valores dos campos antes da serialização. Por exemplo, já que de qualqer forma a HandField usa strings para armazenar os dados, podemos usar alguns códigos de conversão existentes:

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

    def value_to_string(self, obj):
        value = self.value_from_object(obj)
        return self.get_prep_value(value)

Alguns avisos gerais

Escrever um um campo personalizado pode ser um um pouco complicado, particularmente se estiver fazendo conversões complexas entre tipos Python e o banco de dados e formatos de serialização. Aqui algumas dicas par fazer as coisas mais tranquilas.

  1. Olhe para os campos existentes no Django (em django/db/models/fields/__init__.py) para inspação. Tente encontrar um campo que seja similar ao o que você quer extender um pouco, ao invés de criar um inteiramente novo desde o início.

  2. Coloque um método __str__() (__unicode__() em Python 2) na classe que você está construindo como um campo. Há vários lugares onde o comportamento padrão do código do campo é chamar force_text() para o valor. (Em nossos exemplos no nosso documento, value pode ser uma instância de Hand, e não um HandField). Então se o seu método __str__() (__unicode__() on Python 2) automaticamente converter para o formato string do seu objeto Python, você pode economizar muito trabalho.

Escrevendo uma subclasse FileField

Além dos métodos acima, campos que lidam com arquivos tem alguns outros requerimentos especiais que devem ser levados em conta. A maioria dos mecanismos fornecidos pelo ``FileField`, como controlar o armazenamento e leitura na base de dados, podem permanecer inalterados, deixando as subclasses lidarem com o desafio de dar suporte a um tipo de arquivo particular.

O Django fornece uma classe File, a qual é usada como um proxy para conteúdos e operações de arquivos . Isso pode ser herdado para customizar como o arquivo é acessado, e quais métodos estão disponíveis. Isso está em django.db.models.fields.files, e seu comportamento padrão é explicado no arquivo documentação.

Uma vez que uma subclasse de File é criada, a nova subclasse FileField deve ser informada para usá-lo. Para isso, simplesmente passe a nova subclasse de File para o atributo especial attr_class da subclasse de FileField.

Algumas sugestões

Além dos detalhes acima, tem algumas orientações as quais podem melhorar muito a eficiência e a legibilidade do código do campo.

  1. O código fonte para o ImageField do Django (em django/db/models/fields/files.py) é um bom exemplo de como herdar FileField para dar suporte ao um tipo de arquivo em particular, já que ele incorpora todas as técnicas descritas acima.

  2. Faça o cache dos atributos do arquivo sempre que possível. Uma vez que é possível que arquivos sejam armazenados em sistemas de armazenamento remotos, recuperá-los talvez requeira tempo extra, ou mesmo dinheiro, que não é sempre necessário. Uma vez que o arquivo é recuperado para obter alguns dados sobre seu conteúdo, faça o cache de dados o quanto possível para reduzir o número de vezes que o arquivo deve ser recuperado em chamadas subsequentes para aquela informação.

Back to Top