Hoje a minha intenção é fazer algo um pouco mais técnico. Grande parte dos meus amigos leitores são programadores. Destes, grande parte programam em Python e usam Django para suas aplicações web. Pensando nisso, resolvi compartilhar com vocês o meu guia de boas práticas para escrever models no Django.
Este guia é fruto da minha experiência, minhas impressões pessoais e também de muita pesquisa sobre o tema. O “definitivo” do título é apenas um click bait! 😬 Nada aqui é definitivo, muito pelo contrário. Essas são as minhas impressões e sua colaboração/questionamento será MUITO BEM VINDA!
Sem mais delongas, vamos ao tópicos:
Nomenclaturas
As boas-práticas de nomenclaturas dos models em Django seguem também as boas práticas da PEP8, que são, basicamente:
- Nomes de classes são capitalizadas;
- Nome dos métodos e atributos são em snake_case;
Além disso, os nomes das classes devem ser sempre no singular. Isso se dá pois uma classe model representa um objeto daquele tipo de dado, e não uma coleção.
Resumindo, não faça isso:
Faça isso:
Ordenação dos métodos e atributos
De acordo com a documentação oficial de boas práticas do Django, a ordem indicada para os métodos e atributos de uma classe são:
- Constantes
- Campos da tabela
- Custom Managers
- classe Meta
- sobrescrição do
__str__()
- sobrescrição do
__save__()
- definição do
get_absolute_url()
- Properties customizadas
- Métodos customizados
Parâmetros e funcionalidades do Django
Relações entre Models
Com a possibilidade de criar relações entre models no ORM do Django, temos também a facilidade de integrar essas relações de forma muito simples.
Para toda nova relação criada, o django cria automaticamente um novo método que integra os os models com o sufixo _set
. Veja o exemplo:
Se nós usarmos reporter.article_set
poderemos manipular todos os artigos associados ao repórter em questão. Entretanto, visualmente isso não é muito agradável. Para resolver o problema, usamos o campo related_name
.
Assim, agora podemos fazer:
Além deste parâmetro, temos também o related_query_name
. Basicamente o que ele faz é poder alterar a o nome pelo qual você irá fazer uma busca reversa.
No primeiro caso, para fazermos uma busca reversa faríamos:
Mas se quisermos ser mais específicos em dizer que o repórter é na verdade o autor do artigo, então usamos o related_query_name
:
Mais um ponto legal de frisar nas relações do Django é que sempre que você tiver uma relação única entre dois elementos, você NÃO DEVE fazer isso:
Ao invés de usar o unique=True
, utilize o OneToOneField
:
Blanks e Nulls
É importante saber a diferença entre os atributos blank
e null
.
null
: é o atributo que corresponde a capacidade do campo aceitar valores nulos no banco de dados. Por padrão, todo campo criado no django não aceita valores nulos.blank
: é o atributo responsável por lidar com a entrada deste dado, seja via django-admin ou forms. Você pode ter um campo que aceita valores nulos no banco de dados mas que não aceita uma entrada vazia no seu formulário.
Ainda falando sobre brancos e nulos, vai mais uma dica: Ao usar campos boleanos, não aceite entradas nulas. Para esse caso o django tem um campo específico, que chamado NullBooleanField
.
Não faça isso:
Faça isso:
Choices
Diversos campos dos models Django permitem você utilizar o atributo choices. Para usá-los de forma otimizada, as recomendações são:
- Armazenar strings ao invés de números no banco. (Pode não ser a melhor opção do ponto de vista de performance do banco, porém para fazer filtro vai ser muito mais claro e intuitivo).
- As variáveis que armazenam as opções devem ser declaradas como constantes, no começo da classe.
- Se o campo representar o estado do dado, é importante manter uma ordem cronológica. Veja o exemplo.
Mensagens de ajuda
O Django oferece uma facilidade muito boa que é o help_text
. Basicamente é um parâmetro que você coloca no campo do seu model e toda a vez que você gerar um formulário baseado neste model ele exibirá a mensagem definida como um texto de ajuda.
O código abaixo:
Irá produzir a seguinte tela no Django Admin:
Lidando com valores monetários
Lidar com moedas é um tema polêmico. A grande complicação está no fator de que a informação, sob a ótica de modelagem de dados, é uma tupla composta do valor com duas casas decimais e a moeda em questão (real, dolar, etc).
Existem projetos que ajudam a lidar melhor com isso, como por exemplo o django-money. Acredito que se o projeto envolver varias moedas diferentes, faça sentido utilizar o projeto.
Caso seja um projeto mais simples, a dica é: Sempre utilize DecimalField, e não FloatField.
Consultando dados de forma correta
Use queryset.count()
ao invés de len(queryset)
Quando você utiliza o comando len(queryset)
o Django vai carregar todos os ítens em memória para encontrar o tamanho dessa queryset. Quando você utiliza o queryset.count()
o Django vai delegar essa função ao banco de dados. Quem você acha que é mais rápido?
Traga somente o dado que você precisa
Veja este código abaixo:
Na prática, se tivermos 10 linhas no banco de dados, o Django vai fazer 11 queries, sendo uma para trazer todos os resultados e depois uma nova query para exibir o nome de cada item. Ruim né?
Agora veja este novo trecho:
Pronto. Somente uma requisição ao banco. Bem melhor né?
Utilize o método .exists()
Lembre-se: as queries python são lazy. Se você utilizar if queryset:
para saber se existem elementos na sua query, uma nova consulta no banco de dados será feita. O modo correto de verificar se a query retornou algum elemento é if queryset.exists():
.
Não utilize ObjectDoesNotExist
Quando você faz retorna um elemento do banco de dados através do método .get()
, caso esse elemento não exista, uma excessão ObjectDoesNotExist
vai disparar. Porém a melhor forma de capturar essa excessão não é utilizando esta classe, e sim a classe ModelName.DoesNotExists
. Fazendo dessa forma você isola a excessão somente a aquele model.
Modo não recomendado:
Modo recomendado:
Como testar models?
Este é um tema um pouco mais obscuro. Acredito que aqui exija um pouco de boa prática para o Django em si, um pouco de boa prática de programação, de modo genérico e um pouco de maneirismo do desenvolvedor.
Como gosto de desenvolver utilizando TDD, sempre começo escrevendo o teste. Então, a primeira coisa que testo são os atributos deste model.
Depois de implementar os atributos, eu testo a criação de um elemento:
Por fim, eu testo os métodos customizados implementados, como __str__
e __save__:
Confesso que essa foi a parte que mais tive dúvidas para escrever. Você concorda com esses testes? Discorda? Quero saber sua opinião.
Updates:
- Meu amigo Marcelo Andriolli citou o lance do subtest com um exemplo, e o Ronaldo Oliveira completou muito bem. Valeu pessoal!
- Grande parte dessas dicas estão no livro Two Scoops of Django, como sugeriu nosso amigo Bernardo Gomes.
Dica extra
Conheça o projeto Django Models Utils. Trata-se de uma série de utilidades para facilitar seu trabalho com os models. Conheça em https://github.com/jazzband/django-model-utils
Referências
- https://simpleisbetterthancomplex.com/tips/2018/02/10/django-tip-22-designing-better-models.html
- https://steelkiwi.com/blog/best-practices-working-django-models-python/
- https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/coding-style/
- https://realpython.com/testing-in-django-part-1-best-practices-and-examples/#testing-models
- https://docs.djangoproject.com/en/dev/topics/testing/overview/#assertions
E ai, gostou? Concorda, discorda? Deixa seu comentário e compartilha com a galera.
Abraços