Back-End

13 mar, 2014

Como alterar campo do formulário em um Inline FormSet

Publicidade

Oi, pessoal! Tudo bem? Depois de algum tempo sem novidades, aqui esta o primeiro artigo do ano pra vocês!

Este artigo mostra como resolver um problema aparentemente simples mas que me custou algumas horas de pesquisa. O problema está relacionado a alterações que fazemos em campos, ou seus atributos, de formulários do Django. Por exemplo, se precisamos incluir opções ao choices de um ChoiceField ou CharField de forma dinâmica, podemos sobrescrever o método inicializador do Form, mais ou menos assim:

class BookForm(forms.ModelForm):
    class Meta:
        model = Book

    def __init__(self, *args, **kwargs):
        super(BookForm, self).__init__(*args, **kwargs)
        new_choices = [('teste', 'Teste')]
        self.fields['category'].choices.extend(new_choices)

Bem tranquilo, certo? Mas e se este formulário fizer parte de um (Inline) FormSet? O Django prove uma forma fácil de trabalhar com estas estruturas. São factories que constroem FormSets conforme nossa necessidade.

Até aqui tudo bem, mas como realizar uma alteração como no exemplo acima em um Inline FormSet? Neste caso, a alteração no inicializador do formulário não funciona, isso acontece porque após criar o FormSet com a factory o formulário não é mais usado. A saída é utilizar um parâmetro pouco conhecido das funções que constroem FormSets, o formfield_callback. Este parâmetro deve ser uma função que fará a mudança no campo que você precisa alterar. Criei um exemplo para ajudar a entender:

def add_category(field, **kwargs):
    if field.name == 'category':
        additional_choices = [
            ('best_seller', 'Best Seller'),
            ('self_help', 'Auto Ajuda')
        ]
    for choice in additional_choices:
        if not choice in field.choices:
            field.choices.extend(additional_choices)

    return field.formfield(**kwargs)

Esta é a função que faz a alteração que preciso. Como o exemplo anterior, estou inserindo mais opções ao choices de um campo chamado category. Estas são opções “hard coded”, mas é possível fazer todo tipo de alteração que você imaginar. Se faz necessário uma pequena verificação se aquela opção já existe no choices do campo para não haver duplicações.

def author_edit_view(request, author):
    BookInlineFormSet = inlineformset_factory(
        Author, Book, extra=1,
        form=BookForm,
        formfield_callback=add_category
    )

    form = AuthorForm(request.POST or None, instance=author)
    formset = BookInlineFormSet(request.POST or None, instance=author)

    if form.is_valid() and formset.is_valid():
        form.save()
        formset.save()
        return HttpResponseRedirect('/inlines/')

    return render_to_response("manage_authors.html",
        {"formset": formset, "form": form},
        RequestContext(request))

Esta é a view de edição do modelo. Ela cria o FormSet com a função inlineformset_factory, usando a função add_category. E instancia esse FormSet com os dados do post e uma instância de author. Assim minha alteração é aplicada em todas as ocorrências neste FormSet:

captura-de-tela-de-2013-01-22-193503

Legal, né? Gostou da dica? Então veja o código completo no meu Github: https://github.com/rafaelnovello/Django-Examples

Bom, é isso pessoal! Espero que essa dica possa ajudar mais alguém. Se tiverem dúvidas, criticas ou sugestões escrevam nos comentários.

Um abraço!

Referências