No Django os campos do tipo ForeignKey são preenchidos por dados que vem de uma QuerySet, até ai nenhuma novidade, mas você sabia que as QuerySets são implementadas utilizando o recurso de avaliação preguiçosa?
Mas o que é avaliação preguiçosa e o que isso tem a ver com o tema?
Quando você executa algo como ClasseX.objects.all(), você recebe um QuerySet com todos os registros, nenhuma novidade. Mas você sabia que somente quando você tentar acessar algum atributo é que um comando SQL será enviado para o banco para recuperar o valor deste atributo? É por isso que é chamada de avaliação preguiçosa, pois o ORM só vai enviar um comando SQL quando realmente for necessário. E isso poupa bastante recurso e tempo.
Mas cuidado... esse comportamento é uma faca de dois gumes.
Imagine que você tem uma ForeignKey que aponta para uma tabela com 2000 registros, na melhor das hipóteses você terá 2000 consultas ao banco, e se a classe para qual aponta o ForeignKey usar herança, a situação fica ainda pior, pois não teremos uma consulta simples, mas um join com outra(s) tabela(s), que aumentará ainda mais o tempo de resposta.
Então como faço para contornar este "problema"?
Eu consigo contornar este problema usando um Form, na verdade um ModelForm:
class ClienteForm(forms.ModelForm):
choices = PessoaBase.objects.values_list("id", "nome").all()
""" Precisamos executar a etapa abaixo, senão o admin vai
ficar pegando o primeiro registro, e não queremos isso.
Aqui eu usei -------- para seguir o que o Django adota, mas
você pode colocar qualquer mensagem.
Se você conseguir aperfeiçoar este código, compartilhe conosco.
;)
"""
choices = [('','--------')] + [i for i in choices]
pessoa_base = forms.ChoiceField(choices=choices)
def __init__(self, *args, **kwargs):
super(ClienteForm, self).__init__(*args, **kwargs)
class Meta:
model = Cliente
def clean(self):
""" É preciso sobrescrever o método clean porque o Django
vai estar esperando um objeto do tipo PessoaBase, e caso
não façamos isso ele vai receber um inteiro e levantar uma
exceção
"""
cleaned_data = self.cleaned_data
pessoa_base_id = cleaned_data.get('pessoa_base')
try:
pessoa_base = PessoaBase.objects.get(id=pessoa_base_id)
except PessoaBase.DoesNotExist:
raise forms.ValidationError('Escolha inválida!')
cleaned_data["pessoa_base"] = pessoa_base
return cleaned_data
Para finalizar só precisamos vincular nosso form ao admin(caso você esteja usando):
class ClienteAdmin(admin.ModelAdmin):
model = Cliente
form = ClienteForm
Apenas para vocês terem uma noção da diferença que essa técnica simples causa no desempenho, vejam os prints que fiz antes e depois de usar código acima(observem os campos hora e SQL):


Este comentário foi removido pelo autor.
ResponderExcluirNa verdade o queryset precisa ter mais intruções para se comportar melhor com sua necessidade. Para isso vc deve usar select_related, only e prefetch_related
ResponderExcluirValeu valder, vou testar sua dica e coloco o feedback.
ResponderExcluirOi Elton!!
ResponderExcluirParabéns pela iniciativa do blog! Estou iniciando no Python/Django e percebi que os campos ChoiceField são muito lentos (carregam tudo no HTML). O Select2 é melhor pra FKs com grande quantidade de registros.
Uma das coisas que estão pegando aqui são os ChoiceFields "aninhados". Exemplo:
- No models.py tenho uma classe Endereco que tem um atributo cidade (FK).
- Para montar meu form, gostaria que o usuário selecionasse primeiro o país, depois o estado e daí então a cidade, filtrando os resultados de cada escolha.
Porém, não tenho o país e estado no modelo... como faço isso?
Abraços!
Tudo bom Junior?
ExcluirEu faria o seguinte:
No forms.py defino um ModelForm, e acrescento dois ModelChoiceField, um com o pais e outro com o estado.
Depois via javascript você vai fazendo a lógica que você explicou.
Espero ter ajudado.
Vou ter que aprender javascript também! Kkkk :/
Excluir