Pegadinha do Djangão: form não salva Many to Many com commit=False

Lidar com formulário na web costuma ser muito chato, mas os forms do Django ajudam a aliviar esta dor.

Para manipular instâncias de models existe o ModelForm, com ele não é preciso criar todos os campos de um model, ele já cria os controles padrão para cada tipo de campo, é só alimentar o form, ele faz sozinho uma série de validações.

Se precisar manipular a instância antes de salvar no banco, pode usar um commit=False no método save, fazer as manipulações necessárias e salvar novamente.

Mas existe uma pegadinha aí. Se a instância não está sendo salva no banco, não existe uma chave primária ainda, e então não é possível associar os valores selecionados do campo many-to-many.

Consultando o oráculo encontrei a solução no changeset 5804: Fixed #4001 — Added dynamic save_m2m method() to forms created with form_for_model and form_for_instance on save(commit=False).

Tá, tá!! O changeset já tem quase dois anos de idade, mas e daí? Eu não sabia ainda…

Enfim, ao usar o commit=False, é criado no form um método dinâmico save_m2m para salvar as relações many-to-many após salvar uma instância.

Criei um exemplo para simular este comportamento, como estou com preguiça, apenas considere que estou usando um ModelForm com um model que tenha um ManyToMany

def some_view(request):

    if request.method != 'POST':
        form = SomeModelForm()
    else:
        form = form = SomeModelForm(request.POST)
        if form.is_valid():
            # Com o commit=False, a instância é criada mas não é salva no banco
            # Assim é possível alterar antes de salvar no banco.
            instance = form.save(commit=False)

            # Salva o usuário atual como autor da instância
            instance.author = request.user
            instance.save()

            # Se o form tiver algum campo M2M e for usado o commit=False,
            # ficará disponível o método save_m2m, que associa os valores
            # selecionados à instância recém-criada.

            # NOTA: vale lembrar que este método é criado dinamicamente,
            # ou seja, não é seguro executá-lo sempre ao salvar um form.

            if hasattr(form, 'save_m2m'):
                form.save_m2m()

            # Redireciona para a página de sucesso
            return HttpResponseRedirect('./ok/')

    context = {'form': form}

    return render_to_response('some_template.html', context)

UPDATE 09/02/2009: corrigida verificação de GET/POST, graças ao Igor Sobreira.

Sobre Rico
Software engineer

6 Responses to Pegadinha do Djangão: form não salva Many to Many com commit=False

  1. Igor Sobreira disse:

    Realmente, isso pode pegar os desavisados. Tá na documentação [1], mas lá poderia chamar mais atenção pra isso.

    Sim, e tem algo estranho na tua view. Se o método for GET é que você deve retornar um formlário vazio.

    if request.method == ‘GET’:
    form == SomeModelForm()
    else:
    # salva…

    [1] http://docs.djangoproject.com/en/dev/topics/forms/modelforms/#the-save-method

    Parabéns, muito bom o teu blog.

  2. Maique Rosa disse:

    Eh meu amigo post de trocentos anos atras mais me ajudou a resolver meu problema kkkkk
    muito obrigado por nao ter deletado🙂

  3. Muito obrigado cara!!

Deixe uma resposta

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s

%d blogueiros gostam disto: