目录
form 1
HTML中GET和POST: 1
例1,form处理: 2
django form: 4
例2,自动生成表单: 6
例3,手动渲染字段: 11
form field: 11
form field args: 12
form
HTML中GET和POST:
处理表单时候只会用到POST 和 GET 方法。
Django 的登录表单使用POST 方法,在这个方法中浏览器组合表单数据、对它们进行编码以用于传输、将它们发送到服务器然后接收它的响应。
相反,GET 组合提交的数据为一个字符串,然后使用它来生成一个URL。 这个URL 将包含数据发送的地址以及数据的键和值。 如果你在Django 文档中做一次搜索,你会立即看到这点,此时将生成一个https://docs.djangoproject.com/search/?q=forms&release=1 形式的URL。
POST 和GET 用于不同的目的。
用于改变系统状态的请求 —— 例如,给数据库带来变化的请求 —— 应该使用POST。 GET 只应该用于不会影响系统状态的请求。
GET 还不适合密码表单,因为密码将出现在URL 中,以及浏览器的历史和服务器的日志中,而且都是以普通的文本格式。 它还不适合数据量大的表单和二进制数据,例如一张图片。 使用GET 请求作为管理站点的表单具有安全隐患:攻击者很容易模拟表单请求来取得系统的敏感数据。 POST,如果与其它的保护措施结合将对访问提供更多的控制,例如Django 的CSRF protection。
另一个方面,GET 适合网页搜索这样的表单,因为这种表示一个GET 请求的URL 可以很容易地作为书签、分享和重新提交。
例1,form处理:
mysite/polls/templates/polls/detail.html
<h1>{{ question.question_text }}</h1>
{% if error_message %}
<P><strong>{{ error_message }}</strong></P>
{% endif %}
<form action="{% url 'polls:vote' question.id %}">
{% csrf_token %}
{% for choice in question.choice_set.all %}
<input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" />
<label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br />
{% endfor %}
<input type="submit" value="Vote" />
</form>
mysite/polls/views.py
from django.shortcuts import render, get_object_or_404, reverse
from django.http import HttpResponse, HttpResponseRedirect
from .models import Question, Choice
from django.template import loader
# from django.core.urlresolvers import reverse
# def index(request):
# latest_question_list = Question.objects.order_by('-pub_date')[:4]
# template = loader.get_template('polls/index.html')
# context = {'latest_question_list': latest_question_list}
# # output = ', '.join([q.question_text for q in latest_question_list])
# return HttpResponse(template.render(context))
def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:4]
context = {'latest_question_list': latest_question_list}
return render(request, 'polls/index.html', context)
def detail(request, question_id):
# try:
# question = Question.objects.get(id=question_id)
# except Question.DoesNotExist:
# return HttpResponse('Not Found', status=404)
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/detail.html', {'question': question})
def results(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/results.html', {'question': question})
def vote(request, question_id):
question = get_object_or_404(Question, pk=question_id)
print(request)
if request.method == 'POST':
choice_id = request.POST.get('choice', 0)
try:
selected_choice = question.choice_set.get(pk=choice_id)
except Choice.DoesNotExist:
return render(request, 'polls/detail.html', {
'question': question, 'error_message': 'You did not select a choice',
})
else:
selected_choice.votes += 1
selected_choice.save()
return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))
mysite/polls/templates/polls/results.html
<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
<li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
{% endfor %}
</ul>
<a href="{% url 'polls:detail' question.id %}">Vote again?</a>
django form:
模型类的属性映射到数据库表中的字段;
表单类的属性映射到HTML form的input元素;
所有的表单类都作为django.forms.Form的子类创建,包括ModelForm;
<form>{% csrf_token %}</form>,django原生支持一个简单易用的protection against cross site request forgeries,当提交一个启用CSRF防护的POST表单时,必须使用{ % csrf_token %}模板标签;
forms.py中,在定义表单时,若有URLField、EmailField或其它整数字段类型,django将使用number、url和email这样的HTML5输入类型,默认browser会对这些字段进行它们自身的验证,这些验证比django验证严格,如果要禁用HTML5中的验证,在模板中加上form的novalidate属性或指定一个不同的字段TextInput;
若打算直接用来添加和编辑django模型,ModelForm可节省时间、精力、代码,因为它将根据Model类构建一个表单及适当的字段和属性;
绑定表单和未绑定表单:
form.is_bound属性返回True|False;
不包含数据的表单称为未绑定表单,当render给用户时,它将为空或包含默认的值;
包含数据的表单称为绑定表单,可验证数据是否合法,如果render一个不合法的绑定表单,它将给browser抛错,让用户纠正数据;
widget,窗口小部件,每个表单字段都有一个对应的widget class,它对应一个HTML表单widget,如<input type="text">;大部分情况下,字段都具有一个合理默认widget,如CharField具有一个TextInput Widget,它在HTML中生成一个<input type="text">,如果需要message,在定义表单字段时应指定一个合适的widget,如message = forms.CharField(widget=forms.Textarea);
表单呈现选项:
form.as_p()
form.as_table() #在模板中需自己提供<table>或<ul>元素
form.as_ul()
HTML的label标签为input元素定义标注,label标签的for属性应与相关元素的id属性相同;
定义表单字段时用了label标签,使用form.as_p自动生成label的for为id_<field-name>,对应的input中的id也是id_<field-name>;
渲染表单错误信息:
模板中,{{ form.non_field_errors }},查找每个字段的错误,并展示到上方;
| Returns an ErrorList of errors that aren't associated with a particular
| field -- i.e., from Form.clean(). Returns an empty ErrorList if there
| are none.
模板中,{{ form.<field-name>.errors }},显示表单错误的一个清单,并渲染成一个<ul>;
循环表单字段:
https://yiyibooks.cn/xx/Django_1.11.6/topics/forms/index.html#html-forms
若表单使用相同的html,可循环迭代每个字段来减少重复代码;
{% 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 }}属性:
{{ field.label }},字段的label,如Email;
{{ field.label_tag }},包含在HTML label标签中的字段label,它包含表单的label_suffix,如默认label_suffix是冒号;
{{ field.id_for_label }},手动渲染字段用到,如<label for="id_email">Email:</label>,lable的for对应{{ field.id_for_label }};
{{ field.value }},字段的值,如someone@ane56.com;
{{ field.html_name }},输入元素的name属性中将使用的名称,它将考虑到表单的前缀;
{{ field.help_text }},与该字段关联的提示信息(帮助文档);
{{ field.errors }},输出一个<ul>,包含这个字段的验证错误信息;
表单验证:
view中在调用form.is_valid()时验证;
模板中{{ form.non_filed_errors }}、{{ field.errors }}访问errors属性时隐式调用;
field手动校验:
>>> form = forms.EmailField()
>>> form.clean('jowin@ane56.com')
'jowin@ane56.com'
>>> form.clean('test')
……
raise ValidationError(errors)
django.core.exceptions.ValidationError: ['Enter a valid email address.']
>>> from django import forms
>>> class NameForm(forms.Form):
... your_name = forms.CharField(label='your name', max_length=100)
...
>>> form = NameForm()
>>> form.is_bound
False
>>> form.as_p() #表单呈现选项,将其渲染在<p>标签中;label标签为input元素定义标注,label标签的for属性应与相关元素的id属性相同
'<p><label for="id_your_name">your name:</label> <input type="text" name="your_name" maxlength="100" required id="id_your_name" /></p>'
>>> form.as_table() #以表格形式将其渲染在<tr>中
'<tr><th><label for="id_your_name">your name:</label></th><td><input type="text" name="your_name" maxlength="100" required id="id_your_name" /></td></tr>'
>>> form.as_ul() #将其渲染在<li>标签中
'<li><label for="id_your_name">your name:</label> <input type="text" name="your_name" maxlength="100" required id="id_your_name" /></li>'
>>> form=NameForm({'your_name':'jowin'})
>>> form.is_bound #绑定表单和未绑定表单
True
>>> form
<NameForm bound=True, valid=Unknown, fields=(your_name)>
>>> form.cleaned_data['your_name'] #访问数据前没有form.is_valid()抛no attribute
Traceback (most recent call last):
File "<console>", line 1, in <module>
AttributeError: 'NameForm' object has no attribute 'cleaned_data'
>>> form.is_valid() #做2件事,1为所有字段运行验证的程序,若所有字段都是合法数据返回True,2并将表单的数据放到cleaned_data属性中
True
>>> form.cleaned_data['your_name']访问数据前要先执行form.is_valid()
'jowin'
>>> form=NameForm({})
>>> form.is_bound
True
>>> form.is_valid()
False
>>> form.errors
{'your_name': ['This field is required.']}
>>> form = NameForm({'your_name':'jowin'*50})
>>> form.is_bound
True
>>> form.is_valid()
False
>>> form.errors
{'your_name': ['Ensure this value has at most 100 characters (it has 250).']}
例2,自动生成表单:
出版社、作者、书籍案例;
模板中用{{ form }}自动生成;
<form method="post" action="">
{% csrf_token %}
{{ form }}
<input type="submit" value="Submit">
</form>
(webproject) C:\webproject\mysite>django-admin startapp books
(webproject) C:\webproject\mysite>pip install django-bootstrap3
https://github.com/dyve/django-bootstrap3
mysite/mysite/settings.py
INSTALLED_APPS = [
'bootstrap3',
mysite/books/models.py
from django.db import models
from django import forms
class Publisher(models.Model):
name = models.CharField(max_length=30)
address = models.CharField(max_length=50)
city = models.CharField(max_length=60)
state_province = models.CharField(max_length=30)
country = models.CharField(max_length=50)
website = models.URLField()
def __str__(self):
return self.name
class Author(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=40)
email = models.EmailField()
def __str__(self):
return '{} {}'.format(self.first_name, self.last_name)
class Book(models.Model):
title = models.CharField(max_length=100)
authors = models.ManyToManyField(Author)
publisher = models.ForeignKey(Publisher)
publication_date = models.DateField()
def __str__(self):
return self.title
mysite/books/forms.py
from django import forms
class PublisherForm(forms.Form):
name = forms.CharField(label='Your name', max_length=30)
address = forms.CharField(max_length=50)
city = forms.CharField(max_length=60)
state_province = forms.CharField(max_length=30)
country = forms.CharField(max_length=20)
website = forms.CharField(max_length=50)
mysite/books/views.py
from django.shortcuts import render, redirect
from .models import Publisher
from .forms import PublisherForm
from django.http import HttpResponse
# def publisher_add(request): #ver1,验证用户输入(验证用户输入和model的约束重复)、返回用户error、html构造form、代码冗长,用django form解决
# if request.method == 'POST':
# name = request.POST.get('name', '')
# address = request.POST.get('address', '')
# city = request.POST.get('city', '')
# state_province = request.POST.get('state_province', '')
# country = request.POST.get('country', '')
# website = request.POST.get('website', '')
#
# error_message = []
# if not name:
# error_message.append('name is required')
# if len(name) > 100:
# error_message.append('name should be short than 100')
# if not address:
# error_message.append('address is required')
#
# if error_message:
# return render(request, 'books/book_add.html', {'error_message': error_message})
# else:
# publisher = Publisher(name=name, address=address, city=city, state_province=state_province,
# country=country, website=website)
# # return redirect('books:publisher-detail', kwargs={'publisher_id': publisher.id})
# publisher.save()
# return HttpResponse('add succeed')
# else:
# return render(request, 'books/publisher_add.html')
def publisher_add(request): #ver2,用户的输入不用手动获取;用户输入验证自动完成;模板中的表单不用手写html;带来的问题,需要编写Form结构(和model重复),需要手动获取内容保存到数据库,解决,使用modelform
if request.method == 'POST': #若使用POST请求提交表单,将创建一个表单实例,并用browser传来的数据填充,这被称为将数据绑定到表单
form = PublisherForm(request.POST) #将数据绑定到表单
if form.is_valid():
publisher = Publisher(
name=form.cleaned_data['name'], #form.cleaned_data('name')访问数据前要先form.is_valid(),form.is_valid()会将数据放到cleaned_data属性中 address=form.cleaned_data['address'],
city=form.cleaned_data['city'],
state_province=form.cleaned_data['state_province'],
country=form.cleaned_data['country'],
website=form.cleaned_data['website']
)
publisher.save() #用form.cleaned_data['website']找到所有合法的表单数据,在发送HTTP重定向给browser告诉它下一步的去向前,用这些数据更新DB
return HttpResponse('add successed')
else: #form.is_valid()不是True,返回到表单的模板,这时表单不再为空(未绑定),HTML表单将用之前提交的数据填充,再根据要求编辑并改正它
return render(request, 'books/books_add.html', {'form': form})
else: #如果是GET请求,将创建一个空的表单实例并将其放置在要渲染的模板的上下文中
form = PublisherForm()
return render(request, 'books/books_add.html', {'form': form})
mysite/books/templates/books/publisher_add.html #ver1
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form method="post" action="">
{% csrf_token %}
<label>Name: </label>
<input type="text" name="name">
<br>
<label>Address: </label>
<input type="text" name="address">
<br>
<label>City: </label>
<input type="text" name="city">
<br>
<label>State Province: </label>
<input type="text" name="state_province">
<br>
<label>Country: </label>
<input type="text" name="country">
<br>
<label>Website: </label>
<input type="text" name="website">
<br>
<input type="submit" value="Submit">
</form>
</body>
</html>
mysite/books/templates/books/books_add.html #ver2
<!--<form method="post" action="">-->不用bootstrap的样式展示
<!--{% csrf_token %}--> #django原生支持一个简单易用的protection against cross site request forgeries,当提交一个启用CSRF防护的POST表单时,必须使用{ % csrf_token %}模板标签
<!--{{ form }}--> #django会根据模型类的字段和属性,在HTML中自动生成对应表单标签和标签属性,生成的标签会放置在{{ form }}位置
<!--<input type="submit" value="Submit">-->
<!--</form>-->
{% load bootstrap3 %}
<html>使用bootstrap样式展示
<head>
<link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" >
<script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
</head>
<div class="container">
<form method="post" class="form">
{% csrf_token %}
添加</h1>
{% bootstrap_form form %}
{% buttons %}
<button type="submit" class="btn btn-primary">
{% bootstrap_icon "star" %} Submit
</button>
{% endbuttons %}
</form>
</div>
</html>
(webproject) C:\webproject\mysite>python manage.py makemigrations
(webproject) C:\webproject\mysite>python manage.py migrate
mysite/books/urls.py
from django.conf.urls import url
from . import views
app_name = 'books'
urlpatterns = [
url(r'^$', views.publisher_add, name='index'),
]
mysite/mysite/urls.py
urlpatterns = [
url(r'^books/', include('books.urls')),
url(r'^admin/', admin.site.urls),
]
(webproject) C:\webproject\mysite>python manage.py runserver
例3,手动渲染字段:
自己对展示的表单字段进行先后排序;
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)
{{ 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>
form field:
https://docs.djangoproject.com/en/2.1/ref/forms/fields/
BooleanField
CharField
ChoiceField
DateField
DateTimeField
DecimalField
EmailField #实际是CharField的子类,只不过是在其基础上加了些验证
FileField
FilePathField
FloatField #py float
ImageField
IntergerField #py int
form field args:
required
label
label_suffix
widget
help_text
error_messages