Como funciona o ORM do Django
Publicado em: . | Por: Gileno Filho | Arquivado em: tutoriais
Uma das coisas mais interessantes do framework Django é sem dúvidas o seu ORM. E o que o torna interessante é a sua simplicidade e objetividade quando se utiliza os Lookups para realizar consultas simples e até as complexas que envolvem join's.
Neste artigo irei explorar algumas coisas básicas para quem está iniciando mas também irei mostrar casos de uso mais avançados como uso de funções próprias do banco de dados. Para demonstrar as consultas irei utilizar uma modelagem simples para uma aplicação de atividades realizadas, os modelos estão abaixo:
from django.db import models
class User(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField()
age = models.IntegerField()
class Meta:
db_table = 'users'
def __str__(self):
return self.name
class Activity(models.Model):
user = models.ForeignKey(User)
title = models.CharField(max_length=100)
date = models.DateField()
start_time = models.TimeField()
end_time = models.TimeField()
special = models.BooleanField(default=False)
score = models.IntegerField(default=0)
class Meta:
db_table = 'activities'
def __str__(self):
return '[{}] {}'.format(self.user, self.title)
Dados de teste:
id | name | age | |
---|---|---|---|
1 | Gileno Filho | contato@gilenofilho.com.br | 28 |
2 | Admin | admin@admin.com | 30 |
3 | Fulano | fulano@gilenofilho.com.br | 18 |
id | user | title | date | start_time | end_time | special | score |
---|---|---|---|---|---|---|---|
1 | Gileno Filho | escrever | 10/07/2016 | 10:00 | 13:00 | True | 4 |
2 | Gileno Filho | gravar | 11/07/2016 | 12:00 | 17:00 | True | 3 |
3 | Admin | gerenciar | 12/07/2016 | 10:00 | 12:00 | False | 2 |
4 | Fulano | nadar | 12/07/2016 | 15:00 | 17:00 | False | 1 |
5 | Fulano | passear | 15/07/2016 | 10:00 | 11:00 | True | 5 |
Para acessar qualquer objeto salvo no banco de dados é preciso acessar primeiro o Manager
do Model
, isto é, o atributo objects
presente em todo Model
. Esse Manager
tem diversos métodos que no final de contas vão chamar um objeto do tipo QuerySet
, este é que fica responsável pelas consultas de fato. Eu vou separar a explicação das consultas por partes:
- Consultas Simples
- Consultas Relacionamento
- Aggregate e Annotate
- Consultas Manuais
- Funções do Banco de Dados
Consultas Simples
O principal método da QuerySet
é o filter
, o Manager
tem um atalho para ele com o mesmo nome. Com esse método você irá realizar as consultas passando as condições da seguinte forma:
>>> User.objects.filter(NOMEDOCAMPO__LOOKUP)
Os dois underlines/underscores servem para indicar ao Django qual o nome do campo e qual o lookup será utilizado, pois o django vai receber esse parâmetro de forma dinâmica, também conhecido como parâmetros **kwargs
de uma função. Abaixo seguem alguns exemplos de uso com os modelos definidos anteriormente:
Com o lookup icontains
eu posso filtrar campos de texto procurando por parte da palavra, no caso qualquer usuário que tenha a palavra gileno
no nome.
>>> User.objects.filter(name__icontains='gileno')
[<User: Gileno Filho>]
O icontains
é apenas um dos lookups disponíveis, para ver todos os lookups possívels acesse: https://docs.djangoproject.com/en/1.9/ref/models/querysets/#field-lookups
No entanto é comum realizar consultas que verificam mais de um campo, para condições do tipo AND é possível utilizar o filter
normalmente apenas indicando as condições com vírgula, isto é, passando vários parâmetros de uma vez ou realizando a consulta encadeada:
>>> import datetime as dt
>>> start_time = dt.time(10)
>>> Activity.objects.filter(start_time=start_time, title='gerenciar')
[<Activity: [Admin] gerenciar>]
>>> Activity.objects.filter(start_time=start_time).filter(title='gerenciar')
[<Activity: [Admin] gerenciar>]
Para consultas do tipo OR é necessário chamar o objeto Q
, ele é o responsável por transcrever a consulta que será realizada. Quando utilizamos o filter
ele é chamado de forma implicíta, para utilizar de forma explicíta basta passá-lo como parâmetro posicional no filter
:
>>> from django.db.models import Q
>>> User.objects.filter(Q(email__icontains='@gilenofilho.com.br'))
[<User: Gileno Filho>, <User: Fulano>]
>>> Activity.objects.filter(Q(title='nadar') | Q(title='gerenciar'))
[<Activity: [Admin] gerenciar>, <Activity: [Fulano] nadar>]
A barra vertical entre as duas chamadas de Q
serve para criar o OR entre as condições, isto funciona porque o objeto Q
implementa o método mágico __or__
. Essa é a grande sacada das melhores libs e frameworks Python, utilizar-se dos métodos mágicos para sobreescrever operadores e açucares sintáticos previstos na definição de Python.
É possível combinar OR e AND até que a consulta deseja seja gerada, para usar o AND basta utilizar o &
ao invés da barra vertical.
>>> from django.db.models import Q
>>> user_fulano = User.objects.get(pk=3)
>>> Activity.objects.filter(Q(user=user_fulano) & (Q(special=True) | Q(title='nadar')))
[<Activity: [Fulano] nadar>, <Activity: [Fulano] passear>]
Alguns lookups são específicos para determinados tipos de dados, como o range
que funciona com campos de data e verifica se uma determinada data está entre 2 datas especificadas:
>>> import datetime as dt
>>> start_date = dt.date(2016, 7, 10)
>>> end_date = dt.date(2016, 7, 11)
>>> Activity.objects.filter(date__range=(start_date, end_date))
[<Activity: [Gileno Filho] escrever>, <Activity: [Gileno Filho] gravar>]
Consultas Relacionamento
O objeto Q
além de entender diversos lookups, ele também entende quando o atributo a ser utilizado na condição é uma ForeignKey e deseja-se aplicar a condição sobre um valor presente no objeto relacionado. Para isto utiliza-se novamente o dois underlines/underscores, assim ele irá realizar o join necessário para a consulta encadeada:
>>> Activity.objects.filter(user__email='admin@admin.com')
[<Activity: [Admin] gerenciar>]
>>> Activity.objects.filter(user__email__icontains='@gilenofilho.com.br')
[<Activity: [Gileno Filho] escrever>, <Activity: [Gileno Filho] gravar>, <Activity: [Fulano] nadar>, <Activity: [Fulano] passear>]
Em todo relacionamento o Django cria o acesso inverso, isto é, se o model Activity
tem um atributo chamado user
que faz o relacionamento com a classe User
, o User
terá um atributo criado automaticamente com o padrão:
nome_da_classe_set
Assim é possível acessar os objetos da classe Activity
através de uma instância de User
:
>>> gileno = User.objects.get(pk=1)
>>> gileno.activity_set.all()
[<Activity: [Gileno Filho] escrever>, <Activity: [Gileno Filho] gravar>]
Esse atributo é uma QuerySet
previamente filtrada e que pode ser aplicado outras condições como qualquer QuerySet
.
As consultas que envolvem relacionamentos também permitem realizar o fluxo inverso, isto é, filtrar a classe filha para depois consultar a classe mãe. Isto é bastante útil quando você deseja obter objetos que tem algum tipo de pré-requisito relacionado aos seus filhos:
>>> activities = Activity.objects.filter(special=True).values_list('user')
>>> activities
[(1,), (1,), (3,)]
>>> User.objects.filter(pk__in=activities)
[<User: Gileno Filho>, <User: Fulano>]
O objeto activities
é um tipo especial de QuerySet
que armazena apenas o valor dos campos indicados na chamada do values_list
.
Aggregate e Annotate
Como o nome já diz, o Aggregate irá agregar o resultado de uma consulta em apenas uma linha, normalmente apenas um valor. Na prática se utiliza quando deseja-se somar, verificar o máximo e mínimo, contar e outras operações semelhantes:
>>> from django.db.models import Max, Min, Sum
>>> Activity.objects.aggregate(Max('date'), Min('date'))
{'date__max': datetime.date(2016, 7, 15), 'date__min': datetime.date(2016, 7, 10)}
>>> User.objects.aggregate(total_age=Sum('age'))
{'total_age': 76}
O retorno do aggregate
é um dicionário, e caso a chamada for com parâmetros não nomeados ele gera a chave para cada valor de acordo com o nome do atributo e o tipo do Aggregate.
Já o Annotate serve para adicionar informações no objeto que está sendo consultado, como no exemplo abaixo:
>>> from django.db.models import Count
>>> users = User.objects.annotate(Count('activity'))
>>> for u in users:
>>> print(u.activity__count)
>>>
2
1
2
Isso funciona porque o model User
sabe que tem um relacionamento com o model Activity
, você consegue ver isso de forma dinâmica acessando a meta informação sobre a classe:
>>> User._meta.get_fields()
(<ManyToOneRel: core.activity>,
<django.db.models.fields.AutoField: id>,
<django.db.models.fields.CharField: name>,
<django.db.models.fields.EmailField: email>,
<django.db.models.fields.IntegerField: age>)
O annotate é bastante poderoso, por exemplo se eu quisesse as últimas atividades cadastradas do usuário, isto é, as atividades com maior ID para cada usuario. Primeiro eu precisaria fazer um select das atividades agrupadas por usuário para em seguida pegar o ID máximo. Com annotate eu posso fazer assim:
>>> from django.db.models import Max
>>> pks = Activity.objects.values('user').annotate(max_pk=Max('pk'))
>>> pks
[{'user': 1, 'max_pk': 2}, {'user': 2, 'max_pk': 3}, {'user': 3, 'max_pk': 5}]
>>> Activity.objects.filter(pk__in=pks.values('max_pk'))
[<Activity: [Gileno Filho] gravar>, <Activity: [Admin] gerenciar>, <Activity: [Fulano] passear>]
Com o aggregate e annotate eu também posso fazer o group by em Django:
>>> from django.db.models import Avg, Sum
>>> users = Activity.objects.values('user__name')
>>> users.annotate(total_score=Sum('score'), avg_score=Avg('score'))
[{'total_score': 2, 'user__name': 'Admin', 'avg_score': 2.0}, {'total_score': 6, 'user__name': 'Fulano', 'avg_score': 3.0}, {'total_score': 7, 'user__name': 'Gileno Filho', 'avg_score': 3.5}]
Consultas Manuais
Mesmo com tantas possibilidades que o ORM do Django nos fornece, algumas vezes precisamos fazer uma consulta mais "manual". Podemos realizar consultas utilizando o método extra
da seguinda forma:
>>> sql = "users.id in (select activities.user_id from activities where activities.special=%s)"
>>> User.objects.extra(where=[sql], params=[True])
[<User: Gileno Filho>, <User: Fulano>]
No caso acima não era realmente necessário escrever o SQL da clásula where manualmente, mas eu o fiz para demonstrar que se não for possível realizar alguma consulta com os métodos descritos anteriormente, você pode utilizar o método extra
e escrever a consulta de forma manual. Lembrando que no caso é preciso saber o nome da tabela e se houver parâmetros devem ser passados como no exemplo para que o Django faça o escape para evitar SQL Injection.
O Django também permite se realizar uma consulta raw, isto é, você faz toda a consulta SQL e só vai indicar o modelo que irá representar o resultado dessa consulta, isso é possível utilizando o método raw
.
>>> users = User.objects.raw('select name, email, age from users')
>>> for user in users:
print(user)
Gileno Filho
Admin
Fulano
O uso do raw
é interessante quando existe alguma consulta mais complexa em que os valores do campos virão de outras tabelas ou de algum cálculo de alguma função no banco de dados, assim basta que o resultado da consulta tenha os campos que o model tenha.
Funções do Banco de Dados
Em geral, os bancos de dados relacionais, têm uma série de funções que auxiliam a criação de scripts SQL. Se você deseja utilizar essas funções para fazer cálculos diretamente no seu SGBD, o Django permite da seguinte forma:
from django.db import models
class TimeDiff(models.Func):
function = 'timediff'
output_field = models.TimeField()
O parâmetro function
é o nome da função no banco de dados, nesse exemplo esto considerando um banco de dados MySQL onde existe essa função. O parâmetro output_field
indica que tipo de Field
será retornado.
>>> activities = Activity.objects.annotate(duration=TimeDiff('end_time', 'start_time'))
>>> for activity in activities:
print(activity.duration)
03:00:00
05:00:00
02:00:00
02:00:00
01:00:00
Ainda existe mais coisas a se explorar no ORM do Django mas eu vou parar por aqui, espero que esse artigo lhe ajude a compreender mais as opções que o ORM do Django disponibiliza. Se tiver outras dicas ou críticas comenta ai :)

Quer aprender Django desenvolvendo um projeto real desde a concepção até o deploy seguindo as melhores práticas do mercado?
Com o conhecimento adquirido neste curso será possível tirar seus projetos do papel, criando incríveis aplicações web com Python/Django além de utilizar as melhores práticas do mercado, pois iremos desenvolver uma aplicação real chamada Django E-Commerce - uma plataforma de comércio eletrônico. Vamos passar por diversos problemas encontrados no desenvolvimento web, desde a concepção do projeto ao deploy num servidor real.