Django的Form表单类与Django模型描述对象的逻辑结构、行为以及它呈现给我们内容的形式的方式大致相同。
那为什么有了模型,还要自己创建表单类呢?原因之一是模型中有一些字段你不需要用户从前端输入数据,或者需要用户额外输入一些非模型字段的数据。Form表单精确控制了这些行为,相当于你在用户HTML表单输入框和Django模型之间的中间件。
假设你想从表单接收用户名数据,一般情况下,你需要在HTML中手动编写一个如下的表单元素:
<form action="/your-name/" method="post"> <label for="your_name">Your name: </label> <input id="your_name" type="text" name="your_name" value="{{ current_name }}"> <input type="submit" value="OK"> </form>
<form action="/your-name/" method="post">
这一行定义了我们的发送目的地/your-name/
和HTTP方法POST
。form元素内部还定义了一个说明标签<label>
和一个发送按钮submit
,以及最关键的接收用户输入的<input>
元素。具体的更多HTML语言相关内容,请自行学习。
首先,在你当前应用内新建一个forms.py
文件(这个套路是Django的惯用手法,就像views.py
,models.py
等等),然后输入下面的内容:
from django import forms class NameForm(forms.Form): your_name = forms.CharField(label='Your name', max_length=100)
相关说明:
CharField
,它们分别对应一种HTML语言中的<form>
元素中的表单元素。这一点和Django模型系统的设计非常相似。max_length
限制最大长度为100。它同时起到两个作用,一是在浏览器页面限制用户输入不可超过100个字符,二是在后端服务器验证用户输入的长度是否超过100。(警告:由于浏览器页面是可以被篡改、伪造、禁用、跳过的,所有的HTML手段的数据验证只能防止意外不能防止恶意行为,是不安全的。破坏分子完全可以跳过浏览器的防御手段伪造发送请求!所以,在服务器后端,必须将前端当做“裸机”来对待,再次进行完全彻底的数据验证和安全防护!)
当我们将上面的表单渲染成真正的HTML元素,其内容如下:
<label for="your_name">Your name: </label> <input id="your_name" type="text" name="your_name" maxlength="100" required />
一定要注意,它不包含<form>
标签本身以及提交按钮!!!为什么要这样?方便你自己控制表单动作和CSS,JS以及其它类似bootstrap框架的嵌入!
发回Django网站的表单数据由某个指定视图来处理,一般情况下,处理和发布这个表单都会使用同一个视图,这样我们可以重用一些相同的代码。
为了处理表单数据,我们需要将表单类实例化:
# views.py from django.shortcuts import render from django.http import HttpResponseRedirect from .forms import NameForm def get_name(request): # 如果form通过POST方法发送数据 if request.method == 'POST': # 使用request.POST参数(参数中包含了发送过来的所有数据),构造form类的实例 form = NameForm(request.POST) # 验证数据是否合法 if form.is_valid(): # 处理form.cleaned_data中的数据 # ... # 重定向到一个新的URL return HttpResponseRedirect('/thanks/') # 如果是通过GET方法请求数据,返回一个空的表单 else: form = NameForm() return render(request, 'name.html', {'form': form})
表单本质上是个类,类可以实例化,每个表单实例都有一个内置的is_valid()
方法,用来验证接收的数据是否合法。如果所有数据都合法,那么该方法将返回True,并将所有的表单数据转存到它的一个叫做cleaned_data
的属性中,该属性是一个字典类型数据。
相关说明:
form.is_valid()
不通过的时候,就没有正常的返回值了。所以,这一行是整个视图的托底。在Django的模板中,我们只需要按下面处理,就可以得到完整的HTML页面:
<form action="/your-url/" method="post"> {% csrf_token %} {{ form }} <input type="submit" value="Submit" /> </form>
要点:
<form>...</form>
标签要自己写;{% csrf_token %}
标签,用于处理csrf安全问题;{{ form }}
代表Django为你生成其它所有的form标签元素,也就是我们上面做的事情;上面的例子中,只有一个用户名输入框,太简单了,实际上会有很多的表单元素。看下面的例子:
from django import forms class ContactForm(forms.Form): subject = forms.CharField(max_length=100) message = forms.CharField(widget=forms.Textarea) sender = forms.EmailField() cc_myself = forms.BooleanField(required=False)
这个例子有4个框组。
每一个表单字段类型都对应一种Widget类,每一种Widget类都对应了HMTL语言中的一种input元素类型,比如<input type="text">
。需要在HTML中使用什么类型的input,就在Django的表单字段中选择相应的field类型。比如要一个<input type="text">
,就增加一个CharField
。
一旦你的表单接收数据并验证通过了(is_valid()
),那么就可以从form.cleaned_data
字典中读取所有的表单数据,并且已经转化为恰当的Python类型。下面是一个例子:
# views.py from django.core.mail import send_mail if form.is_valid(): subject = form.cleaned_data["subject"] message = form.cleaned_data["message"] sender = form.cleaned_data["sender"] cc_myself = form.cleaned_data["cc_myself"] recipients = ["info@example.com"] if cc_myself: recipients.append(sender) send_mail(subject, message, sender, recipients) return HttpResponseRedirect("/thanks/")
前面我们通过{{ form }}
模板语言,简单地将表单渲染到HTML页面中了,实际上,有更多的方式:
{{ form.as_div }}
将表单的每个输入框包裹在一个<div>
标签内。这是默认方式{{ form.as_table }}
将表单渲染成一个表格元素,每个输入框作为一个<tr>
标签{{ form.as_p }}
将表单的每个输入框包裹在一个<p>
标签内{{ form.as_ul }}
将表单渲染成一个列表元素,每个输入框作为一个<li>
标签注意:你要自己手动编写<table>
和<ul>
标签。
下面是将ContactForm
作为{{ form.as_p }}
渲染的结果:
<p><label for="id_subject">Subject:</label> <input id="id_subject" type="text" name="subject" maxlength="100" required></p> <p><label for="id_message">Message:</label> <textarea name="message" id="id_message" required></textarea></p> <p><label for="id_sender">Sender:</label> <input type="email" name="sender" id="id_sender" required></p> <p><label for="id_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_cc_myself"></p>
注意:Django自动为每个input元素设置了一个id名称,对应label的for参数。
直接{{ form }}
虽然好,啥都不用操心,但是往往并不是你想要的,比如你要使用CSS和JS,比如你要引入Bootstarps框架,这些都需要对表单内的input元素进行额外控制,那怎么办呢?手动渲染字段就可以了。
可以通过{{ form.name_of_field }}
获取每一个字段,然后分别渲染,如下例所示:
{{ form.non_field_errors }} <div class="fieldWrapper"> {{ form.subject.errors }} <label for="{{ form.subject.id_for_label }}">Email subject:</label> {{ form.subject }} </div> <div class="fieldWrapper"> {{ form.message.errors }} <label for="{{ form.message.id_for_label }}">Your message:</label> {{ form.message }} </div> <div class="fieldWrapper"> {{ form.sender.errors }} <label for="{{ form.sender.id_for_label }}">Your email address:</label> {{ form.sender }} </div> <div class="fieldWrapper"> {{ form.cc_myself.errors }} <label for="{{ form.cc_myself.id_for_label }}">CC yourself?</label> {{ form.cc_myself }} </div>
其中的label标签甚至可以用label_tag()
方法来生成,于是可以简写成下面的样子:
<div class="fieldWrapper"> {{ form.subject.errors }} {{ form.subject.label_tag }} {{ form.subject }} </div>
这样子是不是更加灵活了呢?但是灵活的代价就是我们要写更多的代码,又偏向原生的HTML代码多了一点。
注意上面的例子中,我们使用{{ form.name_of_field.errors }}
模板语法,在表单里处理错误信息。对于每一个表单字段的错误,它其实会生成一个无序列表,参考下面的样子:
<ul class="errorlist"> <li>Sender is required.</li> </ul>
这个列表有个默认的CSS样式类errorlist
,如果你想进一步定制这个样式,可以循环错误列表里的内容,然后单独设置样式:
{% if form.subject.errors %} <ol> {% for error in form.subject.errors %} <li><strong>{{ error|escape }}</strong></li> {% endfor %} </ol> {% endif %}
一切非字段的错误信息,比如表单的错误,隐藏字段的错误都保存在{{ form.non_field_errors }}
中,上面的例子,我们把它放在了表单的外围上面,它将被按下面的HTML和CSS格式渲染:
<ul class="errorlist nonfield"> <li>Generic validation error</li> </ul>
如果你的表单字段有相同格式的HTML表现,那么完全可以循环生成,不必要手动的编写每个字段,只需要使用模板语言中的{% for %}
循环,减少冗余和重复代码,如下所示:
{% for field in form %} <div class="fieldWrapper"> {{ field.errors }} {{ field.label_tag }} {{ field }} {% if field.help_text %} <p class="help">{{ field.help_text|safe }}</p> {% endif %} </div> {% endfor %}
下表是{{ field }}
中非常有用的属性,这些都是Django内置的模板语言给我们提供的方便:
属性 | 说明 |
---|---|
{{ field.label }} |
字段对应的label信息 |
{{ field.label_tag }} |
自动生成字段的label标签,注意与{{ field.label }} 的区别。 |
{{ field.id_for_label }} |
自定义字段标签的id |
{{ field.value }} |
当前字段的值,比如一个Email字段的值someone@example.com |
{{ field.html_name }} |
指定字段生成的input标签中name属性的值 |
{{ field.help_text }} |
字段的帮助信息 |
{{ field.errors }} |
包含错误信息的元素 |
{{ field.is_hidden }} |
用于判断当前字段是否为隐藏的字段,如果是,返回True |
{{ field.field }} |
返回字段的参数列表。例如{{ char_field.field.max_length }} |
{{ field.legend_tag }} |
类似{{ field.label_tag }} ,不同的是用<legend> 替代<label> |
{{ field.use_fieldset }} |
如果想使用<fieldset> 标签来控制form表单内的组件,请使用它。例子: |
{% if field.use_fieldset %} <fieldset> {% if field.label %}{{ field.legend_tag }}{% endif %} {% else %} {% if field.label %}{{ field.label_tag }}{% endif %} {% endif %} {{ field }} {% if field.use_fieldset %}</fieldset>{% endif %}
很多时候,我们的表单中会有一些隐藏的不可见的字段,比如honeypot
。我们需要让它在任何时候都仿佛不存在一般,比如有错误的时候。如果你在页面上显示了不可见字段的错误信息,那么用户会很迷惑,这是哪来的呢?所以,通常我们是不显示不可见字段的错误信息的。对于honeypot
蜜罐,不是给用户使用的,而是用于对付机器人pot,不需要在页面上显示。
Django提供了两种独立的方法,用于循环那些不可见的和可见的字段,hidden_fields()
和visible_fields()
。我们可以稍微修改一下前面的例子:
{# 循环那些不可见的字段 #} {% for hidden in form.hidden_fields %} {{ hidden }} {% endfor %} {# 循环可见的字段 #} {% for field in form.visible_fields %} <div class="fieldWrapper"> {{ field.errors }} {{ field.label_tag }} {{ field }} </div> {% endfor %}
如果你在自己的HTML文件中,多次使用同一种表单模板,那么你完全可以把表单模板存成一个独立的HTML文件,然后在别的HTML文件中通过include模板语法将其包含进来,如下例所示:
# 实际的页面文件中: {% include "form_snippet.html" %} ----------------------------------------------------- # 单独的表单模板文件form_snippet.html: {% for field in form %} <div class="fieldWrapper"> {{ field.errors }} {{ field.label_tag }} {{ field }} </div> {% endfor %}
如果你的页面同时引用了好几个不同的表单模板,那么为了防止冲突,你可以使用with参数,给每个表单模板取个别名,如下所示:
{% include "form_snippet.html" with form=comment_form %}
在使用的时候就是:
{% for field in comment_form %} ......
如果你经常做这些重用的工作,建议你考虑自定义一个内联标签,这已经是Django最高级的用法了。
不管上面怎么花式使用表单模板,最终表单会被渲染成什么样子的HTML代码是通过内部预先定义好的模板生成的。
我们可以创建自定义的模板文件,并在settings中配置FORM_RENDERER
项目,从而在整个站点范围内控制表单渲染的结果。
也可以通过指定表单的template_name
属性或者将模板名称直接传递给form.render()
方法来自定义单个表单的渲染模板。
请看下面的例子:
# 假设你在某个detail.html中些的form渲染代码: {{ form }} # 假设你希望表单最终被渲染成form_snippet.html内的样子 # 那么在form_snippet.html中,你可以这么些: {% for field in form %} <div class="fieldWrapper"> {{ field.errors }} {{ field.label_tag }} {{ field }} </div> {% endfor %}
我们需要在settings中配置 FORM_RENDERER
:
settings.py
from django.forms.renderers import DjangoTemplates # 注意,官方例子使用的是TemplatesSetting,这不好,因为还需要配置一些东西 #否则会出现查找不到django/forms/errors/list/default.html的异常。所以我们这里直接基础了DjangoTemplates,更方便。 class CustomFormRenderer(DjangoTemplates): # 注意,这个模板必须放置在某个app内,不能放在根目录下的templates目录中 # 因为默认情况下DjangoTemplates是不会去搜索根目录下的templates目录。 form_template_name = "you_app/form_snippet.html" # 指定要渲染的模板格式文件 FORM_RENDERER = "project.settings.CustomFormRenderer" # 注册它
上面的操作,会在整个站点中起效。
如果你只想应用于单个form,可以在form类中定义template_name属性:
class MyForm(forms.Form): template_name = "form_snippet.html" ...
或者在函数视图中使用 Form.render()
方法,将模板名传递给它:
def index(request): form = MyForm() rendered_form = form.render("form_snippet.html") context = {"form": rendered_form} return render(request, "index.html", context)
Django5.0新增。
每个field都有一个 as_field_group()
方法,可以将它的label、widget、errors、help text等内容以组的形式,一并渲染出来。
比如下面的例子:
{{ form.non_field_errors }} <div class="fieldWrapper"> {{ form.subject.as_field_group }} </div> <div class="fieldWrapper"> {{ form.message.as_field_group }} </div> <div class="fieldWrapper"> {{ form.sender.as_field_group }} </div> <div class="fieldWrapper"> {{ form.cc_myself.as_field_group }} </div>
默认情况下,Django会使用 django/forms/field.html
模板以及django/forms/div.html
中的表单样式来进行渲染。但这可能不是你想要的样式。我们可以通过指定field_template_name
的值,进而在settings中配置FORM_RENDERER
来整站级别的修改这一行为。例如:
from django.forms.renderers import DjangoTemplates class CustomFormRenderer(DjangoTemplates): field_template_name = "you_app/field_snippet.html" FORM_RENDERER = "project.settings.CustomFormRenderer"
或者在需要的表单字段中,用template_name
参数来指定:
class MyForm(forms.Form): subject = forms.CharField(template_name="my_custom_template.html") ...
或者在函数视图中通过 BoundField.render()
方法指定模板:
def index(request): form = ContactForm() subject = form["subject"] context = {"subject": subject.render("my_custom_template.html")} #关键是这一行 return render(request, "index.html", context)
想要构造另一个form类的实例(但是有的信息request.POST中没有),还可以用什么方法呢?
解决了 values={'user':'root','city':2} obj=MyForm(values)
在html文件中调用{{ form }}的时候没显示输入框啊! 有人跟我出现一样的问题没?
已经解决
打卡
这个不是特别的好用,不如直接写html代码来得方便 另外:“该属性是以个字典类型数据”应该是“该属性是一个字典类型数据”
前端的东西,尽量交给前端框架完成,这样更灵活,效果更好。
说的太对了
请问一下<div class="fieldWrapper">这个类是定义在哪里的呀
fieldWrapper放在这里属于装饰用途,让代码片段看起来像那么回事,实际并不重要。文章的核心是form的用法。
如果感觉前端也使用了一些框架,配合django渲染老感觉很不方便
是的
url.py这个文件的路径怎么配置? forms.py---->view.py---get_name-----前端
讲真的,感觉Django表单限制很多,不如直接写HTML来得爽、灵活。
博主你好,单选按钮如何实现? forms.CharField(widget=forms.RadioSelect)不显示任何结果。