Eu tenho um formulário com um campo FK, no meu exemplo, Setor.
# models.py
class Produto(models.Model):
titulo = models.CharField('Título', max_length=255)
setor = models.ForeignKey(
Setor,
on_delete=models.SET_NULL,
related_name='produtos',
null=True,
blank=True
)
Consequentemente o Setor é um campo do tipo select.
Para inserir novos valores eu estou usando select2, inclusive você pode ver aqui como eu fiz para integrar ele com AlpineJS, vai que um dia você precise.
Bom, com a opção tags: true
eu consigo inserir um novo valor no select pelo frontend.
$(".js-example-tags").select2({
tags: true
});
Mas basicamente ele faz isso aqui
return {
id: params.term,
text: params.term
}
ou seja, ele insere o próprio valor da string como id. E isso no backend não faz sentido.
Inicialmente eu estava tentando inserir o valor via Javascript.
$('#id_setor').select2({
tags: true, // to add new value
}).on('select2:close', function(){
const element = $(this)
const value = $.trim(element.val())
if (value != '') {
fetch('/produto/produtos/setor/create/', {
method: "POST",
headers: { "Content-Type": "application/json", "X-CSRFToken": "{{ csrf_token }}" },
body: JSON.stringify({
text: value
}),
})
.then(response => response.json())
Pega a visão {{ csrf_token }}
de como passar o csrf_token no JS.
Enfim, não deu certo. Ficou muito ruim, prejudicou a usabilidade e deu vários erros.
E o principal motivo desta thread: Form Validation Error
Quando eu liguei o ipdb
form.is_valid()
deu False
.
E form.errors
{'setor': ['Faça uma escolha válida. Sua escolha não é uma das disponíveis.']}
Dai eu tentei:
- Validar os dados no
cleaned_data
do forms.
def clean(self):
self.cleaned_data = super().clean()
setor, _ = Setor.objects.get_or_create(nome=self.data.get('setor'))
self.cleaned_data['setor'] = data['setor']
return self.cleaned_data
Mas não deu certo, o formulário continuava dando erro.
- Tentei usar um initial no form.
Dai eu tentei
# views.py
def produto_create(request):
template_name = 'produto/produto_form.html'
form = ProdutoForm()
if request.method == 'POST':
form = ProdutoForm(data, initial={'setor': setor})
# obj = form.save(commit=False) # TENTEI TB, E NADA.
# del form.errors['setor'] # OLHA A GAMBIARRA MONSTRO QUE EU TENTEI KKKKKKKKK
if form.is_valid():
form.save()
return redirect('produto:produto_list')
# forms.py
class ProdutoForm(forms.ModelForm):
class Meta:
model = Produto
fields = (
'titulo',
'setor',
)
def clean(self):
self.cleaned_data = super().clean()
data = self.initial
setor, _ = Setor.objects.get_or_create(nome=data)
self.cleaned_data['setor'] = data['setor']
return self.cleaned_data
Nada.
- Finalmente consegui resolver com
copy()
def produto_create(request):
template_name = 'produto/produto_form.html'
form = ProdutoForm()
if request.method == 'POST':
setor_nome = request.POST.get('setor')
setor, _ = Setor.objects.get_or_create(nome=setor_nome)
data = request.POST.copy() # sem o copy ele diz que é imutável.
data['setor'] = setor
form = ProdutoForm(data)
if form.is_valid():
form.save()
return redirect('produto:produto_list')
context = {'form': form}
return render(request, template_name, context)
E deletei o clean()
. E funcionou!!!
Mas e ai? Essa é a melhor solução?