Choice Django - criar choices a partir de um model (ModelChoiceField)

Choice Django - criar choices a partir de um model (ModelChoiceField)
django logo

Motivação:

from django.db import models

class Cachorro(models.Model):
    RACA_CHOICE = (
    ('1', 'Pitbull'),
    ('2', 'Rottweiler'),
    ('3', 'Vira-lata'),
    )
    nome = models.CharField(max_length=100)
    descricao = models.CharField(max_length=100)
    raca = models.CharField(max_length=10, null=False, choices=RACA_CHOICE)

Possuo um formulário de cadastro de cachorros, estou usando uma CHOICE e, a principio, só temos essas raças atualmente em nosso sistema, porém fico pensando se futuramente, pode ser necessário adicionar uma nova raça ao formulário, então a minha pergunta é, eu consigo atualizar uma CHOICE em tempo de execução?

Para solucionar este problema é necessário criar um model, e usar esse model como a fonte de dados do nosso campo raca, atribuindo a este campo um ModelChoiceField com um queryset associado.

Vamos então implementar esta funcionalidade

Para isso vai ser necessário criar um formulário baseado em nosso model, para isso eu vou usar a classe ModelForm.

Vamos às mudanças:

from django.db import models

class Cachorro(models.Model):
    nome = models.CharField(max_length=100)
    descricao = models.CharField(max_length=100)
    raca = models.CharField(null=False, max_length=10) #remover o choice aqui

Após remover, devo criar um model que vai representar as minhas opções de raça, para isso vou criar um novo model.

class Raca(models.Model):
    nome = models.CharField(max_length=32)
    def __str__(self):
        return self.nome

Agora devemos aplicar as nossas mudanças, fazendo o makemigrations e o migrate.

Isso foi necessário pois agora temos um novo model que vai possuir a mesma finalidade da variável RACA_CHOICE que tinhamos na nossa primeira versão da implementação.

Para popular as nossas opções, vamos precisar acessar o Django shell para adicionar as os dados necessários (assim vai ser possível ter escolhas no que for renderizado no select/option [no template html]).

from animal.models import Raca
r = Raca(nome='Pitbull')
r.save()
r1 = Raca(nome='Rottweiler')
r1.save()
r2 = Raca(nome='Vira-lata')
r2.save()

Dessa forma vamos ter renderizado os mesmo dados que tinhamos quando o choice era feito inline na mesma classe.

Então após adicionar os dados vamos criar o formulário.

class CachorroForm(ModelForm):
    class Meta:
        model = Cachorro
        fields = '__all__'
    raca = forms.ModelChoiceField(
        queryset=Raca.objects.all(),
    )

Vou criar um views.py apenas para renderizar o formulário para exibição:

from django.shortcuts import render

from .models import CachorroForm

def index(request):
    form = CachorroForm()

    context={
        "form": form
    }
    return render(request, 'animal/index.html', context)

Então teremos renderizado em nosso html o seguinte código:

resultado

Mostro em destaque apenas o trecho do html gerado correspondente ao campo raca:

<select name="raca" required id="id_raca">
  <option value="" selected>---------</option>
  <option value="1">Pitbull</option>
  <option value="2">Rottweiler</option>
  <option value="3">Vira-lata</option>
</select>

Com isso terminamos a nossa implementação.

Obrigado