Se você está construindo uma aplicação baseada em banco de dados, existe uma grande chance de que seus formulários corresponderão com os seus modelos Django. Por exemplo, você tem um modelo BlogComment, e quer criar um formulário que possibilite que as pessoas enviem comentários. Neste caso, seria redundante definir os tipos de campo no seu formulário, porque isso já foi feito no seu modelo.
Por este motivo, o Django disponibiliza uma classe de ajuda que possibilita a criação de uma classe Form a partir de um modelo Django.
Por exemplo:
>>> from django.forms import ModelForm
# Cria a classe de formulário
>>> class ArticleForm(ModelForm):
... class Meta:
... model = Article
# Criando um formulário para adicionar um artigo.
>>> form = ArticleForm()
# Criando um formulário para atualizar dados de um artigo.
>>> article = Article.objects.get(pk=1)
>>> form = ArticleForm(instance=article)
A classe Form gerada terá um campo de formulário para cada campo de modelo. Cada campo de modelo tem um campo de formulário correspondente padrão. Por exemplo, um CharField num modelo é representado como um CharField num formulário. Um ManyToManyField no modelo é representado como um MultipleChoiceField. Segue uma lista completa de conversões:
Campo de modelo | Campo de formulário |
---|---|
AutoField | Não é representado no formulário |
BooleanField | BooleanField |
CharField | CharField com max_length igual ao valor de max_length do campo do modelo |
CommaSeparatedIntegerField | CharField |
DateField | DateField |
DateTimeField | DateTimeField |
DecimalField | DecimalField |
EmailField | EmailField |
FileField | FileField |
FilePathField | CharField |
FloatField | FloatField |
ForeignKey | ModelChoiceField (veja abaixo) |
ImageField | ImageField |
IntegerField | IntegerField |
IPAddressField | IPAddressField |
ManyToManyField | ModelMultipleChoiceField (veja abaixo) |
NullBooleanField | CharField |
PhoneNumberField | USPhoneNumberField (de django.contrib.localflavor.us) |
PositiveIntegerField | IntegerField |
PositiveSmallIntegerField | IntegerField |
SlugField | CharField |
SmallIntegerField | IntegerField |
TextField | CharField com widget=forms.Textarea |
TimeField | TimeField |
URLField | URLField com verify_exists igual ao valor de verify_exists do campo do modelo |
XMLField | CharField com widget=Textarea |
Como esperado, os campos de modelo do tipo ForeignKey e ManyToManyField são casos especiais:
Além disso, cada campo de formulário gerado tem os valores de atributos definidos como asseguir:
Finalmente, note que você pode redefinir o campo de formulário utilizado por um determinado modelo. Veja Redefinindo os tipos de campo padrão abaixo.
Considere este conjunto de modelos:
from django.db import models
from django.forms import ModelForm
TITLE_CHOICES = (
('MR', 'Mr.'),
('MRS', 'Mrs.'),
('MS', 'Ms.'),
)
class Author(models.Model):
name = models.CharField(max_length=100)
title = models.CharField(max_length=3, choices=TITLE_CHOICES)
birth_date = models.DateField(blank=True, null=True)
def __unicode__(self):
return self.name
class Book(models.Model):
name = models.CharField(max_length=100)
authors = models.ManyToManyField(Author)
class AuthorForm(ModelForm):
class Meta:
model = Author
class BookForm(ModelForm):
class Meta:
model = Book
Com estes modelos, as subclasses de ModelForm acima seriam equivalentes a isto (a única diferença sendo o método save(), que discutiremos daqui a pouco.):
class AuthorForm(forms.Form):
name = forms.CharField(max_length=100)
title = forms.CharField(max_length=3,
widget=forms.Select(choices=TITLE_CHOICES))
birth_date = forms.DateField(required=False)
class BookForm(forms.Form):
name = forms.CharField(max_length=100)
authors = forms.ModelMultipleChoiceField(queryset=Author.objects.all())
Todo formulário produzido por ModelForm tem também um método save(). Este método cria e grava um objeto no banco de dados a partir dos dados atrelados ao formulário. Uma subclasse de ModelForm pode aceitar uma instância de modelo existente como uma argumento nomeado instance; se este argumento é fornecido, o save() irá atualizar a instância. Se não é fornecido, o save() criará uma nova instância do modelo especificado:
# Cria uma instância de formulário com dados do POST.
>>> f = ArticleForm(request.POST)
# Grava um novo objeto Article com os dados do formulário.
>>> new_article = f.save()
# Cria um formulário para editar um Article existente.
>>> a = Article.objects.get(pk=1)
>>> f = ArticleForm(instance=a)
>>> f.save()
# Cria um formulário para editar um Article existente, mas
# usa os dados do POST para popular o formulário.
>>> a = Article.objects.get(pk=1)
>>> f = ArticleForm(request.POST, instance=a)
>>> f.save()
Note que save() irá levantar uma exceção ValueError se os dados no formulário não validarem -- ou seja if form.errors.
Este método save() aceita um argumento nomeado opcional commit, que aceita ou True ou False. Se você chamar save() com commit=False, então ele devolverá um objeto que ainda não foi gravado no banco de dados. Neste caso, é sua responsabilidade chamar save() na instância de modelo. Isso é útil se você quer fazer algum custom processamento customizado no objeto antes de gravá-lo, ou se você quer usar um umas das opções de gravação de modelo especializadas. commit é True por padrão.
Um efeito colateral no uso de commit=False é notado quando o seu modelo tem um relacionamento de muitos para muitos com outro modelo. Se seu modelo tem um relacionamento de muitos para muitos e você especifica commit=False quando vai gravar um formulário, o Django não pode gravar od dados para o relacionamento de muitos para muitos imediatamente. Isso se deve ao fato de não ser possível gravar os dados de muitos para muitos para uma instância que ainda não existe no banco de dados.
Para contornar este problema, cada vez que você grava um formulário usando commit=False, o Django adiciona um método save_m2m() para a sua subclasse de ModelForm. Depois de de gravar manualmente a instância produzida pelo formulário, você pode chamar save_m2m() para gravar os dados de muitos para muitos do formulário. Por exemplo:
# Cria uma instância de formulário com os dados de POST.
>>> f = AuthorForm(request.POST)
# Cria, mas não grava a nova instância de Author.
>>> new_author = f.save(commit=False)
# Modifica o Author de alguma maneira.
>>> new_author.some_field = 'some_value'
# Grava a nova instância.
>>> new_author.save()
# Agora, grava od dados de muitos para muitos para o formulário.
>>> f.save_m2m()
Só é necessário chamar o save_m2m() se você usar save(commit=False). Quando você simplesmente usa o save() num formulário, todos os dados -- incluindo os dados de muitos para muitos -- são gravados sem a necessidade de chamadas a nenhum método adicional. Por exemplo:
# Cria uma instância de formulário com os dados de POST.
>>> a = Author()
>>> f = AuthorForm(request.POST, instance=a)
# Cria e grava a nova instância de Author. Não há necessidade de fazer
# nada mais.
>>> new_author = f.save()
A não ser pelos métodos save() e save_m2m(), um ModelForm funciona exatamente igual a qualquer outro formulário de forms. Por exemplo, o método is_valid()` é utilizado para validar os dados, o método is_multipart() para determinar se um formulário requer upload de arquivo multipart (e se request.FILES deve ser passado ao formulário), etc. Veja Vinculando arquivos enviados a um formulário para mais informação.
Em alguns casos, você não quer que todos os campos do modelo apareçam no formulário gerado. Existem três maneiras de dizer ao ModelForm para usar somente alguns campos do modelo:
Por exemplo, se você quer que um formulário para o modelo Author (definido acima) tenha somente os campos name e title, você especificaria fields ou exclude assim:
class PartialAuthorForm(ModelForm):
class Meta:
model = Author
fields = ('name', 'title')
class PartialAuthorForm(ModelForm):
class Meta:
model = Author
exclude = ('birth_date',)
Já que o modelo Author tem somente três campos, 'name', 'title', e 'birth_date', os formulários acima conterão exatamente os mesmos campos.
Note
Se você especifica fields ou exclude na criação de um formulário com ModelForm, então na chamada do método save(), não serão atribuídos valores aos campos que não constam do formulário resultante. O Django impedirá qualquer tentativa de gravar um modelo incompleto, então se o modelo não permite que os campos faltantes sejam vazios, e não existe um valor padrão definido para eles, qualquer tentativa de chamar save() num ModelForm com campos faltantes não funcionará. Para evitar esse erro, você deve instanciar seu modelo com valores iniciais para os campos vazios, porém obrigatórios:
author = Author(title='Mr')
form = PartialAuthorForm(request.POST, instance=author)
form.save()
Alternatively, you can use save(commit=False) and manually set any extra required fields:
form = PartialAuthorForm(request.POST)
author = form.save(commit=False)
author.title = 'Mr'
author.save()
Veja a seção sobre gravação de formulários para mais detalhes no uso de save(commit=False).
Os tipos de campos padrão, como descritos na tabela Tipos de campos acima, são padrões sensatos. Se você tem um DateField no seu modelo, existem grandes chances dq que você queira que ele seja representado como um DateField no seu formulário. Mas o ModelForm te dá a flexibilidade de mudar o campo de formulário para um determinado campo de modelo. Isso é feito de maneira declarativa especificando os campos como faria num Form normal. Campos declarados redefinirão os campós padrões gerados pelo uso do atributo model.
Por exemplo, se você quiser usar MyDateFormField para o campo pub_date, faça o seguinte:
>>> class ArticleForm(ModelForm):
... pub_date = MyDateFormField()
...
... class Meta:
... model = Article
Se quiser redefiner um widget padrão de um campo, então especifique o parâmetro widget quando declarar o campo de formulário:
>>> class ArticleForm(ModelForm):
... pub_date = DateField(widget=MyDateWidget())
...
... class Meta:
... model = Article
Você pode sobrescrever o método clean() em um formulário de modelo para fornecer informações adicionais como uma validação, da mesma forma que faria num formulário normal.
In this regard, model forms have two specific characteristics when compared to forms:
Por padrão o método clean() valida a unicidade dos campos que são marcados como unique, unique_together ou unique_for_date|month|year no modelo. Portanto, se você gostaria de sobrescrever o método clean() e manter a validação padrão, você deve chamar o método da clean() da classe pai.
Also, a model form instance bound to a model object will contain a self.instance attribute that gives model form methods access to that specific model instance.
Como nos formulários básicos, você pode extender e reutilizar ModelForms através da herança. Isso é útil se você precisa declarar campos ou métodos adicionais em uma classe pai para uso em alguns formulários derivados de modelos. Por exemplo, usando a classe ArticleForm anterior:
>>> class EnhancedArticleForm(ArticleForm):
... def clean_pub_date(self):
... ...
Isso cria um formulário que se comporta da mesma maneira que ArticleForm, exceto por alguma validação e limpeza de dados adicional para o campo pub_date.
Você pode criar uma subclasse da classe interna Meta da classe pai se você quer mudar as listas Meta.fields ou Meta.excludes:
>>> class RestrictedArticleForm(EnhancedArticleForm):
... class Meta(ArticleForm.Meta):
... exclude = ['body']
Isso adiciona o método de EnhancedArticleForm e modifica o ArticleForm.Meta original para remover um campo.
Entretanto, existem algumas coisas a serem notadas.
É bem provável que estas notas não te afetarão, a não ser que esteja tentando fazer algo complexo utilizando subclasses.
Como nos formsets comuns, o Django fornece um par de classes avançadas de formset que tornam facil trabalhar com modelos do Django. Vamos reutilizar o modelo Author do exemplo acima:
>>> from django.forms.models import modelformset_factory
>>> AuthorFormSet = modelformset_factory(Author)
Isso criará um formset que é capaz de funcionar com os dados associados ao modelo Author que funciona exatamente igual a um formset comum:
>>> formset = AuthorFormSet()
>>> print formset
<input type="hidden" name="form-TOTAL_FORMS" value="1" id="id_form-TOTAL_FORMS" /><input type="hidden" name="form-INITIAL_FORMS" value="0" id="id_form-INITIAL_FORMS" />
<tr><th><label for="id_form-0-name">Name:</label></th><td><input id="id_form-0-name" type="text" name="form-0-name" maxlength="100" /></td></tr>
<tr><th><label for="id_form-0-title">Title:</label></th><td><select name="form-0-title" id="id_form-0-title">
<option value="" selected="selected">---------</option>
<option value="MR">Mr.</option>
<option value="MRS">Mrs.</option>
<option value="MS">Ms.</option>
</select></td></tr>
<tr><th><label for="id_form-0-birth_date">Birth date:</label></th><td><input type="text" name="form-0-birth_date" id="id_form-0-birth_date" /><input type="hidden" name="form-0-id" id="id_form-0-id" /></td></tr>
Note
Uma coisa para se notar é que modelformset_factory usa formset_factory para gerar formsets. Isto significa que um formeset de model é somente uma extensão de um formset básico que sabe como interagir com um model em particular.
Por padrão quando você cria um formset de um modelo o formset irá usar um queryset que inclui todos os objetos no modelo (ex: Author.objects.all()). Você pode sobrescrever esse comportamento usando o argumento queryset:
>>> formset = AuthorFormSet(queryset=Author.objects.filter(name__startswith='O'))
Alternativamente, você pode criar uma subclasse que defina o self.queryset no __init__:
from django.forms.models import BaseModelFormSet
class BaseAuthorFormSet(BaseModelFormSet):
def __init__(self, *args, **kwargs):
self.queryset = Author.objects.filter(name__startswith='O')
super(BaseAuthorFormSet, self).__init__(*args, **kwargs)
Então, passe sua classe BaseAuthorFormSet para a função factory:
>>> AuthorFormSet = modelformset_factory(Author, formset=BaseAuthorFormSet)
Por padrão um model formset usará todos os campos do model, que não estão marcados com editable=False, Entretanto, isto pode ser sobrescrito no nível do formset:
>>> AuthorFormSet = modelformset_factory(Author, fields=('name', 'title'))
Usando fields você irá restringir o formset para usar somente os campos fornecidos. Alternativamente, você pode ter uma abordagem de "out-put", especificando quais campos serão excluídos:
>>> AuthorFormSet = modelformset_factory(Author, exclude=('birth_date',))
Usando exclude você irá evistar que dados campos sejam usados em um formset.
Como num ModelForm você pode gravar os dados no objeto do modelo. Isso é feito com o método save() do formset:
# Cria uma instância de formset com os dados do POST.
>>> formset = AuthorFormSet(request.POST)
# Supondo que tudo está válido, grave os dados
>>> instances = formset.save()
O método save() devolverá as instâncias que foram gravadas no banco de dados. Se uma instância não mudar seus nos dados atrelados ela não será gravada no banco e não será encontrada no valor de retorn (instances no exemplo acima).
Pass commit=False to return the unsaved model instances:
# não grava no banco de dados
>>> instances = formset.save(commit=False)
>>> for instance in instances:
... # faz alguma coisa com a instância
... instance.save()
Isso dá a habilidade de anexar dados às instâncias antes de gravá-las no banco de dados. Se seu formset contém um ManyToManyField você precisará também chamar formset.save_m2m() para assegurar que os relacionamentos de muitos para muitos serão gravados apropriadamente.
Como em formsets comuns você pode usar um parâmetro max_num no modelformset_factory para limitar o número de formulários mostrados. Com formsets de modelo, isso limitará apropriadamente a query para selecionar somente o máximo de objetos necessários:
>>> Author.objects.order_by('name')
[<Author: Charles Baudelaire>, <Author: Paul Verlaine>, <Author: Walt Whitman>]
>>> AuthorFormSet = modelformset_factory(Author, max_num=2, extra=1)
>>> formset = AuthorFormSet(queryset=Author.objects.order_by('name'))
>>> formset.initial
[{'id': 1, 'name': u'Charles Baudelaire'}, {'id': 3, 'name': u'Paul Verlaine'}]
Se o valor de max_num é maior que o número de objetos retornados, formulários em branco extra serão adicionados ao formset, tão logo o total de formulários não exceda max_num:
>>> AuthorFormSet = modelformset_factory(Author, max_num=4, extra=2)
>>> formset = AuthorFormSet(queryset=Author.objects.order_by('name'))
>>> for form in formset.forms:
... print form.as_table()
<tr><th><label for="id_form-0-name">Name:</label></th><td><input id="id_form-0-name" type="text" name="form-0-name" value="Charles Baudelaire" maxlength="100" /><input type="hidden" name="form-0-id" value="1" id="id_form-0-id" /></td></tr>
<tr><th><label for="id_form-1-name">Name:</label></th><td><input id="id_form-1-name" type="text" name="form-1-name" value="Paul Verlaine" maxlength="100" /><input type="hidden" name="form-1-id" value="3" id="id_form-1-id" /></td></tr>
<tr><th><label for="id_form-2-name">Name:</label></th><td><input id="id_form-2-name" type="text" name="form-2-name" value="Walt Whitman" maxlength="100" /><input type="hidden" name="form-2-id" value="2" id="id_form-2-id" /></td></tr>
<tr><th><label for="id_form-3-name">Name:</label></th><td><input id="id_form-3-name" type="text" name="form-3-name" maxlength="100" /><input type="hidden" name="form-3-id" id="id_form-3-id" /></td></tr>
Models formsets são muito similares a formsets. Digamos que queiramos apresentar um formset para editar instâncias do modelo Author:
def manage_authors(request):
AuthorFormSet = modelformset_factory(Author)
if request.method == 'POST':
formset = AuthorFormSet(request.POST, request.FILES)
if formset.is_valid():
formset.save()
# Faça algo.
else:
formset = AuthorFormSet()
return render_to_response("manage_authors.html", {
"formset": formset,
})
Como você pode ver a lógica da visão de um formset de modelo não é drasticamente diferente de como usar um formset "normal". A única diferença é que nos chamamos formset.save() para salvar os dados no banco de dados. (Isto foi descrito acima em Gravando objetos num formset.)
Assim como com ModelForms, por padrão o método clean() de um model_formset irá validar se nenhum dos ítens do formset viola as restrições de unicidade do seu model (qualquer uma unique, unique_together ou unique_for_date|month|year). Se você quiser sobrescrever o método clean() de um model_formset e manter esta validação, você deve chamar o método clean da classe pai:
class MyModelFormSet(BaseModelFormSet):
def clean(self):
super(MyModelFormSet, self).clean()
# exemplo de validação customizada de forms do formset:
for form in self.forms:
# your custom formset validation
# sua validação personalizada de formset
Como já foi dito, você pode sobrescrever o queryset padrão usado pelo model formset:
def manage_authors(request):
AuthorFormSet = modelformset_factory(Author)
if request.method == "POST":
formset = AuthorFormSet(request.POST, request.FILES,
queryset=Author.objects.filter(name__startswith='O'))
if formset.is_valid():
formset.save()
# Faça algo.
else:
formset = AuthorFormSet(queryset=Author.objects.filter(name__startswith='O'))
return render_to_response("manage_authors.html", {
"formset": formset,
})
Note que nós passamos o argumento queryset em ambos os casos POST e GET neste exemplo.
Já três formas de renderizar um formset num template Django.
Primeiro, você pode deixar o formset fazer a maior parte do trabalho:
<form method="POST" action="">
{{ formset }}
</form>
Segundo, você pode manualmente renderizar o formset, mas deixe o form trabalhar por contra própria:
<form method="POST" action="">
{{ formset.management_form }}
{% for form in formset.forms %}
{{ form }}
{% endfor %}
</form>
Quando você renderizar formulários manualmente, certifique-se de renderizar o management_form como mostrado acima. Veja a documentação do management form.
Terceiro, você pode renderizar manualmente cada campo:
<form method="POST" action="">
{{ formset.management_form }}
{% for form in formset.forms %}
{% for field in form %}
{{ field.label_tag }}: {{ field }}
{% endfor %}
{% endfor %}
</form>
Se você optar por usar este terceiro método e você não iterar sobre os campos com um loop {% for %}, você precisará renderizar a chave primária. Por exemplo, se você renderizou os campos name e age de um model:
<form method="POST" action="">
{{ formset.management_form }}
{% for form in formset.forms %}
{{ form.id }}
<ul>
<li>{{ form.name }}</li>
<li>{{ form.age }}</li>
</ul>
{% endfor %}
</form>
Observe como é preciso, explicitamente, renderizar {{form.id}}. Isto garante que o formset do model, no caso do POST, irá funcionar perfeitamente. (Este exemplo assume uma chave primária chamada id, certifique-se de renderizá-la.)
Formsets em linha é uma pequena camada de abstração sobre os formsets de model. Este simplificam o caso de trabalhar com objetos relacionados através de uma chave estrangeira. Suponhamos que você tenha dois models:
class Author(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
author = models.ForeignKey(Author)
title = models.CharField(max_length=100)
Se você quiser criar um formset que permita editar books pertencentes a um author em particular, você pode fazer isto:
>>> from django.forms.models import inlineformset_factory
>>> BookFormSet = inlineformset_factory(Author, Book)
>>> author = Author.objects.get(name=u'Mike Royko')
>>> formset = BookFormSet(instance=author)
Note
inlineformset_factory usa modelformset_factory e marca can_delete=True.
Se o seu model contém mais de uma chave estrangeira para o mesmo model, você precisará resolver a ambiguidade manualmente utilizando fk_name. Por exemplo, considere o seguinte model:
class Friendship(models.Model):
from_friend = models.ForeignKey(Friend)
to_friend = models.ForeignKey(Friend)
length_in_months = models.IntegerField()
Para resolver isto, você pode usar fk_name para inlineformset_factory:
>>> FriendshipFormSet = inlineformset_factory(Friend, Friendship, fk_name="from_friend")
Você pode querer prover um view que permite um usuário editar os objetos relacionados de um model. Aqui tem uma maneira de se fazer isso:
def manage_books(request, author_id):
author = Author.objects.get(pk=author_id)
BookInlineFormSet = inlineformset_factory(Author, Book)
if request.method == "POST":
formset = BookInlineFormSet(request.POST, request.FILES, instance=author)
if formset.is_valid():
formset.save()
# Do something.
else:
formset = BookInlineFormSet(instance=author)
return render_to_response("manage_books.html", {
"formset": formset,
})
Observe como passamos instance em ambos os casos POST e GET.
Dec 26, 2011