目錄
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>
{{ field.errors }}
{{ field.label_tag }} {{ field }}
{% if field.help_text %}
<p>{{ 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 }},字段的值,如[email protected];
{{ 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('[email protected]')
>>> 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>
<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>
<form method="post">
{% csrf_token %}
<h1>添加</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>
{{ form.subject.errors }}
<label for="{{ form.subject.id_for_label }}">Email subject:</label>
{{ form.subject }}
</div>
<div>
{{ form.message.errors }}
<label for="{{ form.message.id_for_label }}">Your message:</label>
{{ form.message }}
</div>
<div>
{{ form.sender.errors }}
<label for="{{ form.sender.id_for_label }}">Your email address:</label>
{{ form.sender }}
</div>
<div>
{{ 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