13.Django1.11.6文檔

第一步 入門

檢查版本css

python -m django --version

建立第一個項目html

django-admin startproject mysite

運行前端

 python manage.py runserver

更改端口python

python manage.py runserver 8080

更改IPmysql

python manage.py runserver 0:8000

1.建立app

建立投票應用jquery

python manage.py startapp polls

polls/views.pygit

from django.shortcuts import render

from django.http import HttpResponse


def index(request):
    return HttpResponse("Hello, world. You're at the polls index.")

在polls裏面建立一個urls.py文件,代碼以下程序員

from django.conf.urls import url
from . import views

urlpatterns = [
    url(r'^$',views.index,name='index'),
]

在mysite/urls.py中添加includegithub

from django.conf.urls import url,include
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'polls/',include('polls.urls')),
]

訪問:http://127.0.0.1:8000/polls/,就能夠看到信息「Hello, world. You're at the polls index.」web

2.建立模型

設置爲Mysql數據庫

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'django',        #數據庫名字
        'USER': 'root',          #帳號
        'PASSWORD': '123456',      #密碼
        'HOST': '127.0.0.1',    #IP
        'PORT': '3306',                   #端口
    }
}

polls/init.py

import pymysql
pymysql.install_as_MySQLdb()

polls/models.py

from django.db import models


class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')

    def __str__(self):
        return self.question_text


class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)
    
    def __str__(self):
        return self.choice_text

而後執行下面的命令

python manage.py makemigrations      建立遷移文件

python manage.py migrate             更新到數據庫中。

 3.後臺管理

(1)建立管理員用戶,設置用戶名,郵箱,密碼

python manage.py createsuperuser

登陸:http://127.0.0.1:8000/admin/

(2)把polls應用添加到後臺管理站點

編輯polls/admin.py

from django.contrib import admin

from polls.models import Question

admin.site.register(Question)

刷新就能夠看到polls

4.視圖

(1)polls/urls.py

from django.conf.urls import url

from . import views

urlpatterns = [
    # ex: /polls/
    url(r'^$', views.index, name='index'),
    # ex: /polls/5/
    url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
    # ex: /polls/5/results/
    url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'),
    # ex: /polls/5/vote/
    url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
]

(2)建立polls/templates/polls/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    {% if latest_question_list %}
    <ul>
    {% for question in latest_question_list %}
        <li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
    {% endfor %}
    </ul>
    {% else %}
        <p>No polls are available.</p>
    {% endif %}
</body>
</html>

(3)polls/views.py

from django.shortcuts import render,HttpResponse
from polls.models import Question


def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    context = {'latest_question_list': latest_question_list,}
    return render(request,'polls/index.html',context)

def detail(request, question_id):
    return HttpResponse("You're looking at question %s." % question_id)

def results(request, question_id):
    response = "You're looking at the results of question %s."
    return HttpResponse(response % question_id)

def vote(request, question_id):
    return HttpResponse("You're voting on question %s." % question_id)

(4)拋出404異常

from django.shortcuts import render,HttpResponse,Http404

def detail(request, question_id):
    try:
        question = Question.objects.get(pk=question_id)
    except Question.DoesNotExist:
        raise Http404('Question does not exist')
    return render(request,'polls/detail.html',{'question':question})

detail.html

{{ question }}

(5)快捷方式  get_object_or_404

 更改detail()視圖以下

from django.shortcuts import get_object_or_404

def detail(request, question_id):
    question = get_object_or_404(Question,pk=question_id)
    return render(request,'polls/detail.html',{'question':question})

get_object_or_404() 函數將一個Django模型做爲它的第一個參數,任意數量的關鍵字參數做爲它的第二個參數,它會將這些關鍵字參數傳遞給模型管理器中的get() 函數。 若是對象不存在,它就引起一個 Http404異常。

 還有一個get_list_or_404()函數,它的工做方式相似get_object_or_404() —— 差異在於它使用filter()而不是get()若是列表爲空則引起Http404

 (6)使用模板系統

編輯detail.html

 <h1>{{ question.question_text }}</h1>
    <ul>
        {% for choice in question.choice_set.all %}
            <li>{{ choice.choice_text }}</li>
        {% endfor %}
    </ul>

方法調用發生在{% for %}循環中:Choice被解釋爲Python的代碼question.choice_set.all,它返回一個由question.choice_set.all()對象組成的可迭代對象,並將其用於{% for %}標籤。

最終:

http://127.0.0.1:8000/polls/   訪問index頁面,顯示question列表

點任意一個擊question,而後跳轉到detail頁面

(7)移除模板中硬編碼的URL

前面在index.html中跳轉到detail頁面的編碼

<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>

這種硬編碼、緊耦合的方法有一個問題,就是若是咱們想在擁有許多模板文件的項目中修改URLs,那將會變得頗有挑戰性。 然而,由於你在polls.urls模塊的url()函數中定義了name 參數,你能夠經過使用{% url %}模板標籤來移除對你的URL配置中定義的特定的URL的依賴:修改以下

<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>

若是你想把polls應用中detail視圖的URL改爲其它樣子好比polls/specifics/12/,就能夠沒必要在該模板(或者多個模板)中修改它,只須要修改polls/urls.py

url(r'^specifics/(?P<question_id>[0-9]+)/$', views.detail, name='detail'),

(8)命名空間URL名稱

在真實的Django項目中,可能會有五個、十個、二十個或者更多的應用。 Django如何區分它們URL的名字呢?

答案是添加命名空間到你的URLconf。 polls/urls.py文件中,繼續添加app_name來設置應用程序命名空間:

添加 app_name='polls'

from django.conf.urls import url
from . import views
 app_name = 'polls'

urlpatterns = [
    url(r'^$', views.index, name='index'),
    # ex: /polls/5/
    url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
    # ex: /polls/5/results/
    url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'),
    # ex: /polls/5/vote/
    url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
]

而後修改polls/index.html

<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>

改成

<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>

 5.表單

(1)更新一下polls/details.html

<h1>{{ question.question_text }}</h1>

{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}

<form action="{% url 'polls:vote' question.id %}" method="post">
{% 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>

簡要說明:

在detail網頁模板中,咱們爲Question對應的每一個Choice都添加了一個單選按鈕用於選擇。每一個單選按鈕的value屬性是對應的各個Choice的ID。 每一個單選按鈕的name"choice"。 這意味着,當有人選擇一個單選按鈕並提交表單提交時,它將發送一個POST數據choice=#,其中# 爲選擇的Choice的ID

因爲咱們建立一個POST表單(它具備修改數據的做用),因此咱們須要當心跨站點請求僞造。簡而言之,全部針對內部URL的POST表單都應該使用{% csrf_token %}模板標籤。

(2)vote.py處理投票

如今,咱們來建立一個處理提交的數據的Django視圖,並用它來處理

from django.shortcuts import get_object_or_404, render
from django.http import HttpResponseRedirect, HttpResponse
from django.urls import reverse

from .models import Choice, Question
# ...
def vote(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    try:
        selected_choice = question.choice_set.get(pk=request.POST['choice'])
    except (KeyError, Choice.DoesNotExist):
        # 從新顯示該問題的表單
        return render(request, 'polls/detail.html', {
            'question': question,
            'error_message': "You didn't select a choice.",
        })
    else:
        selected_choice.votes += 1
        selected_choice.save()
        # 始終在成功處理 POST 數據後返回一個 HttpResponseRedirect ,
        # (合併上句) 這樣能夠防止用戶點擊「後退」按鈕時數據被髮送兩次。
        # (合併至上一句)
        return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))

這個例子中,request.POST['choice']以字符串形式返回選擇的Choice的ID。 request.POST 的值永遠是字符串。

在增長Choice的得票數以後,代碼返回一個 HttpResponseRedirect而不是經常使用的HttpResponse。 HttpResponseRedirect只接收一個參數:用戶將要被重定向的URL

咱們在HttpResponseRedirect的構造函數中使用reverse()函數。 這個函數避免了咱們在視圖函數中硬編碼URL。 它須要咱們給出咱們想要跳轉的視圖的名字和該視圖所對應的URL模式中須要給該視圖提供的參數。重定向的URL將調用'results'視圖來顯示最終的頁面。

(3)results()

當有人對Question進行投票後,vote()視圖將請求重定向到Question的結果界面。

def results(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, 'polls/results.html', {'question': question})

(4)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>    #pluralize複數
{% endfor %}
</ul>

<a href="{% url 'polls:detail' question.id %}">Vote again?</a>

如今,在你的瀏覽器中訪問/polls/1/而後爲Question投票。 你應該看到一個投票結果頁面,而且在你每次投票以後都會更新。 若是你提交時沒有選擇任何Choice,你應該看到錯誤信息。

 6.靜態文件

(1)建立style.css   

-->>目錄 polls/static/polls/style.css

添加下面代碼到style.css

li a {
    color: green;
}

在index.html添加以下

{% load static %}

<link rel="stylesheet" type="text/css" href="{% static 'polls/style.css' %}" />

{% static %}模板標籤生成靜態文件的絕對URL。

再訪問index界面,會發現question連接是綠色的

(2)添加背景圖片

polls/static/polls/目錄中建立一個 images 子目錄。在這個目錄中,放入一張圖片background.gif-->>(polls/static/polls/images/background.gif)

添加樣式

body {
    background: white url("images/background.gif") no-repeat right bottom;
}

7.自定義管理後臺的表單

(1)從新排序編輯表單上的字段

from django.contrib import admin

from polls.models import Question


class QuestionAdmin(admin.ModelAdmin):
    fields = ['pub_date','question_text']

admin.site.register(Question,QuestionAdmin)

任什麼時候候你須要更改模型的管理選項,你將遵循此模式 — 建立一個模型管理類,而後將其做爲第二個參數傳遞給admin.site.register()

(2)添加關聯對象

在建立Question對象的同時能夠直接添加一組Choice

from django.contrib import admin

from .models import Choice, Question


class ChoiceInline(admin.StackedInline):
    model = Choice
    extra = 3


class QuestionAdmin(admin.ModelAdmin):
    fieldsets = [
        (None,               {'fields': ['question_text']}),
        ('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}),
    ]
    inlines = [ChoiceInline]

admin.site.register(Question, QuestionAdmin)

這告訴Django:「Choice對象在Question的管理界面中編輯。 默認提供足夠3個Choice的空間。

打開「Add question」頁面:

它這樣工做:有三個所關聯的Choice —— 由extra指定 —— 每次你回到已經存在對象的"Change"頁面時,都會額外地得到三個空白Choice。

 在現有的三個Choice的底部,你會發現一個「Add another Choice」的連接。 若是你點擊它,就會增長一個新的空白Choice。

 還有個小問題。 顯示全部關聯的Choice 對象的字段佔用大量的屏幕空間。 爲此,Django提供了一種顯示內聯相關對象的表格方式;你只需將ChoiceInline聲明更改成:

class ChoiceInline(admin.TabularInline):
    model = Choice
    extra = 3

使用 TabularInline(而不是StackedInline),這些相關聯的對象顯示成緊湊的、基於表格的形式:

(3)自定義管理變動清單

class QuestionAdmin(admin.ModelAdmin):
    list_display = ('question_text','pub_date')

(4)使用list_filter來添加過濾器

class QuestionAdmin(admin.ModelAdmin):
    list_display = ('question_text','pub_date')
    list_filter = ['pub_date']

(5)添加搜索功能

class QuestionAdmin(admin.ModelAdmin):
    list_display = ('question_text','pub_date')
    list_filter = ['pub_date']
    search_fields = ['question_text']

 

 8.定製管理後臺的外觀

很明顯,每一個管理頁面的頂部都有「Django administration」不太合適。它能夠用Django的模板系統輕鬆改變。 Django的管理站點是用Django本身製做出來的,它的界面代碼使用的是Django本身的模板系統。

定製項目的模板

在你項目的文件夾內(包含 manage.py的目錄)建立一個templates目錄。 打開你的配置文件(記住是mysite/settings.py)在TEMPLATES 設置中添加一個DIRS 選項:

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

如今,在templates下建立一個名爲admin的文件夾,而後從Django安裝的原目錄下(目錄爲django/contrib/admin/templates)將模板頁面的源文件admin/base_site.html拷貝到這個文件夾裏。

C:\Users\Administrator\AppData\Local\Programs\Python\Python36\Lib\site-packages\django\contrib\admin\templates\admin
個人文件位置

 若是不知道Django源文件路徑,運行以下命令

$ python -c "import django; print(django.__path__)"

而後,只需編輯該文件並替換{{ site_header|default:_('Django administration') }} (包括花括號)爲你認爲合適的本身站點的名稱。 編輯完成後應該相似下面的代碼片斷:

{% extends "admin/base.html" %}

{% block title %}{{ title }} | {{ site_title|default:_('Django site admin') }}{% endblock %}

{% block branding %}
<h1 id="site-name"><a href="{% url 'admin:index' %}">{{ site_header|default:_('Django administration') }}</a></h1>
{% endblock %}

{% block nav-global %}{% endblock %}
默認的base.html
{% extends "admin/base.html" %}

{% block title %}{{ title }} | Polls Administration{% endblock %}

{% block branding %}
<h1 id="site-name"><a href="{% url 'admin:index' %}">Polls Administration</a></h1>
{% endblock %}

{% block nav-global %}{% endblock %}

改變以後效果:

 模型層

 1.字段選項

 null

若是爲True,Django將在數據庫中把空值存儲爲NULL。 默認爲False

blank

若是爲True,該字段容許爲空值, 默認爲False

   要注意,這與 null 不一樣。 null純粹是數據庫範疇,指數據庫中字段內容是否容許爲空,而 blank 是表單數據輸入驗證範疇的。 若是一個字段的blank=True,表單的驗證將允              許輸入一個空值。 若是字段的blank=False,該字段就是必填的。

 choices

由二項元組構成的一個可迭代對象(例如,列表或元組),用來給字段提供選擇項。 若是設置了choices ,默認的表單將是一個選擇框而不是標準的文本框,並且這個選擇框的選項就是choices 中的選項。

這是一個關於 choices 列表的例子:

每一個元組中的第一個元素是將被存儲在數據庫中的值。 第二個元素將由默認窗體小部件或ModelChoiceField顯示。 給定一個模型實例,能夠使用get_FOO_display()方法來訪問選項字段的顯示值。 例如:

from django.db import models

class Person(models.Model):
    SHIRT_SIZES = (
        ('S', 'Small'),
        ('M', 'Medium'),
        ('L', 'Large'),
    )
    name = models.CharField(max_length=60)
    shirt_size = models.CharField(max_length=1, choices=SHIRT_SIZES)
>>> p = Person(name="Fred Flintstone", shirt_size="L")
>>> p.save()
>>> p.shirt_size
'L'
>>> p.get_shirt_size_display()
'Large'

default

字段的默認值。 能夠是一個值或者可調用對象。 若是可調用 ,每一個新對象建立時它都會被調用。

help_text

表單部件額外顯示的幫助內容。 即便字段不在表單中使用,它對生成文檔也頗有用。

primary_key

若是爲True,那麼這個字段就是模型的主鍵。

unique

若是爲True, 則這個字段在整張表中必須是惟一的。

2.字段的自述名

ForeignKeyManyToManyField 和 OneToOneField 以外,每一個字段類型都接受一個可選的位置參數(在第一的位置) — 字段的自述名。 若是沒有給定自述名,Django 將根據字段的屬性名稱自動建立自述名 —— 將屬性名稱的下劃線替換成空格。

 

在這個例子中,自述名是 "person's first name":

first_name = models.CharField("person's first name", max_length=30)

在這個例子中,自述名是 "first name"

first_name = models.CharField(max_length=30)

ForeignKeyManyToManyField 和 OneToOneField 都要求第一個參數是一個模型類,因此要使用 verbose_name 關鍵字參數才能指定自述名:

poll = models.ForeignKey(
    Poll,
    on_delete=models.CASCADE,
    verbose_name="the related poll",
)
sites = models.ManyToManyField(Site, verbose_name="list of sites")
place = models.OneToOneField(
    Place,
    on_delete=models.CASCADE,
    verbose_name="related place",
)

習慣上,verbose_name 的首字母不用大寫。 Django 在必要的時候會自動大寫首字母。

 模型繼承

在Django 中有3種風格的繼承。

  1. 一般,你只想使用父類來持有一些信息,你不想在每一個子模型中都敲一遍。 這個類永遠不會單獨使用,因此你要使用抽象的基類
  2. 若是你繼承一個已經存在的模型且想讓每一個模型具備它本身的數據庫表,那麼應該使用多表繼承
  3. 最後,若是你只是想改變一個模塊Python 級別的行爲,而不用修改模型的字段,你能夠使用代理模型

3.抽象基類

當你想將一些共有信息放進其它一些model的時候,抽象化類是十分有用的。 你編寫完基類以後,在 Meta類中設置 abstract=True這個模型就不會被用來建立任何數據表。 取而代之的是,當它被用來做爲一個其餘model的基類時,它的字段將被加入那些子類中。 若是抽象基類和它的子類有相同的字段名,那麼將會出現error(而且Django將拋出一個exception)。

一個例子:

from django.db import models

class ConmonInfo(models.Model):
    name = models.CharField(max_length=100)
    age = models.PositiveIntegerField()

    class Meta:
        abstract = True

class Student(ConmonInfo):
    home_group = models.CharField(max_length=5)

Student 模型將有三個字段:nameagehome_groupCommonInfo 模型沒法像通常的Django模型同樣使用,由於它是一個抽象基類。 它沒法生成一張數據表或者擁有一個管理器,而且不能實例化或者直接儲存。

4.Meta繼承

當一個抽象基類被建立的時候, Django把你在基類內部定義的 Meta 類做爲一個屬性使其可用。 若是子類沒有聲明本身的Meta類, 它將會繼承父類的Meta若是子類想要擴展父類的Meta類,它能夠子類化它。 例如:

from django.db import models

class ConmonInfo(models.Model):
    name = models.CharField(max_length=100)
    age = models.PositiveIntegerField()

    class Meta:
        abstract = True
        ordering = ['name']

class Student(ConmonInfo):
    home_group = models.CharField(max_length=5)

    class Meta(ConmonInfo.Meta):
        db_table = 'student_info'  #更改表名

5.多表繼承

這是 Django 支持的第二種繼承方式。使用這種繼承方式時,每個層級下的每一個 model 都是一個真正意義上完整的 model 。 每一個 model 都有專屬的數據表,均可以查詢和建立數據表。 繼承關係在子 model 和它的每一個父類之間都添加一個連接 (經過一個自動建立的 OneToOneField來實現)。 例如:

from django.db import models

class Place(models.Model):
    name = models.CharField(max_length=50)
    address = models.CharField(max_length=80)

class Restaurant(Place):
    serves_hot_dogs = models.BooleanField(default=False)
    serves_pizza = models.BooleanField(default=False)

Place裏面的全部字段在 Restaurant中也是有效的,只不過沒有保存在數據庫中的Restaurant表中。 因此下面兩個語句都是能夠運行的:

>>> Place.objects.filter(name="Bob's Cafe")
>>> Restaurant.objects.filter(name="Bob's Cafe")

6.代理模型

使用 多表繼承時,model 的每一個子類都會建立一張新數據表, 一般狀況下,這正是咱們想要的操做。這是由於子類須要一個空間來存儲不包含在基類中的字段數據。 但有時,你可能只想更改 model 在 Python 層的行爲實現。好比:更改默認的 manager ,或是添加一個新方法。

而這,正是代理繼承要作的:爲原始模型建立一個代理你能夠建立,刪除,更新代理 model 的實例,並且全部的數據均可以像使用原始 model 同樣被保存。 不一樣之處在於:你能夠在代理 model 中改變默認的排序設置和默認的 manager ,更不會對原始 model 產生影響。

聲明代理 model 和聲明普通 model 沒有什麼不一樣。 設置Meta類中 proxy 的值爲 True,就完成了對代理 model 的聲明。

舉個例子,假設你想給 Person 模型添加一個方法。 你能夠這樣作:

from django.db import models

class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)

class MyPerson(Person):
    class Meta:
        proxy = True

    def do_something(self):
        # ...
        pass

Person類和它的父類 Person 操做同一個數據表。 特別的是,Person 的任何實例也能夠經過 MyPerson訪問,反之亦然:

>>> p = Person.objects.create(first_name="foobar")
>>> MyPerson.objects.get(first_name="foobar")

 7.執行查詢

一個博客應用

from django.db import models

class Blog(models.Model):
    name = models.CharField(max_length=100)
    tagline = models.TextField()

    def __str__(self):              # __unicode__ on Python 2
        return self.name

class Author(models.Model):
    name = models.CharField(max_length=200)
    email = models.EmailField()

    def __str__(self):              # __unicode__ on Python 2
        return self.name

class Entry(models.Model):
    blog = models.ForeignKey(Blog)
    headline = models.CharField(max_length=255)
    body_text = models.TextField()
    pub_date = models.DateField()
    mod_date = models.DateField()
    authors = models.ManyToManyField(Author)
    n_comments = models.IntegerField()
    n_pingbacks = models.IntegerField()
    rating = models.IntegerField()

    def __str__(self):              # __unicode__ on Python 2
        return self.headline

(1)使用過濾器檢索特定對象

all() 方法返回了一個包含數據庫表中全部記錄QuerySet但在一般狀況下,你每每想要獲取的是完整數據集的一個子集。

要建立這樣一個子集,你須要在原始的的QuerySet上增長一些過濾條件。 QuerySet兩個最廣泛的途徑是:

filter(**kwargs)
返回一個新的 QuerySet,它包含知足查詢參數的對象。
exclude(**kwargs)
返回一個新的 QuerySet,它包含 知足查詢參數的對象。

每次你篩選一個QuerySet,獲得的都是全新的另外一個QuerySet,它和以前的QuerySet之間沒有任何綁定關係。 每次篩選都會建立一個獨立的QuerySet,它能夠被存儲及反覆使用。

QuerySets 是惰性執行的 —— 建立QuerySet不會帶來任何數據庫的訪問。 你能夠將過濾器保持一成天,直到QuerySet 須要求值時,Django 纔會真正運行這個查詢。 

(2)使用get()檢索單個對象

filter() 始終給你一個QuerySet,即便只有一個對象知足查詢條件 —— 這種狀況下,QuerySet將只包含一個元素。

若是你知道只有一個對象知足你的查詢,你能夠使用Managerget() 方法,它直接返回該對象:

>>> one_entry = Entry.objects.get(pk=1)

能夠對get() 使用任何查詢表達式,和filter() 同樣 —— 一樣請查看下文的字段查詢

注意,使用get() 和使用filter() 的切片[0] 有一點區別。 若是沒有結果知足查詢,get() 將引起一個DoesNotExist 異常。 這個異常是正在查詢的模型類的一個屬性 —— 因此在上面的代碼中,若是沒有主鍵(pk) 爲1 的Entry對象,Django 將引起一個Entry.DoesNotExist

相似地,若是有多條記錄知足get() 的查詢條件,Django 也將報錯。 這種狀況將引起MultipleObjectsReturned,它一樣是模型類自身的一個屬性。

(3)限制QuerySet

能夠使用Python 的切片語法來限制QuerySet記錄的數目 。 它等同於SQL 的OFFSETLIMIT 子句。

例如,下面的語句返回前面5 個對象(LIMIT 5):

>>> Entry.objects.all()[:5]

下面這條語句返回第6 至第10 個對象(OFFSET 5 LIMIT 5):

>>> Entry.objects.all()[5:10]

不支持負的索引(例如Entry.objects.all()[-1])。

(3)字段查找

>>> Entry.objects.filter(pub_date__lte='2006-01-01')       # <=

查詢條件中指定的字段必須是模型字段的名稱。 但有一個例外,對於ForeignKey你能夠使用字段名加上_id 後綴。 在這種狀況下,該參數的值應該是外鍵的原始值。 像這樣:

>>> Entry.objects.filter(blog_id=4)
iexact

大小寫不敏感的匹配。 因此,查詢:

>>> Blog.objects.get(name__iexact="beatles blog")

將匹配標題爲"Beatles Blog""beatles blog" 甚至"BeAtlES blOG"Blog

contains

大小寫敏感的包含關係測試。 像這樣:

Entry.objects.get(headline__contains='Lennon')
startswithendswith
分別表示以XXX開頭和以XXX結尾。 固然還有大小寫不敏感的版本,叫作istartswithiendswith

(4)跨關聯關係的查詢

Django 提供一種強大而又直觀的方式來「處理」查詢中的關聯關係,它在後臺自動幫你處理JOIN若要跨越關聯關係,只需使用關聯的模型字段的名稱,並使用雙下劃線分隔,直至你想要的字段:

下面這個例子獲取全部Blogname'Beatles Blog'Entry 對象:

>>> Entry.objects.filter(blog__name='Beatles Blog')

這種跨越能夠是任意的深度。

它還能夠反向工做。 若要引用一個「反向」的關係,只須要使用該模型的小寫的名稱。

下面的示例獲取全部的Blog 對象,它們至少有一個Entryheadline包含'Lennon'

>>> Blog.objects.filter(entry__headline__contains='Lennon')

選擇全部包含同時知足兩個條件的entry的blog,這兩個條件是headline 包含Lennon 和發表時間是2008 (同一個entry 知足兩個條件),咱們的代碼是:

Blog.objects.filter(entry__headline__contains='Lennon', entry__pub_date__year=2008)

要選擇全部這樣的blog,有一個entry的headline包含「Lennon」有一個entry發表時間是2008,咱們將這樣編寫:

Blog.objects.filter(entry__headline__contains='Lennon').filter(entry__pub_date__year=2008)

(5)F()

若是你想將模型的一個字段與同一個模型的另一個字段進行比較該怎麼辦?

Django 提供F表達式 來容許這樣的比較。 F() 返回的實例用做查詢內部對模型字段的引用。 這些引用能夠用於查詢的filter 中來比較相同模型實例上不一樣字段之間值的比較。

例如,爲了查找comments 數目多於pingbacks 的Entry,咱們將構造一個F() 對象來引用pingback 數目,並在查詢中使用該F() 對象:

>>> from django.db.models import F
>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks'))   

Django 支持對F() 對象使用加法、減法、乘法、除法、取模以及冪計算等算術操做,兩個操做數能夠都是常數和其它F() 對象。 爲了查找comments 數目比pingbacks 兩倍還要多的Entry,咱們將查詢修改成:

>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks') * 2)

爲了查詢rating 比pingback 和comment 數目總和要小的Entry,咱們將這樣查詢:

>>> Entry.objects.filter(rating__lt=F('n_comments') + F('n_pingbacks'))

你還能夠在F() 對象中使用雙下劃線標記來跨越關聯關係。 帶有雙下劃線的F() 對象將引入任何須要的join 操做以訪問關聯的對象。 例如,如要獲取author 的名字與blog 名字相同的Entry,咱們能夠這樣查詢:

>>> Entry.objects.filter(authors__name=F('blog__name'))

對於date 和date/time 字段,你能夠給它們加上或減去一個timedelta 對象。 下面的例子將返回發佈超過3天后被修改的全部Entry:

>>> from datetime import timedelta
>>> Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))

(6)PK查找快捷方式

爲了方便,Django 提供一個查詢快捷方式pk ,它表示「primary key」 的意思。

在示例Blog模型中,主鍵pk是id字段,因此這三個語句是等價的:

>>> Blog.objects.get(id__exact=14) # Explicit form
>>> Blog.objects.get(id=14) # __exact is implied
>>> Blog.objects.get(pk=14) # pk implies id__exact

(7)緩存和QuerySet

每一個QuerySet都包含一個緩存來最小化對數據庫的訪問。 理解它是如何工做的將讓你編寫最高效的代碼。

在一個新建立的QuerySet中,緩存爲空。 首次對QuerySet進行求值 —— 同時發生數據庫查詢 ——Django 將保存查詢的結果到QuerySet的緩存中並返回明確請求的結果(例如,若是正在迭代QuerySet,則返回下一個結果)。 接下來對該QuerySet 的求值將重用緩存的結果。

請牢記這個緩存行爲,由於對QuerySet使用不當的話,它會坑你的。 例如,下面的語句建立兩個QuerySet,對它們求值,而後扔掉它們:

>>> print([e.headline for e in Entry.objects.all()])
>>> print([e.pub_date for e in Entry.objects.all()])

這意味着相同的數據庫查詢將執行兩次,顯然倍增了你的數據庫負載。 同時,還有可能兩個結果列表並不包含相同的數據庫記錄,由於在兩次請求期間有可能有Entry被添加進來或刪除掉。

爲了不這個問題,只需保存QuerySet並從新使用它:

>>> queryset = Entry.objects.all()
>>> print([p.headline for p in queryset]) # Evaluate the query set.
>>> print([p.pub_date for p in queryset]) # Re-use the cache from the evaluation.

(8)使用Q對象進行復雜查找

filter()中的關鍵字參數查詢 — — 是「AND」的關係。 若是你須要執行更復雜的查詢(例如OR 語句),你能夠使用Q對象

Q object (django.db.models.Q) 對象用於封裝一組關鍵字參數。 這些關鍵字參數就是上文「字段查詢」 中所說起的那些。

例如,下面的LIKE 對象封裝一個Q 查詢:

from django.db.models import Q
Q(question__startswith='What')

Q對象能夠使用&|操做符組合起來。 當一個操做符在兩個Q 對象上使用時,它產生一個新的Q 對象。

例如,下面的語句產生一個"question__startswith" 對象,表示兩個Q 查詢的「OR」 :

Q(question__startswith='Who') | Q(question__startswith='What')

它等同於下面的SQL WHERE 子句:

WHERE question LIKE 'Who%' OR question LIKE 'What%'

你能夠組合&| 操做符以及使用括號進行分組來編寫任意複雜的Q 對象。 同時,~ 對象能夠使用NOT 操做符取反,這容許組合正常的查詢和取反(Q) 查詢:

Q(question__startswith='Who') | ~Q(pub_date__year=2005)

每一個接受關鍵字參數的查詢函數(例如filter()exclude()get())均可以傳遞一個或多個Q 對象做爲位置(不帶名的)參數。 若是一個查詢函數有多個Q 對象參數,這些參數的邏輯關係爲「AND"。 像這樣:

Poll.objects.get(
    Q(question__startswith='Who'),
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))
)

查詢函數能夠混合使用Q和關鍵字參數。 全部提供給查詢函數的參數(關鍵字參數或Q 對象)都將"AND」在一塊兒。 可是,若是出現Q 對象,它必須位於全部關鍵字參數的前面。 像這樣:

Poll.objects.get(
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
    question__startswith='Who',
)

錯誤例子:

# INVALID QUERY
Poll.objects.get(
    question__startswith='Who',
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))
)

(9)反向查詢

若是模型有一個ForeignKey,那麼該ForeignKey所指的模型實例能夠經過一個Manager返回第一個模型的全部實例。 默認狀況下,這個Manager的名字爲FOO_set,其中FOO是源模型的小寫名稱。 Manager返回QuerySets,能夠用上一節提到的方式進行過濾和操做。

例如:

>>> b = Blog.objects.get(id=1)
>>> b.entry_set.all() # Returns all Entry objects related to Blog.

# b.entry_set is a Manager that returns QuerySets.
>>> b.entry_set.filter(headline__contains='Lennon')
>>> b.entry_set.count()

你能夠在ForeignKey 定義時設置related_name 參數來覆蓋FOO_set 的名稱。 例如,若是Entry模型更改成blog = ForeignKey(Blog, on_delete=models.CASCADE, related_name='entries'),上述示例代碼以下所示:

>>> b = Blog.objects.get(id=1)
>>> b.entries.all() # Returns all Entry objects related to Blog.

# b.entries is a Manager that returns QuerySets.
>>> b.entries.filter(headline__contains='Lennon')
>>> b.entries.count()

(10)多對多關係

多對多關係的兩端都會自動得到訪問另外一端的API。 這些API 的工做方式與上面提到的「方向」一對多關係同樣。

惟一的區別在於屬性的命名:定義 ManyToManyField 的模型使用該字段的屬性名稱,而「反向」模型使用源模型的小寫名稱加上'_set' (和一對多關係同樣)。

一個例子可讓它更好理解:

e = Entry.objects.get(id=3)
e.authors.all() # Returns all Author objects for this Entry.
e.authors.count()
e.authors.filter(name__contains='John')

a = Author.objects.get(id=5)
a.entry_set.all() # Returns all Entry objects for this Author.

(11)一對一關係

對一關係與多對一關係很是類似。 若是你在模型中定義一個OneToOneField,該模型的實例將能夠經過該模型的一個簡單屬性訪問關聯的模型。

像這樣:

class EntryDetail(models.Model):
    entry = models.OneToOneField(Entry, on_delete=models.CASCADE)
    details = models.TextField()

ed = EntryDetail.objects.get(id=2)
ed.entry # Returns the related Entry object.

在「反向」查詢中有所不一樣。 一對一關係中的關聯模型一樣具備一個Manager對象,可是該Manager表示一個單一的對象而不是對象的集合:

e = Entry.objects.get(id=2)
e.entrydetail # returns the related EntryDetail object

反向關聯的關係是如何實現的

其它對象關係映射要求你在關聯關係的兩端都要定義。 Django 的開發人員相信這是對DRY(不要重複你本身的代碼)原則的違背,因此Django 只要求你在一端定義關聯關係。

可是這怎麼可能?由於一個模型類直到其它模型類被加載以後才知道哪些模型類是關聯的。

答案在app registry 中。 當Django 啓動時,它導入INSTALLED_APPS 中列出的每一個應用,而後導入每一個應用中的models 模塊。 每建立一個新的模型時,Django 添加反向的關係到全部關聯的模型。 若是關聯的模型尚未導入,Django 將保存關聯關係的記錄並在最終關聯的模型導入時添加這些關聯關係。

因爲這個緣由,你使用的全部模型都定義在INSTALLED_APPS 列出的應用中就顯得特別重要。 不然,反向的關聯關係將不能正確工做。

 8.聚合

Django抽象的數據庫API描述使用Django查詢來增刪查改單個對象的方法。 然而,有時候你須要獲取的值須要根據一組對象聚合後才能獲得。 這份指南描述經過Django 查詢來生成和返回聚合值的方法。

整篇指南咱們都將引用如下模型。 這些模型用來記錄多個網上書店的庫存。

from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=100)
    age = models.IntegerField()

class Publisher(models.Model):
    name = models.CharField(max_length=300)
    num_awards = models.IntegerField()

class Book(models.Model):
    name = models.CharField(max_length=300)
    pages = models.IntegerField()
    price = models.DecimalField(max_digits=10, decimal_places=2)
    rating = models.FloatField()
    authors = models.ManyToManyField(Author)
    publisher = models.ForeignKey(Publisher)
    pubdate = models.DateField()

class Store(models.Model):
    name = models.CharField(max_length=300)
    books = models.ManyToManyField(Book)
    registered_users = models.PositiveIntegerField()

如下是在上述模型的基礎上,進行通常的聚合查詢的方法:

# Total number of books.
>>> Book.objects.count()
2452

# Total number of books with publisher=BaloneyPress
>>> Book.objects.filter(publisher__name='BaloneyPress').count()
73

# Average price across all books.
>>> from django.db.models import Avg
>>> Book.objects.all().aggregate(Avg('price'))
{'price__avg': 34.35}

# Max price across all books.
>>> from django.db.models import Max
>>> Book.objects.all().aggregate(Max('price'))
{'price__max': Decimal('81.20')}

# Difference between the highest priced book and the average price of all books.
>>> from django.db.models import FloatField
>>> Book.objects.aggregate(
...     price_diff=Max('price', output_field=FloatField()) - Avg('price')))
{'price_diff': 46.85}

# All the following queries involve traversing the Book<->Publisher
# foreign key relationship backwards.

# Each publisher, each with a count of books as a "num_books" attribute.
>>> from django.db.models import Count
>>> pubs = Publisher.objects.annotate(num_books=Count('book'))
>>> pubs
<QuerySet [<Publisher: BaloneyPress>, <Publisher: SalamiPress>, ...]>
>>> pubs[0].num_books
73

# The top 5 publishers, in order by number of books.
>>> pubs = Publisher.objects.annotate(num_books=Count('book')).order_by('-num_books')[:5]
>>> pubs[0].num_books
1323

(1)經過QuerySet生成聚合

QuerySet.對象上計算出總價格。 這能夠經過在aggregate()後面附加QuerySet 子句來完成。

>>> Book.objects.aggregate(Avg('price'))
{'price__avg': 34.35}

aggregate()QuerySet 的一個終止子句,意思是說,它返回一個包含一些鍵值對的字典。 該名稱是總值的標識符;該值是計算的聚合。 鍵的名稱是按照字段和聚合函數的名稱自動生成出來的。 若是你想要爲聚合值指定一個名稱,能夠向聚合子句提供它。

>>> Book.objects.aggregate(average_price=Avg('price'))
{'average_price': 34.35}

若是你但願生成不止一個聚合,你能夠向aggregate()子句中添加另外一個參數。 因此,若是你也想知道全部圖書價格的最大值和最小值,能夠這樣查詢:

>>> from django.db.models import Avg, Max, Min
>>> Book.objects.aggregate(Avg('price'), Max('price'), Min('price'))
{'price__avg': 34.35, 'price__max': Decimal('81.20'), 'price__min': Decimal('12.99')}

(2)位QuerySet中每一個項目生成聚合

生成彙總值的第二種方法,是爲QuerySet中每個對象都生成一個獨立的彙總值。 好比,若是你在檢索一列圖書,你可能想知道每一本書有多少做者參與。 每本書與做者有多對多的關係;咱們想在QuerySet中總結每本書的這種關係。

逐個對象的彙總結果能夠由annotate()子句生成。 annotate()子句被指定以後,QuerySet中的每一個對象都會被註上特定的值。

這些註解的語法都和aggregate()子句所使用的相同。 annotate()的每一個參數都描述了將要被計算的聚合。 好比,給圖書添加做者數量的註解:

# Build an annotated queryset
>>> from django.db.models import Count
>>> q = Book.objects.annotate(Count('authors'))
# Interrogate the first object in the queryset
>>> q[0]
<Book: The Definitive Guide to Django>
>>> q[0].authors__count
2
# Interrogate the second object in the queryset
>>> q[1]
<Book: Practical Django Projects>
>>> q[1].authors__count
1

和使用 aggregate()同樣,註解的名稱也根據聚合函數的名稱和聚合字段的名稱獲得的。 你能夠在指定註解時,爲默認名稱提供一個別名:

>>> q = Book.objects.annotate(num_authors=Count('authors'))
>>> q[0].num_authors
2
>>> q[1].num_authors
1

aggregate() 不一樣的是, annotate() 不是一個終止子句。 annotate()子句的輸出是一個QuerySet;能夠使用任何其餘QuerySet操做修改QuerySet,包括filter()order_by()或甚至附加調用annotate()

要查找每一個商店提供的圖書的價格範圍,您能夠使用註釋:

>>> from django.db.models import Max, Min
>>> Store.objects.annotate(min_price=Min('books__price'), max_price=Max('books__price'))

這段代碼告訴 Django 獲取Store模型,並鏈接(經過多對多關係)Book模型,而後對每本書的價格進行聚合,得出最小值和最大值。

一樣的規則也用於 aggregate() 子句。 若是您想知道任何商店中可出售的任何圖書的最低價格和最高價格,您能夠使用匯總:

>>> Store.objects.aggregate(min_price=Min('books__price'), max_price=Max('books__price'))

關係鏈能夠按你的要求一直延伸。 例如,想獲得全部做者當中最小的年齡是多少,就能夠這樣寫:

>>> Store.objects.aggregate(youngest_age=Min('books__authors__age'))

(3)反向

在你所查詢的模型的關聯模型或者字段上的聚合和註解能夠遍歷"反轉"關係。 關聯模型的小寫名稱和雙下劃線也用在這裏。

例如,咱們能夠查詢全部出版商,並註上它們一共出了多少本書(注意咱們如何用 Publisher指定Book -> 'book' 的外鍵反轉關係):

查詢全部圖書中最舊的那本:

>> Publisher.objects.aggregate(oldest_pubdate=Min('book__pubdate'))

這不只僅能夠應用掛在外鍵上面。 還能夠用到多對多關係上。 例如,咱們能夠查詢每一個做者,註上它寫的全部書(以及合著的書)一共有多少頁(注意咱們如何使用 Author來指定Book -> 'book'的多對多的反轉關係):

>>> Author.objects.annotate(total_pages=Sum('book__pages'))

查詢全部圖書的平均評分,這些圖書由咱們存檔過的做者所寫:

>>> Author.objects.aggregate(average_rating=Avg('book__rating'))

使用annotate() 子句時,過濾器有限制註解對象的做用。 例如,你想獲得每本以 "Django" 爲書名開頭的圖書做者的總數:

>>> from django.db.models import Count, Avg
>>> Book.objects.filter(name__startswith="Django").annotate(num_authors=Count('authors'))

使用aggregate()子句時,過濾器有限制聚合對象的做用。 例如,你能夠算出全部以 "Django" 爲書名開頭的圖書平均價格:

>>> Book.objects.filter(name__startswith="Django").aggregate(Avg('price'))

(4)過濾註釋

註解值也能夠被過濾。 像使用其餘模型字段同樣,註解也能夠在filter()exclude() 子句中使用別名。

例如,要獲得不止一個做者的圖書,能夠用:

>>> Book.objects.annotate(num_authors=Count('authors')).filter(num_authors__gt=1)

這個查詢首先生成一個註解結果,而後再生成一個做用於註解上的過濾器。

(5)order_by()

註解能夠用來作爲排序項。 在你定義 order_by() 子句時,你提供的聚合能夠引用定義的任何別名作爲查詢中 annotate()子句的一部分。

例如,根據一本圖書做者數量的多少對查詢集 QuerySet進行排序:

>>> Book.objects.annotate(num_authors=Count('authors')).order_by('num_authors')

(6)values()

一般,註解會添加到每一個對象上 —— 一個被註解的QuerySet會爲初始QuerySet的每一個對象返回一個結果集。 可是,若是使用了values()子句,它就會限制結果中列的範圍,對註解賦值的方法就會徹底不一樣。 不是在原始的 QuerySet返回結果中對每一個對象中添加註解,而是根據定義在values() 子句中的字段組合先對結果進行惟一的分組, 而後爲每一個惟一組提供註釋;在組的全部成員上計算註釋。

>>> Author.objects.annotate(average_rating=Avg('book__rating')).values('name', 'average_rating')

9.返回新的QuerySet 方法

Django 提供了一系列 的QuerySet篩選方法,用於改變 QuerySet 返回的結果類型或者SQL查詢執行的方式。

filter()

filter(**kwargs)

返回一個新的QuerySet,它包含知足查詢參數的對象

exclude()

exclude(**kwargs)

返回一個新的QuerySet,它包含知足給定的查找參數的對象。

下面的示例排除全部pub_date 晚於2005-1-3 且headline 爲「Hello」 的記錄:

Entry.objects.exclude(pub_date__gt=datetime.date(2005, 1, 3), headline='Hello')

annotate()

annotate(*args, **kwargs)

例如,若是你正在操做一個Blog列表,你可能想知道每一個Blog有多少Entry:

>>> from django.db.models import Count
>>> q = Blog.objects.annotate(Count('entry'))
# The name of the first blog
>>> q[0].name
'Blogasaurus'
# The number of entries on the first blog
>>> q[0].entry__count
42

order_by()

order_by(*fields)

默認狀況下,Meta 根據模型ordering 類的QuerySet 選項排序。 你能夠使用QuerySet 方法給每一個order_by 指定特定的排序。

例如:

Entry.objects.filter(pub_date__year=2005).order_by('-pub_date', 'headline')

上面的結果將按照pub_date 降序排序,而後再按照headline 升序排序。 "-pub_date"前面的負號表示降序順序。 升序是隱含的。

reverse()

reverse()

reverse() 方法反向排序QuerySet 中返回的元素。 第二次調用reverse() 將恢復到原有的排序。

如要獲取QuerySet 中最後五個元素,你能夠這樣作:

my_queryset.reverse()[:5]

注意,這與Python 中從一個序列的末尾進行切片有點不同。 上面的例子將首先返回最後一個元素,而後是倒數第二個元素,以此類推。 若是咱們有一個Python 序列,當咱們查看seq[-5:] 時,咱們將一會兒獲得倒數五個元素。 Django 不支持這種訪問模型(從末尾進行切片),由於它不可能利用SQL 高效地實現。

values()

values(*fields, **expressions)

返回一個返回字典的QuerySet,而不是使用模型實例做爲一個迭代。

每一個字典表示一個對象,鍵對應於模型對象的屬性名稱。

下面的例子將values() 與普通的模型對象進行比較:

# This list contains a Blog object.
>>> Blog.objects.filter(name__startswith='Beatles')
<QuerySet [<Blog: Beatles Blog>]>

# This list contains a dictionary.
>>> Blog.objects.filter(name__startswith='Beatles').values()
<QuerySet [{'id': 1, 'name': 'Beatles Blog', 'tagline': 'All the latest Beatles news.'}]>

SELECT 接收可選的位置參數*fields,它指定values() 應該限制哪些字段。 若是指定字段,每一個字典將只包含指定的字段的鍵/值。 若是沒有指定字段,每一個字典將包含數據庫表中全部字段的鍵和值。

例如:

>>> Blog.objects.values()
<QuerySet [{'id': 1, 'name': 'Beatles Blog', 'tagline': 'All the latest Beatles news.'}]>
>>> Blog.objects.values('id', 'name')
<QuerySet [{'id': 1, 'name': 'Beatles Blog'}]>

values_list()

values_list(*fields, flat=False)

values() 相似,只是在迭代時返回的是元組而不是字典。 每一個元組包含傳遞給values_list()調用的相應字段或表達式的值,所以第一個項目是第一個字段等。 像這樣:

>>> Entry.objects.values_list('id', 'headline')
<QuerySet [(1, 'First entry'), ...]>
>>> from django.db.models.functions import Lower
>>> Entry.objects.values_list('id', Lower('headline'))
<QuerySet [(1, 'first entry'), ...]>

select_related()

返回一個QuerySet,當執行它的查詢時它沿着外鍵關係查詢關聯的對象的數據。 它會生成一個複雜的查詢並引發性能的損耗,可是在之後使用外鍵關係時將不須要數據庫查詢。

defer()

defer(*fields)

在一些複雜的數據建模狀況下,你的模型可能包含大量字段,其中一些可能包含大量數據(例如文本字段),或者須要昂貴的處理來將它們轉換爲Python對象。 當你最初獲取數據時不知道是否須要這些特定字段的狀況下,若是你正在使用查詢集的結果,你能夠告訴Django不要從數據庫中檢索它們。

它經過傳遞字段名稱到defer()實現不加載:

# 延遲body和headline兩個字段。
Entry.objects.defer("body").filter(rating=5).defer("headline")

若是要清除延遲字段集,請將None做爲參數傳遞到defer()

# 當即加載全部的字段。
my_queryset.defer(None)

only()

only(*fields)

only()方法或多或少與defer()相反。 你以應該在檢索模型時延遲的字段調用它。 若是你有一個模型幾乎全部的字段須要延遲,使用only()指定補充的字段集能夠致使更簡單的代碼。

10.不返回QuerySet的方法

get()

get(**kwargs)

返回按照查詢參數匹配到的對象

 

count()

count()

返回在數據庫中對應的 QuerySet.對象的個數。 count() 永遠不會引起異常。

in_bulk()

in_bulk(id_list=None)

獲取主鍵值的列表,並返回將每一個主鍵值映射到具備給定ID的對象的實例的字典。 若是未提供列表,則會返回查詢集中的全部對象。

例如:

>>> Blog.objects.in_bulk([1])
{1: <Blog: Beatles Blog>}
>>> Blog.objects.in_bulk([1, 2])
{1: <Blog: Beatles Blog>, 2: <Blog: Cheddar Talk>}
>>> Blog.objects.in_bulk([])
{}
>>> Blog.objects.in_bulk()
{1: <Blog: Beatles Blog>, 2: <Blog: Cheddar Talk>, 3: <Blog: Django Weblog>}

latest()

latest(field_name=None)

使用做爲日期字段提供的field_name,按日期返回表中的最新對象。

此示例根據Entry字段返回表中的最新pub_date

Entry.objects.latest('pub_date')

first()

first()

返回結果集的第一個對象, 當沒有找到時返回None. 若是 QuerySet 沒有設置排序,則將會自動按主鍵進行排序

p = Article.objects.order_by('title', 'pub_date').first()

last()

last()

工做方式相似first(),只是返回的是查詢集中最後一個對象。

aggregate()

aggregate(*args, **kwargs)

返回彙總值的字典(平均值,總和等) 經過QuerySet進行計算。 aggregate() 的每一個參數指定返回的字典中將要包含的值。

exists()

exists()

若是QuerySet 包含任何結果,則返回True,不然返回False

 

11.Field查找

exact

精確匹配。

iexact

不區分大小寫的精確匹配

contains

大小寫敏感的包含關係測試。

例如:

Entry.objects.get(headline__contains='Lennon')

icontains

測試是否包含,不區分大小寫。

in

在給定的列表。

例如:

Entry.objects.filter(id__in=[1, 3, 4])

gt

大於

例如:

Entry.objects.filter(id__gt=4)

gte

大於或等於

lt

小於

lte

小於或等於

startswith

區分大小寫,開始位置匹配

例如:

Entry.objects.filter(headline__startswith='Lennon')

istartswith

不區分大小寫,開始位置匹配

endswith

區分大小寫。

iendswith

不區分大小寫。

date

對於datetime字段,將值做爲日期轉換。 容許連接附加字段查找。 獲取日期值。

例如:

Entry.objects.filter(pub_date__date=datetime.date(2005, 1, 1))
Entry.objects.filter(pub_date__date__gt=datetime.date(2005, 1, 1))

year

對於日期和日期時間字段,確切的年匹配。 容許連接附加字段查找。 整數年。

Entry.objects.filter(pub_date__year=2005)
Entry.objects.filter(pub_date__year__gte=2005)

month

對於日期和日期時間字段,確切的月份匹配。

day

對於日期和日期時間字段,具體到某一天的匹配。

 12.管理器

你能夠在模型中使用自定義的Manager,方法是繼承Manager 基類並實例化你的自定義Manager

你有兩個緣由可能會本身定義Manager:向Manager類中添加額外的方法,或者修改Manager返回的原始QuerySet

(1)修改管理器的初始QuerySet

管理器自帶的QuerySet返回系統中全部的對象。 例如,使用下面這個模型:

from django.db import models

class Book(models.Model):
    title = models.CharField(max_length=100)
    author = models.CharField(max_length=50)

...語句Book.objects.all()將返回數據庫中的全部書籍。

你能夠經過重寫Manager.get_queryset()方法來覆蓋Manager自帶的QuerySetget_queryset()應該返回一個帶有你須要的屬性的QuerySet

例如,下面的模型有兩個Manager,一個返回全部的對象,另外一個則只返回做者是Roald Dahl 的對象:

...語句Book.objects.all()將返回數據庫中的全部書籍。

你能夠經過重寫Manager.get_queryset()方法來覆蓋Manager自帶的QuerySetget_queryset()應該返回一個帶有你須要的屬性的QuerySet

例如,下面的模型有兩個Manager,一個返回全部的對象,另外一個則只返回做者是Roald Dahl 的對象:

# 首先,定義管理器的子類。
class DahlBookManager(models.Manager):
    def get_queryset(self):
        return super(DahlBookManager, self).get_queryset().filter(author='Roald Dahl')

# 而後將它顯式地放入Book模型。
class Book(models.Model):
    title = models.CharField(max_length=100)
    author = models.CharField(max_length=50)

    objects = models.Manager() # 默認的管理器。
    dahl_objects = DahlBookManager() # 用於Dahl的管理器。

在這個簡單的例子中,Book.objects.all()將返回數據庫中全部的圖書,而Book.dahl_objects.all()只返回Roald Dahl寫做的圖書。

固然,由於get_queryset()返回QuerySet對象,你能夠使用filter()exclude()和全部其餘QuerySet方法。 因此下面這些例子都是可用的:

Book.dahl_objects.all()
Book.dahl_objects.filter(title='Matilda')
Book.dahl_objects.count()

該例還展現了另一個頗有意思的技巧:同一模型使用多個管理器。 你能夠依據你本身的偏好在一個模型裏面添加多個 Manager() 實例。 這是給模型添加通用過濾器(選擇器)的一個簡單方法:

像這樣:

class AuthorManager(models.Manager):
    def get_queryset(self):
        return super(AuthorManager, self).get_queryset().filter(role='A')

class EditorManager(models.Manager):
    def get_queryset(self):
        return super(EditorManager, self).get_queryset().filter(role='E')

class Person(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    role = models.CharField(max_length=1, choices=(('A', _('Author')), ('E', _('Editor'))))
    people = models.Manager()
    authors = AuthorManager()
    editors = EditorManager()

這個例子讓你能夠使用Person.authors.all()Person.editors.all()以及Person.people.all(),都會獲得預期的結果

(2)從管理器調用自定義QuerySet方法

雖然大多數標準QuerySet的方法能夠從Manager中直接訪問到,可是若是你須要將一些被定義到一個自定義QuerySet中的額外方法也在Manager上實現,下面所展現的這個例子是惟一可行辦法:

class PersonQuerySet(models.QuerySet):
    def authors(self):
        return self.filter(role='A')

    def editors(self):
        return self.filter(role='E')

class PersonManager(models.Manager):
    def get_queryset(self):
        return PersonQuerySet(self.model, using=self._db)

    def authors(self):
        return self.get_queryset().authors()

    def editors(self):
        return self.get_queryset().editors()

class Person(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    role = models.CharField(max_length=1, choices=(('A', _('Author')), ('E', _('Editor'))))
    people = PersonManager()

這個例子容許你直接從管理器Person.people中調用authors()editors()

13.Meta選項

可用的Meta選項

abstract

Options. abstract

若是 abstract True, 就表示模型是抽象基類

app_label

Options. app_label

若是該項目下有多個app,有一個model不是定義在本app下默認的model.py,而是在其餘app,也即它在本app settings的INSTALLED_APPS沒有聲明,則必須使用app_lable聲明其屬於哪一個app:

app_label = 'myapp'

若是要表示具備格式app_label.object_nameapp_label.model_name的模型,能夠使用model._meta.labelmodel._meta.label_lower

base_manager_name

Options. base_manager_name
Django中的新功能1.10。

模型中_base_manager所使用的manager的名稱(模型管理器的名稱)。

db_table

Options. db_table

該模型所用的數據表的名稱:

db_table = 'music_album'

Table names

爲了節省時間,Django 會自動的使用你的 model class 的名稱和包含這個 model 的 app 名稱來構建 數據庫的表名稱。 一個 model 的數據庫表名稱是經過將 「app label」 – 你在 manage.py startapp 中使用的名稱 – 和 model 的類名稱,加上一個下劃線在他們之間來構成。

舉個例子,bookstore應用(使用manage.py startapp bookstore 建立),以class Book定義的模型的數據表的名稱將是bookstore_book 。

使用 Meta類中的 db_table 參數來重寫數據表的名稱。

若是你的數據庫表名稱是SQL保留字,或包含Python變量名稱中不容許的字符,特別是連字符 — 沒有問題。 Django在後臺引用列和表名。

db_tablespace

Options. db_tablespace

當前模型所使用的database tablespace 的名字。 默認值是項目設置中的DEFAULT_TABLESPACE,若是它存在的話。 若是後端並不支持表空間,這個選項能夠忽略。

default_manager_name

Options. default_manager_name
Django中的新功能1.10。

模型的_default_manager用到的管理器的名稱。

from django.db import models

class Foo(models.Model):
    pass

class Bar(models.Model):
    foo = models.ForeignKey(Foo)

    class Meta:
        default_related_name = 'bars'
>>> bar = Bar.objects.get(pk=1)
>>> # 使用名稱"bar"做爲查詢名稱已棄用。
>>> Foo.objects.get(bar=bar)
>>> # 你應該使用default_related_name "bars"。
>>> Foo.objects.get(bars=bar)

get_latest_by

Options. get_latest_by

模型中某個可排序的字段的名稱,好比DateFieldDateTimeField或者IntegerField。 它指定了Managerlatest()earliest()中使用的默認字段。

例如:

get_latest_by = "order_date"

managed

Options. managed

默認爲True,表示Django會經過migrate建立合適的數據表,而且可經過flush管理命令移除這些數據庫表。 換句話說,Django會管理這些數據表的生命週期。

若是是False,Django 就不會爲當前模型建立和刪除數據表。 若是當前模型表示一個已經存在的且是經過其它方法建立的者數據表或數據庫視圖,這會至關有用。 這是設置爲managed=False惟一的不一樣之處。 模型處理的其它任何方面都和日常同樣。 這包括:

  1. 若是你不聲明它的話,會向你的模型中添加一個自增主鍵。 爲了不給後面的代碼讀者帶來混亂,當你在使用未被管理的模型時,強烈推薦你指定(specify)數據表中全部的列。

  2. 若是一個模型設置了managed=False且含有ManyToManyField,且這個多對多字段指向其餘一樣也是未被管理模型的,那麼這兩個未被管理的模型的多對多中介表也不會被建立。 可是,一個被管理模型和一個未被管理模型之間的中介表就會被建立。

    若是你須要修改這一默認行爲,建立中介表做爲顯式的模型(也要設置managed),而且使用ManyToManyField.through爲你的自定義模型建立關聯。

若是你進行測試,測試中涉及非託管 model (managed=False),那麼在測試以前,你應該要確保在 測試啓動時 已經建立了正確的數據表。

若是你對在Python層面修改模型類的行爲感興趣,你能夠設置 managed=False ,而且爲一個已經存在的模型建立一個副本。 不過在面對這種狀況時還有個更好的辦法就是使 用Proxy models.

order_with_respect_to

Options. order_with_respect_to

使此對象相對於給定字段能夠排序,一般爲ForeignKey。 這能夠用於使關聯的對象相對於父對象可排序。 好比,若是AnswerQuestion相關聯,一個問題有至少一個答案,而且答案的順序很是重要,你能夠這樣作:

from django.db import models

class Question(models.Model):
    text = models.TextField()
    # ...

class Answer(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    # ...

    class Meta:
        order_with_respect_to = 'question'

order_with_respect_to 設置以後,模型會提供兩個額外的用於設置和獲取關聯對象順序的方法:get_RELATED_order()set_RELATED_order(),其中RELATED是小寫的模型名稱。 例如,假設一個Question對象有不少相關聯的Answer對象,返回的列表中含有與之相關聯Answer對象的主鍵:

>>> question = Question.objects.get(id=1)
>>> question.get_answer_order()
[1, 2, 3]

Question對象相關聯的Answer對象的順序,能夠經過傳入一個包含Answer主鍵的列表來設置:

>>> question.set_answer_order([3, 1, 2])

相關聯的對象也有兩個方法, get_next_in_order() 和get_previous_in_order(),用於按照合適的順序訪問它們。 假設Answer對象按照 id來排序:

>>> answer = Answer.objects.get(id=2)
>>> answer.get_next_in_order()
<Answer: 3>
>>> answer.get_previous_in_order()
<Answer: 1>

ordering

Options. ordering

對象默認的順序,在獲取對象的列表時使用:

ordering = ['-order_date']

它是一個字符串的列表或元組。 每一個字符串是一個字段名,前面帶有可選的「-」前綴表示倒序。 前面沒有「-」的字段表示正序。 使用字符串「?」來隨機排序。

例如,要按照pub_date字段的正序排序,這樣寫:

ordering = ['pub_date']

按照pub_date字段的倒序排序,這樣寫:

ordering = ['-pub_date']

先按照pub_date的倒序排序,再按照 author 的正序排序,這樣寫:

ordering = ['-pub_date', 'author']

permissions

Options. permissions

設置建立對象時權限表中額外的權限。 增長、刪除和修改權限會自動爲每一個模型建立。 這個例子指定了一種額外的權限,can_deliver_pizzas

permissions = (("can_deliver_pizzas", "Can deliver pizzas"),)

它是一個包含二元組的元組或者列表,格式爲 (permission_code, human_readable_permission_name)

default_permissions

Options. default_permissions

默認爲('add', 'change', 'delete')。 你能夠自定義這個列表,好比,若是你的應用不須要默認權限中的任何一項,能夠把它設置成空列表。 在模型被migrate命令建立以前,這個屬性必須被指定,以防一些遺漏的屬性被建立。

proxy

Options. proxy

若是proxy True, 它做爲另外一個模型的子類,將會做爲一個proxy model

required_db_features

Options. required_db_features

當前鏈接應具備的數據庫功能列表,以便在遷移階段考慮該模型。 例如,若是將此列表設置爲['gis_enabled'],則模型將僅在啓用GIS的數據庫上同步。 在使用多個數據庫後端進行測試時,跳過某些模型也頗有用。 避免與ORM無關的模型之間的關係。

required_db_vendor

Options. required_db_vendor

此型號特定於受支持的數據庫供應商的名稱。 當前內置的供應商名稱是:sqlitepostgresqlmysqloracle。 若是此屬性不爲空,而且當前鏈接供應商不匹配,則該模型將不會同步。

select_on_save

Options. select_on_save

該選項決定Django是否採用1.6以前的django.db.models.Model.save()算法。 舊的算法使用SELECT來判斷是否存在須要更新的行。 而新的算法直接嘗試使用UPDATE。 在某些少見的狀況下,一個已存在行的UPDATE操做對Django不可見。 一個例子是PostgreSQL的返回NULLON UPDATE觸發器。 這種狀況下,新式的算法最終會執行INSERT操做,即便這一行已經在數據庫中存在。

一般這個屬性不須要設置。 默認爲False

關於舊式和新式兩種算法,請參見django.db.models.Model.save()

indexes

Options. indexes
Django中的新功能1.11。

要在模型上定義的索引的列表:

from django.db import models

class Customer(models.Model):
    first_name = models.CharField(max_length=100)
    last_name = models.CharField(max_length=100)

    class Meta:
        indexes = [
            models.Index(fields=['last_name', 'first_name']),
            models.Index(fields=['first_name'], name='first_name_idx'),
        ]

unique_together

Options. unique_together

用來設置的不重複的字段組合:

unique_together = (("driver", "restaurant"),)

它是一個元組的元組,組合起來的時候必須是惟一的。 它在Django admin層面使用,在數據庫層上進行數據約束(好比,在 CREATE TABLE 語句中包含 UNIQUE語句)。

爲了方便起見,處理單一字段的集合時,unique_together 能夠是一維的元組:

unique_together = ("driver", "restaurant")

ManyToManyField不能包含在unique_together中。 (不清楚它的含義是什麼!) 若是你須要驗證ManyToManyField關聯的惟一性,試着使用信號或者顯式的through模型。

unique_together的約束被違反時,模型校驗期間會拋出ValidationError異常。

index_together

Options. index_together
index_together = [
    ["pub_date", "deadline"],
]

列表中的字段將會創建索引(例如,會在CREATE INDEX語句中被使用)。

爲了方便起見,當須要處理的字段的集合只有一個的時候(集合只有一個!),index_together能夠只用一箇中括號。也就是隻用一個一維列表。

index_together = ["pub_date", "deadline"]

verbose_name

Options. verbose_name

對象的一個易於理解的名稱,爲單數:

verbose_name = "pizza"

若是此項沒有設置,Django會把類名拆分開來做爲自述名,好比CamelCase 會變成camel case

verbose_name_plural

Options. verbose_name_plural

該對象複數形式的名稱:

verbose_name_plural = "stories"

若是此項沒有設置,Django 會使用 verbose_name + "s"

只讀的Meta屬性

label

Options. label

對象的表示,返回app_label.object_name,例如'polls.Question'

label_lower

Options. label_lower

模型的表示,返回app_label.model_name,例如'polls.question'

視圖層

1.URL配置

當一個用戶請求Django 站點的一個頁面,下面是Django 系統決定執行哪一個Python 代碼遵循的算法:

  1. Django 決定要使用的根URLconf 模塊。 一般,這是ROOT_URLCONF設置的值,可是若是傳入的HttpRequest對象具備urlconf屬性(由中間件設置),則其值將被用於代替ROOT_URLCONF設置。
  2. Django 加載該Python 模塊並尋找可用的urlpatterns它是django.conf.urls.url() 實例的一個Python 列表。
  3. Django 依次匹配每一個URL 模式,在與請求的URL 匹配的第一個模式停下來。
  4. 一旦正則表達式匹配,Django將導入並調用給定的視圖,該視圖是一個簡單的Python函數(或基於類的class-based view)。 視圖將得到以下參數:
    • 一個HttpRequest 實例。
    • 若是匹配的正則表達式返回了沒有命名的組,那麼正則表達式匹配的內容將做爲位置參數提供給視圖。
    • 關鍵字參數由正則表達式匹配的命名組組成,可是能夠被django.conf.urls.url()的可選參數kwargs覆蓋。
  5. 若是沒有匹配到正則表達式,或者若是過程當中拋出一個異常,Django 將調用一個適當的錯誤處理視圖。 請參見下面的錯誤處理

(1)示例

下面是一個簡單的 URLconf:

from django.conf.urls import url

from . import views

urlpatterns = [
    url(r'^articles/2003/$', views.special_case_2003),
    url(r'^articles/([0-9]{4})/$', views.year_archive),
    url(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive),                  # 
    url(r'^articles/([0-9]{4})/([0-9]{2})/([0-9]+)/$', views.article_detail),        #  
]/articles/2005/03//articles/2003/03/03/

注:

  • 若要從URL 中捕獲一個值,只須要在它周圍放置一對圓括號
  • 不須要添加一個前導的反斜槓,由於每一個URL 都有。 例如,應該是^articles 而不是 ^/articles
  • 每一個正則表達式前面的'r' 是可選的可是建議加上。 它告訴Python 這個字符串是「原始的」 —— 字符串中任何字符都不該該轉義。 

一些請求的例子:

  • /articles/2005/03/ 請求將匹配列表中的第三個模式。 Django 將調用函數views.month_archive(request, '2005', '03')
  • /articles/2005/3/ 不匹配任何URL 模式,由於列表中的第三個模式要求月份應該是兩個數字。
  • /articles/2003/ 將匹配列表中的第一個模式不是第二個,由於模式按順序匹配,第一個會首先測試是否匹配。 請像這樣自由插入一些特殊的狀況來探測匹配的次序。 這裏,Django會調用函數views.special_case_2003(request)
  • /articles/2003 不匹配任何一個模式,由於每一個模式要求URL 以一個斜線結尾。
  • /articles/2003/03/03/ 將匹配最後一個模式。 Django 將調用函數views.article_detail(request, '2003', '03', '03')

(2)命名組

上面的示例使用簡單的、沒有命名的正則表達式組(經過圓括號)來捕獲URL 中的值並以位置 參數傳遞給視圖。 在更高級的用法中,能夠使用命名的正則表達式組來捕獲URL 中的值並以關鍵字 參數傳遞給視圖。

在Python 正則表達式中,命名正則表達式組的語法是(?P<name>pattern),其中name 是組的名稱,pattern 是要匹配的模式。

下面是以上URLconf 使用命名組的重寫:

from django.conf.urls import url

from . import views

urlpatterns = [
    url(r'^articles/2003/$', views.special_case_2003),
    url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
    url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive),
    url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2})/$', views.article_detail),
]

這個實現與前面的示例徹底相同,只有一個細微的差異:捕獲的值做爲關鍵字參數而不是位置參數傳遞給視圖函數。 像這樣:

  • /articles/2005/03/ 請求將調用views.month_archive(request, year='2005', month='03')函數,而不是views.month_archive(request, '2005', '03')
  • /articles/2003/03/03/ 請求將調用函數views.article_detail(request, year='2003', month='03', day='03')

在實際應用中,這意味你的URLconf 會更加明晰且不容易產生參數順序問題的錯誤 —— 你能夠在你的視圖函數定義中從新安排參數的順序。 固然,這些好處是以簡潔爲代價的;一些開發人員發現命名組語法醜陋並且太冗長。

捕獲的參數老是字符串

 

每一個捕獲的參數都做爲一個普通的Python 字符串傳遞給視圖,不管正則表達式使用的是什麼匹配方式。 例如,下面這行URLconf 中:

url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive)
...傳遞給 views.year_archive()year參數將是一個字符串,
不是整數,即便 [0-9]{4}只匹配整數字符串。

(3)傳遞額外的參數來查看函數

URLconfs 具備一個鉤子,讓你傳遞一個Python 字典做爲額外的參數傳遞給視圖函數。

django.conf.urls.url() 函數能夠接收一個可選的第三個參數,它是一個字典,表示想要傳遞給視圖函數的額外關鍵字參數。

像這樣:

from django.conf.urls import url
from . import views

urlpatterns = [
    url(r'^blog/(?P<year>[0-9]{4})/$', views.year_archive, {'foo': 'bar'}),
]

在這個例子中,對於/blog/2005/請求,Django 將調用views.year_archive(request, year='2005', foo='bar')

(4)URL的反向解析

在 Django 項目中常常須要獲取最終形式的 URL,這麼作是爲了在生成的內容中嵌入 URL(視圖和素材資源網址,呈現給用戶的網址,等等), 或者用於在服務器端處理導航流程(重定向等)

此時,必定不能硬編碼 URL(費時、不可伸縮,並且容易出錯), 或者參照 URL 配置創造一種生成 URL 的機制,由於這樣很是容易致使線上 URL 失效。

換句話講,咱們須要的是一個 DRY 機制。 這種機制的一個優勢是,當改進 URL 設計以後無需在項目源碼中大範圍搜索、替換失效的 URL。

咱們能夠得到URL的主要信息是負責處理URL的視圖的標識(例如名稱)。 必須參與正確URL查找的其餘信息片斷是視圖參數的類型(位置,關鍵字)和值。

Django 提供了一種方案,只需在 URL 映射中設計 URL。 咱們爲其提供 URL 配置,而後就能夠雙向使用:

  • 根據用戶/瀏覽器發起的URL 請求,它調用正確的Django 視圖,並從URL 中提取它的參數須要的值。
  • 根據Django 視圖的標識和將要傳遞給它的參數的值,獲取與之關聯的URL。

第一種方式是咱們在前面的章節中一直討論的用法。 第二種方式叫作反向解析URL反向URL匹配反向URL查詢或者簡單的URL反查

在須要URL 的地方,對於不一樣層級,Django 提供不一樣的工具用於URL 反查:

  • 在模板中:使用url 模板標籤。
  • 在Python代碼中:使用reverse()函數。
  • 在更高層的與處理Django 模型實例相關的代碼中:使用get_absolute_url() 方法。

2.視圖函數

一個視圖函數,簡稱視圖,是一個簡單的Python 函數,它接受Web請求而且返回Web響應。 此響應能夠是網頁的HTML內容,重定向,404錯誤,XML文檔或圖像。 . . 或任何東西,真的。 不管視圖自己包含什麼邏輯,都要返回響應。 代碼寫在哪裏也無所謂,只要它在你的Python目錄下面。 除此以外沒有更多的要求了——能夠說「沒有什麼神奇的地方」。 爲了將代碼放在某處,約定是將視圖放置在項目或應用程序目錄中的名爲views.py的文件中。

(1)Django的快捷函數

render()

render(request, template_name, context=None, content_type=None, status=None, using=None)[source]

結合一個給定的模板和一個給定的上下文字典,並返回一個渲染後的 HttpResponse 對象。

Django 不提供返回TemplateResponse 的快捷函數,由於TemplateResponse 的構造與render() 提供的便利是一個層次的。

必需參數

request
該request用於生成response
template_name
要使用的模板的完整名稱或者模板名稱的一個序列。 若是給出的是一個序列,將使用存在的第一個模板。 關於如何查找模板的更多信息請參見 template loading documentation

可選參數

context
添加到模板上下文的一個字典。 默認是一個空字典。 若是字典中的某個值是可調用的,視圖將在渲染模板以前調用它。
content_type
用於生成的文檔的MIME類型。 默認爲DEFAULT_CONTENT_TYPE設置的值。
status
響應的狀態代碼。 默認爲200
using
用於加載模板使用的模板引擎的 NAME

render_to_response()

render_to_response(template_name, context=None, content_type=None, status=None, using=None)[source]

此功能在引入render()以前進行,除了不能使request可用於響應以外,它的工做方式相似。 不推薦,之後可能會被棄用

redirect()

redirect(to, permanent=False, *args, **kwargs)[source]

爲傳遞進來的參數返回HttpResponseRedirect 給正確的URL 。

參數能夠是:

  • 一個模型:將調用模型的get_absolute_url() 函數
  • 視圖名稱,可能帶有參數:reverse()將用於反向解析名稱。
  • 一個絕對的或相對的URL,將原封不動的做爲重定向的位置。

默認狀況下會發出臨時重定向;經過permanent=True發出永久重定向。

get_object_or_404()

get_object_or_404(klass, *args, **kwargs)[source]

在一個給定的模型管理器上調用get(),可是引起Http404 而不是模型的DoesNotExist 異常。

必需參數

klass
獲取該對象的一個 Model 類, ManagerQuerySet 實例。
**kwargs
查詢的參數,格式應該能夠被 get()filter()接受。

實例

下面的示例從MyModel 中使用主鍵1 來獲取對象:

from django.shortcuts import get_object_or_404

def my_view(request):
    my_object = get_object_or_404(MyModel, pk=1)

 2.基於類的視圖

基於類的視圖使用Python 對象實現視圖,它提供除函數視圖以外的另一種方式。 它們不替換基於函數的視圖,但與基於函數的視圖相比具備必定的區別和優點:

  • 組織與特定HTTP方法相關的代碼(GETPOST等) 能夠經過單獨的方法而不是條件分支來解決。
  • 面嚮對象的技術例如Mixin(多繼承)能夠將代碼分解成可重用的組件。

基於類的視圖的核心是容許你用不一樣的實例方法來響應不一樣的HTTP 請求方法,而不是在一個視圖函數中使用條件分支代碼來實現。

因此,視圖函數中處理HTTP GET 的代碼看上去將像:

from django.http import HttpResponse

def my_view(request):
    if request.method == 'GET':
        # <view logic>
        return HttpResponse('result')

在基於類的視圖中,它將變成:

from django.http import HttpResponse
from django.views import View

class MyView(View):
    def get(self, request):
        # <view logic>
        return HttpResponse('result')

由於Django的URL解析器但願將請求和關聯的參數發送到可調用函數,而不是類,基於類的視圖具備一個as_view()類方法,它返回一個能夠在請求時調用的函數到達與相關模式匹配的URL。 該函數建立一個類的實例並調用其dispatch()方法。 GET 查看請求是POST 仍是dispatch 等等,並將請求轉發給相應的方法,若是該方法沒有定義則引起HttpResponseNotAllowed

# urls.py
from django.conf.urls import url
from myapp.views import MyView

urlpatterns = [
    url(r'^about/$', MyView.as_view()),
]

值得注意的是,方法的返回值與基於函數的視圖的返回值徹底相同,即HttpResponse 的某種形式。 這表示在基於類的視圖中能夠使用http shortcutsTemplateResponse 對象。

雖然基於類的視圖的最小實現不須要任何類屬性來完成它的功能,可是在許多基於類的設計中類屬性很是重要,有兩種方式來設置類屬性。

第一種方式是Python 標準的方式,子類化並在子類中覆蓋屬性和方法。 因此,若是父類有一個greeting 屬性:

from django.http import HttpResponse
from django.views import View

class GreetingView(View):
    greeting = "Good Day"

    def get(self, request):
        return HttpResponse(self.greeting)

你能夠在子類中覆蓋它:

class MorningGreetingView(GreetingView):
    greeting = "Morning to ya"

另一種方式是在URLconf 中用as_view() 調用的關鍵字參數配置類的屬性:

urlpatterns = [
    url(r'^about/$', GreetingView.as_view(greeting="good day")),
]

3.Mixins的使用

Mixin 是多繼承的一種形式,其來自多個父類的行爲和屬性能夠組合在一塊兒。

例如,在通用的基於類的視圖中,有一個Mixin 叫作 TemplateResponseMixin,它的主要目的是定義render_to_response() 方法。 它與View 基類的組合是TemplateView 類,這個類能夠調度請求給正確的方法(TemplateResponseMixin 基類中定義的行爲),同時還具備一個render_to_response() 方法,該方法使用template_name 屬性來返回一個TemplateResponse 對象( View 中定義的行爲)。

Mixin 是重用多個類的代碼的一種極好的方法,可是它們須要一些代價。 代碼在Mixin 中越分散,子類將越難閱讀並知道它的行爲;若是你的繼承很深,將難以知道應該覆蓋哪個Mixin 的方法。

還要注意,只能繼承一個通用視圖 —— 也就是說,只能有一個父類繼承View,其它的父類必須是Mixin。 繼承多個繼承自View 的類 將不能像預期的那樣工做

一個最基本的用於處理表單的視圖函數多是這樣的:

from django.http import HttpResponseRedirect
from django.shortcuts import render

from .forms import MyForm

def myview(request):
    if request.method == "POST":
        form = MyForm(request.POST)
        if form.is_valid():
            # <process form cleaned data>
            return HttpResponseRedirect('/success/')
    else:
        form = MyForm(initial={'key': 'value'})

    return render(request, 'form_template.html', {'form': form})

相似的一個基於類的視圖看上去是這樣:

from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.views import View

from .forms import MyForm

class MyFormView(View):
    form_class = MyForm
    initial = {'key': 'value'}
    template_name = 'form_template.html'

    def get(self, request, *args, **kwargs):
        form = self.form_class(initial=self.initial)
        return render(request, self.template_name, {'form': form})

    def post(self, request, *args, **kwargs):
        form = self.form_class(request.POST)
        if form.is_valid():
            # <process form cleaned data>
            return HttpResponseRedirect('/success/')

        return render(request, self.template_name, {'form': form})

這是一個很是簡單的例子,但您能夠看到,您能夠選擇經過覆蓋任何類屬性來定製此視圖,例如。 form_class,經過URLconf配置,或子類化和覆蓋一個或多個方法(或二者都)!)。

更多-->>http://usyiyi.cn/translate/Django_111/topics/class-based-views/mixins.html

4.裝飾基於類的視圖

基於類的視圖的擴展不只僅侷限於使用Mixin。 你還能夠使用裝飾器。 因爲基於類的視圖不是函數,對它們的裝飾取決於你使用as_view() 仍是建立一個子類。

(1)在URLconf中進行裝飾

裝飾基於類的視圖的最簡單的方法是裝飾as_view() 方法的結果。 最方便的地方是URLconf 中部署視圖的位置:

from django.contrib.auth.decorators import login_required, permission_required
from django.views.generic import TemplateView

from .views import VoteView

urlpatterns = [
    url(r'^about/$', login_required(TemplateView.as_view(template_name="secret.html"))),
    url(r'^vote/$', permission_required('polls.can_vote')(VoteView.as_view())),
]

這個方法在每一個實例的基礎上運用裝飾器。 若是想讓視圖的每一個實例都被裝飾,你須要一種不一樣的方法。

(2)裝飾類

若要裝飾基於類的視圖的每一個實例,你須要裝飾類自己。 能夠將裝飾器運用到類的dispatch() 方法上來實現這點。

類的方法和獨立的函數不徹底相同,因此你不能夠直接將函數裝飾器運用到方法上 —— 你首先須要將它轉換成一個方法裝飾器。 method_decorator 裝飾器將函數裝飾器轉換成方法裝飾器,這樣它就能夠用於實例方法上。 像這樣:

from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.views.generic import TemplateView

class ProtectedView(TemplateView):
    template_name = 'secret.html'

    @method_decorator(login_required)
    def dispatch(self, *args, **kwargs):
        return super(ProtectedView, self).dispatch(*args, **kwargs)

或者,更簡潔的是,您能夠裝飾類,並將要裝飾的方法的名稱做爲關鍵字參數name傳遞:

@method_decorator(login_required, name='dispatch')
class ProtectedView(TemplateView):
    template_name = 'secret.html'

若是您在幾個地方使用了一組經常使用的裝飾器,您能夠定義一個列表或元組的裝飾器,並使用它,而不是屢次調用method_decorator()這兩個類是至關的:

decorators = [never_cache, login_required]

@method_decorator(decorators, name='dispatch')
class ProtectedView(TemplateView):
    template_name = 'secret.html'

@method_decorator(never_cache, name='dispatch')
@method_decorator(login_required, name='dispatch')
class ProtectedView(TemplateView):
    template_name = 'secret.html'

裝飾器將按照傳遞給裝飾器的順序處理請求。 在這個例子中,never_cache()將在login_required()以前處理請求。

5.中間件

中間件是一個鉤子框架,它們能夠介入Django 的請求和響應處理過程。 它是一個輕量級、底層的「插件」系統,用於在全局修改Django 的輸入或輸出。

每一箇中間件組件負責完成某個特定的功能。 例如,Django 包含的一箇中間件組件AuthenticationMiddleware ,它使用會話將用戶和請求關聯起來。

(1)編寫本身的中間件

一箇中間件工廠是一個可調用的,它採用一個get_response可調用並返回一箇中間件。 中間件是一個可調用的函數,它接受請求並返回響應,就像視圖同樣。

中間件能夠寫成一個以下所示的功能:

def simple_middleware(get_response):
    # 一次性配置和初始化。

    def middleware(request):
        # 在調用視圖(以及稍後的中間件)以前
        # 要爲每一個請求執行代碼。

        response = get_response(request)

        # 爲每一個請求/響應執行的代碼
        # 在調用視圖以後

        return response

    return middleware

或者它能夠寫成一個類,其實例是可調用的,以下所示:

class SimpleMiddleware(object):
    def __init__(self, get_response):
        self.get_response = get_response
        # 一次性配置和初始化。

    def __call__(self, request):
        # Code to be executed for each request before
        # the view (and later middleware) are called.

        response = self.get_response(request)

        # Code to be executed for each request/response after
        # the view is called.

        return response

__init__(get_response)

中間件工廠必須接受get_response參數。 您也能夠初始化中間件的全局狀態。 記住幾個注意事項:

  • Django只使用get_response參數初始化您的中間件,所以您不能將__init__()定義爲須要任何其餘參數。
  • 與每一個請求一次調用的__call__()方法不一樣,當Web服務器啓動時,__init__()僅被調用一次

(2)激活中間件

要激活中間件組件,請將其添加到Django設置中的MIDDLEWARE列表中。

MIDDLEWARE中,每一箇中間件組件由一個字符串表示:完整的Python路徑到中間件工廠的類或函數名稱。 例如,使用 django-admin startproject建立工程的時候生成的默認值:

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

(3)中間件順序和分層

在請求階段,在調用視圖以前,Django以MIDDLEWARE(自上而下)定義的順序應用中間件。

你能夠像洋蔥同樣想起來:每一箇中間件類都是一個「層」,它覆蓋了洋蔥核心的視圖。 若是請求經過洋蔥的全部層(每一個調用get_response將請求傳遞到下一層),一直到核心的視圖,響應將經過在每一層(以相反的順序)的路上退出。

若是其中一個層決定短路並返回響應而不調用其get_response,那麼該層(包括視圖)內的洋蔥層都不會看到請求或響應。 響應將只返回經過請求傳遞的相同的層。

(4)其它中間件鉤子

除了前面描述的基本請求/響應中間件模式,您還能夠向基於類的中間件添加三種其餘特殊方法:

process_view()

process_viewrequestview_funcview_argsview_kwargs

request是一個HttpRequest 對象。 view_func是 Django會調用的一個Python的函數。 (它是一個真實的函數對象,不是函數的字符名稱。) view_args是一個會被傳遞到視圖的位置參數列表,而view_kwargs 是一個會被傳遞到視圖的關鍵字參數字典。 view_argsview_kwargs 都不包括第一個視圖參數(request)。

process_view()會在Django 調用視圖以前被調用。

它應該返回一個None 或一個HttpResponse對象。 若是返回None,Django 將會繼續處理這個請求,執行其它的process_view() 中間件,而後調用對應的視圖。 若是它返回一個HttpResponse對象,Django不會打擾調用相應的視圖;它將應用響應中間件到HttpResponse並返回結果。

process_exception()

process_exceptionrequestexception

request是一個HttpRequest 對象。 Exception是一個被視圖中的方法拋出來的 exception對象。

當一個視圖拋出異常時,Django會調用process_exception()來處理。 None應該返回一個process_exception() 或者一個HttpResponse對象。 若是它返回一個HttpResponse對象,則將應用模板響應和響應中間件,並將生成的響應返回給瀏覽器。 不然,default exception handling開始。

再次提醒,在處理響應期間,中間件的執行順序是倒序執行的,這包括process_exception若是異常中間件返回響應,那麼中間件上面的中間件類的process_exception方法根本就不會被調用。

process_template_response()

process_template_response請求響應

request是一個HttpRequest 對象。 response是一個TemplateResponse對象(或等價的對象),由Django視圖或者中間件返回。

若是響應的實例有render()方法,process_template_response()在視圖恰好執行完畢以後被調用,這代表了它是一個TemplateResponse對象(或等價的對象)。

這個方法必須返回一個實現了render方法的響應對象。 它能夠修改給定的response.template_name對象,經過修改 responseresponse.context_data或者它能夠建立一個全新的 TemplateResponse或等價的對象。

你不須要顯式渲染響應 —— 一旦全部的模板響應中間件被調用,響應會自動被渲染。

在一個響應的處理期間,中間件以相反的順序運行,這包括process_template_response()

 模板層

 1.配置

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')]
        ,
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

BACKEND 是一個指向實現了Django模板後端API的模板引擎類的帶點的Python路徑。 內置的後端有 django.template.backends.django.DjangoTemplatesdjango.template.backends.jinja2.Jinja2

因爲絕大多數引擎都是從文件加載模板的,因此每種模板引擎都包含兩項通用設置:

  • DIRS 定義了一個目錄列表,模板引擎按列表順序搜索這些目錄以查找模板源文件。
  • APP_DIRS 告訴模板引擎是否應該進入每一個已安裝的應用中查找模板。 每種模板引擎後端都定義了一個慣用的名稱做爲應用內部存放模板的子目錄名稱。(譯者注:例如django爲它本身的模板引擎指定的是 ‘templates’ ,爲jinja2指定的名字是‘jinja2’)

特別的是,django容許你有多個模板引擎後臺實例,且每一個實例有不一樣的配置選項。 在這種狀況下你必須爲每一個配置指定一個惟一的NAME .

OPTIONS 中包含了具體的backend設置

2.模板語言

模板

模版是純文本文件。 它能夠生成任何基於文本的格式(HTML,XML,CSV等))。

模版包括在使用時會被值替換掉的 變量,和控制模版邏輯的 標籤

下面是一個小模版,它說明了一些基本的元素。 後面的文檔中會解釋每一個元素。

{% extends "base_generic.html" %}

{% block title %}{{ section.title }}{% endblock %}

{% block content %}
<h1>{{ section.title }}</h1>

{% for story in story_list %}
<h2>
  <a href="{{ story.get_absolute_url }}">
    {{ story.headline|upper }}
  </a>
</h2>
<p>{{ story.tease|truncatewords:"100" }}</p>
{% endfor %}
{% endblock %}

變量

變量看起來就像是這樣: {{ variable }}當模版引擎遇到一個變量,它將計算這個變量,而後用結果替換掉它自己。 變量的命名包括任何字母數字以及下劃線 ("_")的組合。 點(".") 也在會變量部分中出現,不過它有特殊的含義,咱們將在後面說明。 重要的, 變量名稱中不能有空格或標點符號。在前文的例子中, {{ section.title }}將被替換爲 title 對象的 section 屬性。

過濾器

您能夠經過使用 過濾器來改變變量的顯示。

過濾器看起來是這樣的:{{ name|lower }}這將在變量 {{ name }} 被過濾器 lower 過濾後再顯示它的值,該過濾器將文本轉換成小寫。 使用管道符號 (|)來應用過濾器。

過濾器能夠「連接」。一個過濾器的輸出應用於下一個過濾器。 {{ text|escape|linebreaks }} 就是一個經常使用的過濾器鏈,它編碼文本內容,而後把行打破轉成<p> 標籤。

一些過濾器帶有參數。 過濾器的參數看起來像是這樣: {{ bio|truncatewords:30 }}這將顯示 bio 變量的前30個詞。

過濾器參數包含空格的話,必須被引號包起來;例如,使用逗號和空格去鏈接一個列表中的元素,你須要使用 {{ list|join:", " }}

經常使用模板過濾器

默認

若是一個變量是false或者爲空,使用給定的默認值。 不然,使用變量的值。 像這樣:

{{ value|default:"nothing" }}

若是 value沒有被提供,或者爲空, 上面的例子將顯示「nothing」。

長度

返回值的長度。 它對字符串和列表都起做用。 像這樣:

{{ value|length }}

若是 value['a', 'b', 'c', 'd'],那麼輸出是 4

filesizeformat

格式化爲「人類可讀」文件大小(即'13 KB't4> MB''102 bytes'等)。 像這樣:

{{ value|filesizeformat }}

若是 value 是 123456789,輸出將會是 117.7 MB

更多-->>http://usyiyi.cn/translate/Django_111/ref/templates/builtins.html#ref-templates-builtins-filters

標籤

標籤看起來像是這樣的: {% tag %}標籤比變量複雜得多:有些用於在輸出中建立文本,有些用於控制循環或邏輯,有些用於加載外部信息到模板中供之後的變量使用。

一些標籤須要開始和結束標籤(即 {% 標籤 %} ... 標籤 內容 ... {% ENDTAG %})。

經常使用標籤,更多-->>http://usyiyi.cn/translate/Django_111/ref/templates/builtins.html#ref-templates-builtins-tags

對於循環數組中的每一個元素。 例如,顯示 athlete_list中提供的運動員列表:

for

<ul>
{% for athlete in athlete_list %}
    <li>{{ athlete.name }}</li>
{% endfor %}
</ul>
變量     描述
forloop.counter     循環的當前迭代(1索引)
forloop.counter0     循環的當前迭代(0索引)
forloop.revcounter     循環結束的迭代次數(1索引)
forloop.revcounter0     循環結束的迭代次數(0索引)
forloop.first     若是這是第一次經過循環,則爲真
forloop.last     若是這是最後一次循環,則爲真
forloop.parentloop     對於嵌套循環,這是圍繞當前循環的循環

if,elif和else

計算一個變量,而且當變量是「true」時,顯示塊中的內容:

{% if athlete_list %}
    Number of athletes: {{ athlete_list|length }}
{% elif athlete_in_locker_room_list %}
    Athletes should be out of the locker room soon!
{% else %}
    No athletes.
{% endif %}

在上面的例子中,若是 athlete_list 不是空的,運動員的數量將顯示爲 {{ athlete_list|length }} 的輸出。 不然,若是athlete_in_locker_room_list不爲空,將顯示「運動員應該出...」。 若是兩個列表都是空的,將顯示 「No athletes.」 。

block

block標籤能夠被子模板覆蓋. 

comment

{% comment %}{% endcomment %},之間的內容會被忽略,做爲註釋。 在第一個標籤能夠插入一個可選的記錄。 好比,當要註釋掉一些代碼時,能夠用此來記錄代碼被註釋掉的緣由。

例如:

<p>Rendered text with {{ pub_date|date:"c" }}</p>
{% comment "Optional note" %}
    <p>Commented out text with {{ create_date|date:"c" }}</p>
{% endcomment %}

comment標籤不能嵌套使用。

csrf_token

這個標籤用於跨站請求僞造保護

extends

表示當前模板繼承自一個父模板

註釋

要註釋模版中一行的部份內容,使用註釋語法 {# #}.

例如,這個模版將被渲染爲 'hello'

{# greeting #}hello

3.模板繼承

Django模版引擎中最強大也是最複雜的部分就是模版繼承了。 模版繼承可讓您建立一個基本的「骨架」模版,它包含您站點中的所有元素,而且能夠定義可以被子模版覆蓋的 blocks

經過從下面這個例子開始,能夠容易的理解模版繼承:

<!DOCTYPE html>
<html lang="en">
<head>
    <link rel="stylesheet" href="style.css" />
    <title>{% block title %}My amazing site{% endblock %}</title>
</head>

<body>
    <div id="sidebar">
        {% block sidebar %}
        <ul>
            <li><a href="/">Home</a></li>
            <li><a href="/blog/">Blog</a></li>
        </ul>
        {% endblock %}
    </div>

    <div id="content">
        {% block content %}{% endblock %}
    </div>
</body>
</html>

這個模版,咱們把它叫做 base.html, 它定義了一個能夠用於兩列排版頁面的簡單HTML骨架。 「子模版」的工做是用它們的內容填充空的blocks。

在這個例子中, block 標籤訂義了三個能夠被子模版內容填充的block。 block 告訴模版引擎: 子模版可能會覆蓋掉模版中的這些位置。

子模版可能看起來是這樣的:

{% extends "base.html" %}

{% block title %}My amazing blog{% endblock %}

{% block content %}
{% for entry in blog_entries %}
    <h2>{{ entry.title }}</h2>
    <p>{{ entry.body }}</p>
{% endfor %}
{% endblock %}

extends 標籤是這裏的關鍵。 它告訴模版引擎,這個模版「繼承」了另外一個模版。 當模版系統處理這個模版時,首先,它將定位父模版——在此例中,就是「base.html」。

那時,模版引擎將注意到 base.html 中的三個 block 標籤,並用子模版中的內容來替換這些block。

請注意,子模版並無定義 sidebar block,因此係統使用了父模版中的值。 父模版的 {% block %} 標籤中的內容老是被用做備選內容(fallback)。

您能夠根據須要使用多級繼承。 使用繼承的一個經常使用方式是相似下面的三級結構:

  • 建立一個 base.html 模版來控制您整個站點的主要視覺和體驗。
  • 爲您的站點的每個「分支」建立一個base_SECTIONNAME.html 模版。 例如, base_news.html, base_sports.html這些模版都繼承自 base.html ,而且包含了每部分特有的樣式和設計。
  • 爲每一種頁面類型建立獨立的模版,例如新聞內容或者博客文章。 這些模版繼承對應分支的模版。

這種方式使代碼獲得最大程度的複用,而且使得添加內容到共享的內容區域更加簡單,例如分支範圍內的導航。

這裏是使用繼承的一些提示:

  • 若是你在模版中使用 {% extends %} 標籤,它必須是模版中的第一個標籤。 其餘的任何狀況下,模版繼承都將沒法工做。

  • 在base模版中設置越多的 {% block %} 標籤越好。 請記住,子模版沒必要定義所有父模版中的blocks,因此,你能夠在大多數blocks中填充合理的默認內容,而後,只定義你須要的那一個。 多一點鉤子總比少一點好。

  • 若是你發現你本身在大量的模版中複製內容,那可能意味着你應該把內容移動到父模版中的一個 {% block %} 中。

  • 若是須要獲取父模板中的block 的內容,能夠使用{{ block.super }} 變量。 若是你想要在父block 中新增內容而不是徹底覆蓋它,它將很是有用。 使用{{ block.super }} 插入的數據不會被自動轉義,由於父模板中的內容已經被轉義。

  • {% block %}以外建立的變量使用模板標籤as語法不能在塊內使用。 例如,此模板不會顯示任何內容:

最後,請注意不能在一個模版中定義多個相同名字的block 標籤。 這個限制的存在是由於block標籤的做用是「雙向」的。 這個意思是,block 標籤不只提供了一個坑去填,它定義向父模版的坑中所填的內容。 若是在一個模版中有兩個名字同樣的 block 標籤,模版的父模版將不知道使用哪一個block的內容。

include

加載模板並以標籤內的參數渲染。 這是一種能夠引入別的模板的方法。

模板名能夠是變量或者是硬編碼的字符串,能夠用單引號也能夠是雙引號.

下面這個示例包括模板"foo/bar.html"的內容:

{% include "foo/bar.html" %}

load

加載自定義模板標籤集。

舉個例子, 下面這模板將會從package包中載入全部otherlibrarysomelibrary 中已經註冊的標籤和過濾器:

{% load somelibrary package.otherlibrary %}

4.自動html轉義

當從模版中生成HTML時,總會有這樣一個風險:值可能會包含影響HTML最終呈現的字符。 例如,思考這個模版片斷:

Hello, {{ name }}

首先,它看起來像是一個無害的方式來顯示用戶的名字,可是設想一下,若是用戶像下面這樣輸入他的名字,會發生什麼:

<script>alert('hello')</script>

...這意味着瀏覽器會彈出一個JavaScript警報框!

顯然,用戶提交的數據都被不該該被盲目的信任,而且被直接插入到你的網頁中,由於一個懷有惡意的用戶可能會使用這樣的漏洞來作一些可能的壞事。 這種類型的安全問題被叫作 跨站腳本(Cross Site Scripting) (XSS) 攻擊。

爲避免這個問題,你有兩個選擇:

  • 第一, 你能夠對每一個不被信任的值運行escape 過濾器(下面的文檔中將提到),它將把潛在的有害HTML 字符轉換成無害的。 在Django 最初的幾年裏,這是默認的解決方案,但問題是它將責任放在大家這些開發人員/模板做者身上,以確保轉義了全部內容。 並且很容易忘記轉義數據。
  • 第二,你能夠利用Django的自動HTML轉義。 本節其他部分描述自動轉義是如何工做的。

默認狀況下,Django 中的每一個模板會自動轉義每一個變量的輸出。 明確地說,下面五個字符被轉義:

  • < 會轉換爲&lt;
  • > 會轉換爲&gt;
  • '(單引號)轉換爲&#39;
  • " (雙引號)會轉換爲 &quot;
  • & 會轉換爲 &amp;

咱們要再次強調這個行爲是默認打開的。 若是你使用Django的模板系統,會處於保護之下。

如何關閉

若是你不但願數據自動轉義,不管是在站點、模板仍是變量級別,你能夠使用幾種方法來關閉它。

然而你爲何想要關閉它呢? 因爲有時,模板變量含有一些你打算渲染成原始HTML的數據,你並不想轉義這些內容。 例如,你可能會在數據庫中儲存一些HTML代碼,而且直接在模板中嵌入它們。 或者,你可能使用Django的模板系統來生成不是HTML的文本 -- 好比郵件信息。

對於單個變量

使用safe過濾器來關閉獨立變量上的自動轉義:

This will be escaped: {{ data }}
This will not be escaped: {{ data|safe }}

對於模板庫

{% autoescape off %}
    Hello {{ name }}
{% endautoescape %}

autoescape標籤接受on 或者 off做爲它的參數。 有時你可能想在自動轉義關閉的狀況下強制使用它。 下面是一個模板的示例

Auto-escaping is on by default. Hello {{ name }}

{% autoescape off %}
    This will not be auto-escaped: {{ data }}.

    Nor this: {{ other_data }}
    {% autoescape on %}
        Auto-escaping applies again: {{ name }}
    {% endautoescape %}
{% endautoescape %}

自動轉義標籤做用於擴展了當前模板的模板,以及經過 include 標籤包含的模板,就像全部block標籤那樣。 像這樣:

{% autoescape off %}
<h1>{% block title %}{% endblock %}</h1>
{% block content %}
{% endblock %}
{% endautoescape %}

5.自定義模板標籤和過濾器

指定自定義模板標籤和過濾器的最多見的地方在Django應用程序中。 若是它們與現有的應用程序相關聯,則將它們捆綁在一塊兒是有意義的;不然,它們能夠添加到新的應用程序。 當將Django應用程序添加到INSTALLED_APPS中時,在下面描述的常規位置中定義的任何標籤將自動在模板中加載。

這個應用應該包含一個templatetags 目錄,和views.pymodels.py等文件處於同一級別目錄下。 若是目錄不存在則建立它——不要忘記建立__init__.py 文件以使得該目錄能夠做爲Python 的包。

你的自定義的標籤和過濾器將放在templatetags 目錄下的一個模塊裏。 這個模塊的名字是你稍後將要載入標籤時使用的,因此要謹慎的選擇名字以防與其餘應用下的自定義標籤和過濾器名字衝突。

例如,你的自定義標籤/過濾器在一個名爲poll_extras.py的文件中,那麼你的app目錄結構看起來應該是這樣的:

polls/
    __init__.py
    models.py
    templatetags/
        __init__.py
        poll_extras.py
    views.py

而後你能夠在模板中像以下這樣使用:

{% load poll_extras %}

(1)編寫自定義過濾器

自定義過濾器就是一個帶有一個或兩個參數的Python 函數:

  • (輸入的)變量的值 —— 不必定是字符串形式。
  • 參數的值 —— 能夠有一個初始值,或者徹底不要這個參數。

例如,在{{ var|foo:"bar" }}中,foo過濾器應當傳入變量var和參數 "bar"

因爲模板語言沒有提供異常處理,任何從過濾器中拋出的異常都將會顯示爲服務器錯誤。 所以,若是有合理的值能夠返回,過濾器應該避免拋出異常。 在模板中有一個明顯錯誤的狀況下,引起一個異常可能仍然要好於用靜默的失敗來掩蓋錯誤。

這是一個定義過濾器的例子:

def cut(value, arg):
    """Removes all values of arg from the given string"""
    return value.replace(arg, '')

下面是這個過濾器應該如何使用:

{{ somevariable|cut:"0" }}

大多數過濾器沒有參數。 在這種狀況下,你的函數不帶這個參數便可。 例如:

def lower(value): # Only one argument.
    """Converts a string into all lowercase"""
    return value.lower()

(2)註冊自定義過濾器

django.template.Library. filter()

一旦你寫好了你的自定義過濾器函數,你就開始須要把它註冊爲你的 Library實例,來讓它在Django模板語言中可用:

register.filter('cut', cut)
register.filter('lower', lower)

Library.filter()方法須要兩個參數:

  1. 過濾器的名稱(一個字符串對象)
  2. 編譯的函數 – 一個Python函數(不要把函數名寫成字符串)

你還能夠把register.filter()用做裝飾器:

@register.filter(name='cut')
def cut(value, arg):
    return value.replace(arg, '')

@register.filter
def lower(value):
    return value.lower()

更多-->>http://usyiyi.cn/translate/Django_111/howto/custom-template-tags.html

(3)編寫自定義模板標籤

標籤比過濾器更復雜,由於標籤能夠作任何事情。

簡單標籤

django.template.Library. simple_tag()

許多模板標籤須要許多參數 - 字符串或模板變量,而且僅在基於輸入參數和一些外部信息進行一些處理後返回結果。 例如,current_time 標籤可能接受一個格式字符串,並返回與之對應的格式化後的時間。

爲了簡單化這些類型標籤的建立,Django 提供一個輔助函數simple_tag這個函數是django.template.Library 的一個方法,接受一個任意數目的參數的函數,將其包裝在一個render 函數和上面提到的其餘必要部分中,並在模板系統中註冊它。

咱們的current_time 函數從而能夠這樣寫

import datetime
from django import template

register = template.Library()

@register.simple_tag
def current_time(format_string):
    return datetime.datetime.now().strftime(format_string)

關於simple_tag 輔助函數幾件值得注意的事項︰

  • 檢查所需參數的數量等等,在咱們的函數調用的時刻已經完成,因此咱們不須要作了。
  • 參數(若是有)的引號都已經被截掉,因此咱們收到的只是一個普通字符串。
  • 若是該參數是一個模板變量,傳遞給咱們的函數是當前變量的值,不是變量自己。

和其餘標籤程序不一樣, 若是模板上下文中開啓了自動轉義模式 simple_tag 的輸出將經過conditional_escape() 轉義, 來保證正確的HTML和防護XSS漏洞.

若是不須要額外的轉義,您將須要使用mark_safe(),若是您絕對確保您的代碼不包含XSS漏洞。 要創建小型HTML片斷,強烈建議您使用format_html()而不是mark_safe()

若是你的模板標籤須要訪問當前上下文,你能夠在註冊標籤時使用takes_context 參數︰

@register.simple_tag(takes_context=True)
def current_time(context, format_string):
    timezone = context['timezone']
    return your_get_current_time_method(timezone, format_string)

 表單

GET和POST

處理表單時候只會用到POSTGET 方法。

Django 的登陸表單使用POST 方法,在這個方法中瀏覽器組合表單數據、對它們進行編碼以用於傳輸、將它們發送到服務器而後接收它的響應。

相反,GET 組合提交的數據爲一個字符串,而後使用它來生成一個URL。 這個URL 將包含數據發送的地址以及數據的鍵和值。 若是你在Django 文檔中作一次搜索,你會當即看到這點,此時將生成一個https://docs.djangoproject.com/search/?q=forms&release=1 形式的URL。

POSTGET 用於不一樣的目的。

用於改變系統狀態的請求 —— 例如,給數據庫帶來變化的請求 —— 應該使用POSTGET 只應該用於不會影響系統狀態的請求。

GET 還不適合密碼錶單,由於密碼將出如今URL 中,以及瀏覽器的歷史和服務器的日誌中,並且都是以普通的文本格式。 它還不適合數據量大的表單和二進制數據,例如一張圖片。 使用GET 請求做爲管理站點的表單具備安全隱患:攻擊者很容易模擬表單請求來取得系統的敏感數據。 POST,若是與其它的保護措施結合將對訪問提供更多的控制,例如Django 的CSRF protection

另外一個方面,GET 適合網頁搜索這樣的表單,由於這種表示一個GET 請求的URL 能夠很容易地做爲書籤、分享和從新提交。

Django在表單中的角色

處理表單是一件很複雜的事情。 考慮一下Django 的Admin 站點,不一樣類型的大量數據項須要在一個表單中準備好、渲染成HTML、使用一個方便的界面編輯、返回給服務器、驗證並清除,而後保存或者向後繼續處理。

Django 的表單功能能夠簡化並自動化大部分這些工做,並且還能夠比大部分程序員本身所編寫的代碼更安全。

Django 會處理表單工做中的三個顯著不一樣的部分:

  • 準備數據、重構數據,以便下一步提交。
  • 爲數據建立HTML 表單
  • 接收並處理客戶端提交的表單和數據

能夠手工編寫代碼來實現,可是Django 能夠幫你完成全部這些工做。

Django的Form類

表單系統的核心部分是Django 的Form 類。 Django 的模型描述一個對象的邏輯結構、行爲以及展示給咱們的方式,與此相似,Form 類描述一個表單並決定它如何工做和展示。

就像模型類的屬性映射到數據庫的字段同樣,表單類的字段會映射到HTML 的<input>表單的元素。 ModelForm 經過一個Form 映射模型類的字段到HTML 表單的<input> 元素;Django 的Admin 站點就是基於這個)。

一個表單的字段自己就是類;他們管理表單數據,並在提交表單時執行驗證。 DateFieldFileField 處理的數據類型差異很大,必須完成不一樣的事情。

表單字段在瀏覽器中呈現給用戶的是一個HTML 的「widget」 —— 用戶界面的一個片斷。 每一個字段類型都有一個合適的默認Widget class,須要時能夠覆蓋。

實例化、處理和渲染表單

在Django 中渲染一個對象時,咱們一般:

  1. 在視圖中得到它(例如,從數據庫中獲取)
  2. 將它傳遞給模板的context
  3. 使用模板變量將它擴展爲HTML 標記

除了幾個關鍵點不一樣以外,在模板中渲染表單和渲染其它類型的對象幾乎同樣。

在模型實例不包含數據的狀況下,在模板中對它作處理不多有什麼用處。 可是渲染一個未填充的表單卻很是有意義 —— 咱們但願用戶去填充它。

因此當咱們在視圖中處理模型實例時,咱們通常從數據庫中獲取它。 當咱們處理表單時,咱們通常在視圖中實例化它。

當咱們實例化表單時,咱們能夠選擇讓它爲空仍是預先填充它,例如使用:

  • 來自一個保存後的模型實例的數據(例如用於編輯的管理表單)
  • 咱們從其它地方得到的數據
  • 從前面一個HTML 表單提交過來的數據

獲取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>

這告訴瀏覽器使用POST方法將表單數據返回到URL / your-name /它將顯示一個標籤爲"Your name:"的文本字段,和一個"OK"按鈕。 若是模板上下文包含current_name變量​​,則將用於預填your_name字段。

您將須要一個視圖來渲染包含HTML表單的模板,而且能夠根據須要提供current_name字段。

當表單提交時,發往服務器的POST 請求將包含表單數據。

如今你還須要一個對應/your-name/ URL 的視圖,它在請求中找到正確的鍵/值對,而後處理它們。

這是一個很是簡單的表單。 實際應用中,一個表單可能包含幾十上百個字段,其中大部分須要預填充,並且咱們預料到用戶未來回編輯-提交幾回才能完成操做。

即便在提交表單以前,咱們也可能須要在瀏覽器中進行一些驗證。咱們可能想要使用更復雜的字段,這樣可讓用戶作一些事情,例如從日曆中選擇日期等等。

這個時候,讓Django 來爲咱們完成大部分工做是很容易的。

 1.建立一個表單

(1)form.py

咱們已經計劃好了咱們的 HTML 表單應該呈現的樣子。 在Django 中,咱們的起始點是這裏:

from django import forms

class NameForm(forms.Form):
    your_name = forms.CharField(label='Your name', max_length=100)

它定義一個Form 類,只帶有一個字段(your_name)。 咱們已經對這個字段使用一我的性化的標籤,當渲染時它將出如今<label> 中(在這個例子中,即便咱們省略它,咱們指定的label仍是會自動生成)。

字段容許的最大長度經過max_length 定義。 它完成兩件事情。 首先,它在HTML 的<input> 上放置一個maxlength="100" (這樣瀏覽器將在第一時間阻止用戶輸入多於這個數目的字符)。 它還意味着當Django 收到瀏覽器發送過來的表單時,它將驗證數據的長度。

Form 的實例具備一個is_valid() 方法,它爲全部的字段運行驗證的程序。 當調用這個方法時,若是全部的字段都包含合法的數據,它將:

  • 返回True
  • 將表單的數據放到cleaned_data 屬性中。

完整的表單,第一次渲染時,看上去將像:

<label for="your_name">Your name: </label>
<input id="your_name" type="text" name="your_name" maxlength="100" required />

注意它不包含 <form> 標籤和提交按鈕。 咱們必須本身在模板中提供它們。

(2)視圖

發送回Django網站的表單數據由視圖處理,一般是發佈表單的相同視圖。 這容許咱們重用一些相同的邏輯。

要操做一個經過URL發佈的表單,咱們要在視圖中實例化它。

from django.shortcuts import render
from django.http import HttpResponseRedirect

from .forms import NameForm

def get_name(request):
    # 若是這是一個POST請求,咱們就須要處理表單數據
    if request.method == 'POST':
        # 建立一個表單實例,而且使用表單數據填充request請求:
        form = NameForm(request.POST)
        # 檢查數據有效性:
        if form.is_valid():
            # 在須要時,能夠在form.cleaned_date中處理數據
            # ...
            # 重定向到一個新的URL:
            return HttpResponseRedirect('/thanks/')

    # 若是是GET或者其它請求方法,咱們將建立一個空的表單。
    else:
        form = NameForm()

    return render(request, 'name.html', {'form': form})

若是訪問視圖的是一個GET 請求,它將建立一個空的表單實例並將它放置到要渲染的模板的上下文中。 這是咱們在第一次訪問該URL 時預期發生的狀況。

若是使用POST請求提交表單,該視圖將再次建立一個表單實例,並使用請求中的數據填充表單:形式 = NameForm(request.POST)這被稱爲「將數據綁定到表單」(如今是綁定的形式)。

咱們調用窗體的is_valid()方法;若是不是True,咱們返回到表單的模板。 這時表單再也不爲空(未綁定),因此HTML 表單將用以前提交的數據填充,而後能夠根據要求編輯並改正它。

若是Trueis_valid(),咱們將可以在cleaned_data 屬性中找到全部合法的表單數據。 在發送HTTP 重定向給瀏覽器告訴它下一步的去向以前,咱們能夠用這個數據來更新數據庫或者作其它處理。

(3)模板

咱們不須要在name.html 模板中作不少工做。 最簡單的例子是:

<form action="/your-name/" method="post">
    {% csrf_token %}
    {{ form }}
    <input type="submit" value="Submit" />
</form>

Django會根據模型類的字段和屬性,在HTML中自動生成對應表單標籤和標籤屬性。生成的標籤會被放置到{{ form }}所在的位置。

如今咱們有了一個能夠工做的網頁表單,它經過Django Form 描述、經過視圖處理並渲染成一個HTML <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)

咱們前面的表單只使用一個字段your_name,它是一個CharField在這個例子中,咱們的表單具備四個字段:messagesubjectsendercc_myselfCharFieldEmailFieldBooleanField只是三種可用的字段類型

窗口小部件

每一個表單字段都有一個對應的Widget class,它對應一個HTML 表單Widget,例如<input type="text">

在大部分狀況下,字段都具備一個合理的默認Widget。 例如,默認狀況下,CharField 具備一個TextInput Widget,它在HTML 中生成一個<input type="text">若是你須要message,在定義表單字段時你應該指定一個合適的Widget,例如咱們定義的<textarea> 字段。

字段數據

無論表單提交的是什麼數據,一旦經過調用is_valid() 成功驗證(is_valid() 返回True),驗證後的表單數據將位於form.cleaned_data 字典中。 這些數據已經爲你轉換好爲Python 的類型。

在上面的聯繫表單示例中,cc_myself 將是一個布爾值。 相似地,IntegerFieldFloatField 字段分別將值轉換爲Python 的intfloat

下面是在視圖中如何處理表單數據:

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/')

2.表單API

(1)綁定和綁定形式

Form要麼是綁定的,要麼是未綁定的

  • 若是是綁定的,那麼它可以驗證數據,並渲染表單及其數據成HTML。
  • 若是未綁定,則沒法進行驗證(由於沒有數據能夠驗證!),但它仍然能夠以HTML形式呈現空白表

若要建立一個未綁定的Form實例,只需簡單地實例化該類:

>>> f = ContactForm()

若要綁定數據到表單,能夠將數據以字典的形式傳遞給Form類的構造函數的第一個參數:

>>> data = {'subject': 'hello',
...         'message': 'Hi there',
...         'sender': 'foo@example.com',
...         'cc_myself': True}
>>> f = ContactForm(data)
>>> f.is_bound
True

若是你有一個綁定的Form實例可是想改下數據,或者你想綁定一個未綁定的Form表單到某些數據,你須要建立另一個Form實例。 Form 實例的數據沒有辦法修改。 Form實例一旦建立,你應該將它的數據視爲不可變的,不管它有沒有數據。

(2)使用表單驗證數據

讓咱們試下非法的數據。 下面的情形中,subject 爲空(默認全部字段都是必需的)且sender 是一個不合法的郵件地址:

>>> data = {'subject': '',
...         'message': 'Hi there',
...         'sender': 'invalid email address',
...         'cc_myself': True}
>>> f = ContactForm(data)
>>> f.is_valid()
False
Form.errors

訪問errors 屬性能夠得到錯誤信息的一個字典:

>>> f.errors
{'sender': ['Enter a valid email address.'], 'subject': ['This field is required.']}
Form.errors.as_data()

返回一個dict,它映射字段到原始的ValidationError 實例。

>>> f.errors.as_data()
{'sender': [ValidationError(['Enter a valid email address.'])],
'subject': [ValidationError(['This field is required.'])]}
Form.errors. as_json(escape_html=False)

返回JSON 序列化後的錯誤。

>>> f.errors.as_json()
{"sender": [{"message": "Enter a valid email address.", "code": "invalid"}],
"subject": [{"message": "This field is required.", "code": "required"}]}

(3)訪問乾淨數據

Form.cleaned_data

Form類中的每一個字段不只負責驗證數據,還負責「清潔」它們 —— 將它們轉換爲正確的格式。 這是個很是好用的功能,由於它容許字段以多種方式輸入數據,並總能獲得一致的輸出。

例如,DateField 將輸入轉換爲Python 的 datetime.date 對象。 不管你傳遞的是DateField 格式的字符串、datetime.date 對象、仍是其它格式的數字,'1994-07-15' 將始終將它們轉換成datetime.date 對象,只要它們是合法的。

一旦你建立一個Form實例並經過驗證後,你就能夠經過它的cleaned_data 屬性訪問清潔的數據:

>>> data = {'subject': 'hello',
...         'message': 'Hi there',
...         'sender': 'foo@example.com',
...         'cc_myself': True}
>>> f = ContactForm(data)
>>> f.is_valid()
True
>>> f.cleaned_data
{'cc_myself': True, 'message': 'Hi there', 'sender': 'foo@example.com', 'subject': 'hello'}

(4)輸出表單爲HTML

as_p()

Form. as_p()

<p> 渲染表單爲一系列的<p> 標籤,每一個as_p() 標籤包含一個字段:

>>> f = ContactForm()
>>> f.as_p()
'<p><label for="id_subject">Subject:</label> <input id="id_subject" type="text" name="subject" maxlength="100" required /></p>\n<p><label for="id_message">Message:</label> <input type="text" name="message" id="id_message" required /></p>\n<p><label for="id_sender">Sender:</label> <input type="text" name="sender" id="id_sender" required /></p>\n<p><label for="id_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_cc_myself" /></p>'
>>> print(f.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> <input type="text" name="message" id="id_message" required /></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>

as_ul()

Form. as_ul()

<li> 渲染表單爲一系列的<li>標籤,每一個as_ul() 標籤包含一個字段。 包含</ul><ul>,因此你能夠本身指定<ul> 的任何HTML 屬性:

as_table()

Form. as_table T0>()

最後,as_table()輸出表單爲一個HTML <table>它與print 徹底相同。 事實上,當你print 一個表單對象時,在後臺調用的就是as_table() 方法:

3.表單字段

class Field(**kwargs)[source]

建立一個Form類時,最重要的部分是定義表單的字段。 每一個字段均可以有自定義的驗證邏輯,以及一些其它的鉤子。

Field. clean(value)[source]

雖然Field類主要使用在Form類中,但你也能夠直接實例化它們來使用,以便更好地瞭解它們是如何工做的。 每一個django.forms.ValidationError實例都有一個clean()方法, 它接受一個參數,而後返回「清潔的」數據或者拋出一個Field異常:

>>> from django import forms
>>> f = forms.EmailField()
>>> f.clean('foo@example.com')
'foo@example.com'
>>> f.clean('invalid email address')
Traceback (most recent call last):
...
ValidationError: ['Enter a valid email address.']

核心字段參數

每一個Field類的構造函數至少接受這些參數。 有些Field類接受額外的、字段特有的參數,但如下參數應該老是能接受:

required

Field. required

默認狀況下,每一個"" 類都假設必需有值,因此若是你傳遞一個空的值 —— 無論是None 仍是空字符串(Field) —— clean() 將引起一個ValidationError 異常:

>>> from django import forms
>>> f = forms.CharField()
>>> f.clean('foo')
'foo'
>>> f.clean('')
Traceback (most recent call last):
...
ValidationError: ['This field is required.']
>>> f.clean(None)
Traceback (most recent call last):
...
ValidationError: ['This field is required.']
>>> f.clean(' ')
' '
>>> f.clean(0)
'0'
>>> f.clean(True)
'True'
>>> f.clean(False)
'False'

若要表示一個字段是必需的,請傳遞Fieldrequired=False 的構造函數:

>>> f = forms.CharField(required=False)
>>> f.clean('foo')
'foo'
>>> f.clean('')
''
>>> f.clean(None)
''
>>> f.clean(0)
'0'
>>> f.clean(True)
'True'
>>> f.clean(False)
'False'

若是ValidationError 具備clean(),而你傳遞給required=False 一個空值,Field 將返回一個轉換後的空值而不是引起clean()例如CharField,它將是一個空的Unicode 字符串。 對於其它Field類,它多是None(每一個字段各不相同)。

label

Field. label

label 參數讓你指定字段「對人類友好」的label。 FieldForm中顯示時將用到它。

正如在前面「輸出表單爲HTML」中解釋的,Field默認label 是經過將字段名中全部的下劃線轉換成空格並大寫第一個字母生成的。 若是默認的標籤不合適,能夠指定label

下面是一個完整示例,Form爲它的兩個字段實現了label咱們指定auto_id=False來讓輸出簡單一些:

>>> from django import forms
>>> class CommentForm(forms.Form):
...     name = forms.CharField(label='Your name')
...     url = forms.URLField(label='Your website', required=False)
...     comment = forms.CharField()
>>> f = CommentForm(auto_id=False)
>>> print(f)
<tr><th>Your name:</th><td><input type="text" name="name" required /></td></tr>
<tr><th>Your website:</th><td><input type="url" name="url" /></td></tr>
<tr><th>Comment:</th><td><input type="text" name="comment" required /></td></tr>

label_suffix

Field. label_suffix

label_suffix 參數讓你基於每一個字段覆蓋表單的label_suffix

>>> class ContactForm(forms.Form):
...     age = forms.IntegerField()
...     nationality = forms.CharField()
...     captcha_answer = forms.IntegerField(label='2 + 2', label_suffix=' =')
>>> f = ContactForm(label_suffix='?')
>>> print(f.as_p())
<p><label for="id_age">Age?</label> <input id="id_age" name="age" type="number" required /></p>
<p><label for="id_nationality">Nationality?</label> <input id="id_nationality" name="nationality" type="text" required /></p>
<p><label for="id_captcha_answer">2 + 2 =</label> <input id="id_captcha_answer" name="captcha_answer" type="number" required /></p>

initial

Field. initial

Form 參數讓你指定渲染未綁定的Field中的initial時使用的初始值。

若要指定動態的初始數據,參見Form.initial 參數。

這個參數的使用場景是當你想要顯示一個「空」的表單,其某個字段初始化爲一個特定的值。 像這樣:

>>> from django import forms
>>> class CommentForm(forms.Form):
...     name = forms.CharField(initial='Your name')
...     url = forms.URLField(initial='http://')
...     comment = forms.CharField()
>>> f = CommentForm(auto_id=False)
>>> print(f)
<tr><th>Name:</th><td><input type="text" name="name" value="Your name" required /></td></tr>
<tr><th>Url:</th><td><input type="url" name="url" value="http://" required /></td></tr>
<tr><th>Comment:</th><td><input type="text" name="comment" required /></td></tr>

widget

Field. widget

Field 參數讓你指定渲染Widget時使用的widget

help_text

Field. help_text

help_text 參數讓你指定Field的描述文本。 若是提供Field,在經過Field的便捷方法(例如,help_text)渲染Form時,它將緊接着as_ul()顯示。

像模型字段的help_text同樣,此值不會以自動生成的形式進行HTML轉義。

下面是一個完整的示例,Form爲它的兩個字段實現了help_text咱們指定auto_id=False來讓輸出簡單一些:

>>> from django import forms
>>> class HelpTextContactForm(forms.Form):
...     subject = forms.CharField(max_length=100, help_text='100 characters max.')
...     message = forms.CharField()
...     sender = forms.EmailField(help_text='A valid email address, please.')
...     cc_myself = forms.BooleanField(required=False)
>>> f = HelpTextContactForm(auto_id=False)
>>> print(f.as_table())
<tr><th>Subject:</th><td><input type="text" name="subject" maxlength="100" required /><br /><span class="helptext">100 characters max.</span></td></tr>
<tr><th>Message:</th><td><input type="text" name="message" required /></td></tr>
<tr><th>Sender:</th><td><input type="email" name="sender" required /><br />A valid email address, please.</td></tr>
<tr><th>Cc myself:</th><td><input type="checkbox" name="cc_myself" /></td></tr>
>>> print(f.as_ul()))
<li>Subject: <input type="text" name="subject" maxlength="100" required /> <span class="helptext">100 characters max.</span></li>
<li>Message: <input type="text" name="message" required /></li>
<li>Sender: <input type="email" name="sender" required /> A valid email address, please.</li>
<li>Cc myself: <input type="checkbox" name="cc_myself" /></li>
>>> print(f.as_p())
<p>Subject: <input type="text" name="subject" maxlength="100" required /> <span class="helptext">100 characters max.</span></p>
<p>Message: <input type="text" name="message" required /></p>
<p>Sender: <input type="email" name="sender" required /> A valid email address, please.</p>
<p>Cc myself: <input type="checkbox" name="cc_myself" /></p>

error_messages

Field. error_messages

error_messages 參數讓你覆蓋字段引起的異常中的默認信息。 傳遞的是一個字典,其鍵爲你想覆蓋的錯誤信息。 例如,下面是默認的錯誤信息:

>>> from django import forms
>>> generic = forms.CharField()
>>> generic.clean('')
Traceback (most recent call last):
  ...
ValidationError: ['This field is required.']

而下面是自定義的錯誤信息:

>>> name = forms.CharField(error_messages={'required': 'Please enter your name'})
>>> name.clean('')
Traceback (most recent call last):
  ...
ValidationError: ['Please enter your name']

validators

Field. validators

validators 參數讓你能夠爲字段提供一個驗證函數的列表。

localize

Field. localize

localize參數能夠實現表單數據輸入的定位,以及渲染輸出。

disabled

Field. disabled

disabled布爾參數,當設置爲True時,使用disabled HTML屬性禁用表單域,以使用戶沒法編輯。 即便用戶篡改了提交給服務器的字段的值,它也將被忽略,有利於表單初始數據中的值。

has_changed()

Field. has_changed()[source]

has_changed() 方法用於決定字段的值是否從初始值發生了改變。 返回TrueFalse

4.內置Field類

BooleanField

class  BooleanField(**kwargs)[source]
  • 默認的Widget:CheckboxInput
  • 空值:False
  • 規範化爲:Python 的True 或 False
  • 若是字段帶有True,驗證值是否爲required=True(例如複選框被勾上)。
  • 錯誤信息的鍵:required

CharField

class  CharField(**kwargs)[source]
  • 默認的Widget:TextInput
  • 空值:與empty_value給出的任何值。
  • 規範化爲:一個Unicode 對象。
  • 若是提供,驗證max_length 或min_length。 不然,全部的輸入都是合法的。
  • 錯誤信息的鍵:min_lengthmax_lengthrequired

有三個可選參數進行驗證:

max_length
min_length

若是提供,這兩個參數將確保字符串的最大和最小長度。

strip

若是True(默認),該值將被剝離前導和尾隨空格。

empty_value
Django中的新功能1.11。

用來表示「空」的值。 默認爲空字符串。

ChoiceField

class  ChoiceField(**kwargs)[source]
  • 默認的Widget:Select
  • 空值:''(一個空字符串)
  • 規範化爲:一個Unicode 對象。
  • 驗證給定的值在選項列表中存在。
  • 錯誤信息的鍵:requiredinvalid_choice

invalid_choice 錯誤消息可能包含%(value)s,它將被選擇的選項替換掉。

還有一個參數:

choices

用來做爲該字段選項的一個二元組組成的可迭代對象(例如,列表或元組)或者一個可調用對象。 參數的格式與模型字段的choices 參數相同。

TypedChoiceField

class  TypedChoiceField(**kwargs)[source]

就像ChoiceField同樣,除了TypedChoiceField還有兩個額外的參數:coerceempty_value

  • 默認的Widget:Select
  • 空值:與empty_value給出的任何值。
  • 規範化爲:coerce 參數類型的值。
  • 驗證給定的值在選項列表中存在而且能夠被強制轉換。
  • 錯誤信息的鍵:requiredinvalid_choice

接收的額外參數:

coerce

接收一個參數並返回強制轉換後的值的一個函數。 例如內建的boolfloatint 和其它類型。 默認爲id 函數。 注意強制轉換在輸入驗證結束後發生,因此它可能強制轉換不在 choices 中的值。

empty_value

用於表示「空」的值。默認爲空字符串; None是這裏的另外一個常見選擇。 注意這個值不會被coerce 參數中指定的函數強制轉換,因此請根據狀況進行選擇。

DateField

class  DateField(**kwargs)[source]
  • 默認的Widget:DateInput
  • 空值:None
  • 規範化爲:一個Python datetime.date 對象。
  • 驗證給出的值是一個datetime.datedatetime.datetime 或指定日期格式的字符串。
  • 錯誤信息的鍵:requiredinvalid

接收一個可選的參數:

input_formats

一個格式的列表,用於轉換一個字符串爲datetime.date 對象。

若是沒有提供input_formats,默認的輸入格式爲:

['%Y-%m-%d',      # '2006-10-25'
 '%m/%d/%Y',      # '10/25/2006'
 '%m/%d/%y']      # '10/25/06'

DateTimeField

class  DateTimeField(**kwargs)[source]
  • 默認的Widget:DateTimeInput
  • 空值:None
  • 規範化爲:一個Python datetime.datetime 對象。
  • 驗證給出的值是一個datetime.datetimedatetime.date 或指定日期格式的字符串。
  • 錯誤信息的鍵:requiredinvalid

接收一個可選的參數:

input_formats

一個格式的列表,用於轉換一個字符串爲datetime.datetime 對象。

若是沒有提供input_formats,默認的輸入格式爲:

['%Y-%m-%d %H:%M:%S',    # '2006-10-25 14:30:59'
 '%Y-%m-%d %H:%M',       # '2006-10-25 14:30'
 '%Y-%m-%d',             # '2006-10-25'
 '%m/%d/%Y %H:%M:%S',    # '10/25/2006 14:30:59'
 '%m/%d/%Y %H:%M',       # '10/25/2006 14:30'
 '%m/%d/%Y',             # '10/25/2006'
 '%m/%d/%y %H:%M:%S',    # '10/25/06 14:30:59'
 '%m/%d/%y %H:%M',       # '10/25/06 14:30'
 '%m/%d/%y']             # '10/25/06'
DecimalField 
class  DecimalField(**kwargs)[source]
  • 默認的Widget:當Field.localize 是False 時爲NumberInput,不然爲TextInput
  • 空值:None
  • 規範化爲:一個Python decimal
  • 驗證給定的值爲一個十進制數。 忽略前導和尾隨的空白。
  • 錯誤信息的鍵:max_whole_digitsmax_digitsmax_decimal_placesmax_valueinvalidrequiredmin_value

%(limit_value)s 和min_value 錯誤信息可能包含max_value,它們將被真正的限制值替換。 相似地,max_whole_digitsmax_decimal_places 和 max_digits 錯誤消息可能包含%(max)s

接收四個可選的參數:

max_value
min_value

它們控制字段中容許的值的範圍,應該以decimal.Decimal 值給出。

max_digits

值容許的最大位數(小數點以前和以後的數字總共的位數,前導的零將被刪除)。

decimal_places

容許的最大小數位。

DurationField

class  DurationField(**kwargs)[source]
  • 默認的Widget:TextInput
  • 空值:None
  • 規範化爲:一個Python timedelta
  • 驗證給出的值是一個字符串,而能夠給轉換爲timedelta
  • 錯誤信息的鍵:requiredinvalid.

接收任何能夠被parse_duration() 理解的格式。

EmailField

class  EmailField(**kwargs)[source]
  • 默認的Widget:EmailInput
  • 空值:''(一個空字符串)
  • 規範化爲:一個Unicode 對象。
  • 驗證給出的值是一個合法的郵件地址,使用一個適度複雜的正則表達式。
  • 錯誤信息的鍵:requiredinvalid

具備兩個可選的參數用於驗證,max_length 和min_length。 若是提供,這兩個參數將確保字符串的最大和最小長度。

FileField

class  FileField(**kwargs)[source]
  • 默認的Widget:ClearableFileInput
  • 空值:None
  • 規範化爲:一個UploadedFile 對象,它封裝文件內容和文件名爲一個單獨的對象。
  • 能夠驗證非空的文件數據已經綁定到表單。
  • 錯誤信息的鍵:missinginvalidrequiredemptymax_length

具備兩個可選的參數用於驗證,max_length 和 allow_empty_file。 若是提供,這兩個參數確保文件名的最大長度,並且即便文件內容爲空時驗證也會成功。

FloatField

class  FloatField(**kwargs)[source]
  • 默認的Widget:當Field.localize 是False 時爲NumberInput,不然爲TextInput
  • 空值:None
  • 規範化爲:一個Float 對象。
  • 驗證給定的值是一個浮點數。 和Python 的float() 函數同樣,容許前導和尾隨的空白符。
  • 錯誤信息的鍵:max_valueinvalidrequiredmin_value

接收兩個可選的參數用於驗證,max_value 和min_value。 它們控制字段中容許的值的範圍。

IntergerField

class  IntegerField(**kwargs)[source]
  • 默認的Widget:當Field.localize 是False 時爲NumberInput,不然爲TextInput
  • 空值:None
  • 規範化爲:一個Python 整數或長整數。
  • 驗證給定值是一個整數。 容許前導和尾隨空格,如Python的int()函數。
  • 錯誤信息的鍵:max_valueinvalidrequiredmin_value

%(limit_value)s 和min_value 錯誤信息可能包含max_value,它們將被真正的限制值替換。

採用兩個可選參數進行驗證:

max_value
min_value

它們控制字段中容許的值的範圍。

GenericIPAddressField

class  GenericIPAddressField(**kwargs)[source]

包含IPv4或IPv6地址的字段。

  • 默認的Widget:TextInput
  • 空值:''(一個空字符串)
  • 規範化爲:一個Unicode 對象。 IPv6地址以下所述進行歸一化。
  • 驗證給定值是有效的IP地址。
  • 錯誤信息的鍵:requiredinvalid

IPv6地址規範化遵循 RFC 4291#section-2.2第2.2節,包括使用該段第3段中建議的IPv4格式,如::ffff:192.0.2.0 例如,::ffff:0a0a:0a0a將被標準化爲2001::12001:0::0:01 ::ffff:10.10.10.10。 全部字符都轉換爲小寫。

有兩個可選參數:

protocol

限制指定協議的有效輸入。 接受的值爲IPv6(默認值),IPv4both。 匹配不區分大小寫。

unpack_ipv4

解開IPv4映射地址,例如::ffff:192.0.2.1。 若是啓用此選項,則該地址將解包到192.0.2.1。 默認爲禁用。 只能在protocol設置爲'both'時使用。

MultipleChoiceField

class  MultipleChoiceField(**kwargs)[source]
  • 默認的Widget:SelectMultiple
  • 空值:[](一個空列表)
  • 規範化爲:一個Unicode 對象列表。
  • 驗證給定值列表中的每一個值都存在於選擇列表中。
  • 錯誤信息的鍵:invalid_listinvalid_choicerequired

invalid_choice 錯誤消息可能包含%(value)s,它將被選擇的選項替換掉。

對於choices,須要一個額外的必需參數ChoiceField

TypedMultipleChoiceField

class  TypedMultipleChoiceField(**kwargs)[source]

就像MultipleChoiceField,除了TypedMultipleChoiceField須要兩個額外的參數,coerceempty_value

  • 默認的Widget:SelectMultiple
  • 空值:empty_value
  • 規範化爲:coerce參數提供的類型值列表。
  • 驗證給定值存在於選項列表中而且能夠強制。
  • 錯誤信息的鍵:requiredinvalid_choice

invalid_choice 錯誤消息可能包含%(value)s,它將被選擇的選項替換掉。

對於TypedChoiceField,須要兩個額外的參數empty_valuecoerce

RegexField

class  RegexField(**kwargs)[source]
  • 默認的Widget:TextInput
  • 空值:''(一個空字符串)
  • 規範化爲:一個Unicode 對象。
  • 驗證給定值與某個正則表達式匹配。
  • 錯誤信息的鍵:requiredinvalid

須要一個必需的參數:

regex

指定爲字符串或編譯的正則表達式對象的正則表達式。

還須要max_lengthmin_lengthstrip,它們與CharField同樣工做。

strip

默認爲False。 若是啓用,則將在正則表達式驗證以前應用剝離

SlugField

class  SlugField(**kwargs)[source]
  • 默認的Widget:TextInput
  • 空值:''(一個空字符串)
  • 規範化爲:一個Unicode 對象。
  • 驗證給定的字符串只包括字母、數字、下劃線及連字符。
  • 錯誤信息的鍵:requiredinvalid

此字段用於在表單中表示模型SlugField

使用可選參數:

allow_unicode

布爾型指令除了ASCII字母外,還能夠接受Unicode字母。 默認爲False

TimeField

class  TimeField(**kwargs)[source]
  • 默認的Widget:TextInput
  • 空值:None
  • 規範化爲:一個Python 的datetime.time 對象。
  • 驗證給定值是datetime.time或以特定時間格式格式化的字符串。
  • 錯誤信息的鍵:requiredinvalid

接收一個可選的參數:

input_formats

用於嘗試將字符串轉換爲有效的datetime.time對象的格式列表。

若是沒有提供input_formats,默認的輸入格式爲:

URLField

class  URLField(**kwargs)[source]
  • 默認的Widget:URLInput
  • 空值:''(一個空字符串)
  • 規範化爲:一個Unicode 對象。
  • 驗證給定值是有效的URL。
  • 錯誤信息的鍵:requiredinvalid

採用如下可選參數:

max_length
min_length

這些與CharField.max_lengthCharField.min_length相同。

更多-->>http://usyiyi.cn/translate/Django_111/ref/forms/fields.html

5.窗口小部件

不要將Widget 與form fields搞混淆。 表單字段負責驗證輸入並直接在模板中使用。 Widget 負責渲染網頁上HTML 表單的輸入元素和提取提交的原始數據

每當你指定表單的一個字段的時候,Django 將使用適合其數據類型的默認Widget。然而,若是你想要使用一個不一樣的Widget,你能夠在定義字段時使用widget 參數。 像這樣:

from django import forms

class CommentForm(forms.Form):
    name = forms.CharField()
    url = forms.URLField()
    comment = forms.CharField(widget=forms.Textarea)

這將使用一個Textarea Widget來設置表單的評論 ,而不是默認的TextInput Widget

許多小部件具備可選的額外參數;在字段上定義窗口小部件時能夠設置它們。 在下面的示例中,設置了SelectDateWidget 的years屬性:

from django import forms

BIRTH_YEAR_CHOICES = ('1980', '1981', '1982')
FAVORITE_COLORS_CHOICES = (
    ('blue', 'Blue'),
    ('green', 'Green'),
    ('black', 'Black'),
)

class SimpleForm(forms.Form):
    birth_year = forms.DateField(widget=forms.SelectDateWidget(years=BIRTH_YEAR_CHOICES))
    favorite_colors = forms.MultipleChoiceField(
        required=False,
        widget=forms.CheckboxSelectMultiple,
        choices=FAVORITE_COLORS_CHOICES,
    )

(1)小部件繼承自select小部件

繼承自Select 的Widget 負責處理HTML 選項。 它們呈現給用戶一個能夠選擇的選項列表。 不一樣的小部件呈現出不一樣的選擇;Select小部件自己使用<select> HTML列表表示,而RadioSelect使用單選按鈕。

ChoiceField 字段默認使用Select。 Widget 上顯示的選項來自ChoiceField,對ChoiceField.choices 的改變將更新Select.choices。 像這樣:

>>> from django import forms
>>> CHOICES = (('1', 'First',), ('2', 'Second',))
>>> choice_field = forms.ChoiceField(widget=forms.RadioSelect, choices=CHOICES)
>>> choice_field.choices
[('1', 'First'), ('2', 'Second')]
>>> choice_field.widget.choices
[('1', 'First'), ('2', 'Second')]
>>> choice_field.widget.choices = ()
>>> choice_field.choices = (('1', 'First and only',),)
>>> choice_field.widget.choices
[('1', 'First and only')]

提供choices 屬性的Widget 也能夠用於不是基於選項的字段 , 例如CharField —— 當選項與模型有關而不僅是Widget 時,建議使用基於ChoiceField 的字段。

(2)樣式化小部件

若是你想讓某個Widget 實例與其它Widget 看上去不同,你須要在Widget 對象實例化並賦值給一個表單字段時指定額外的屬性(以及可能須要在你的CSS 文件中添加一些規則)。

例以下面這個簡單的表單:

from django import forms

class CommentForm(forms.Form):
    name = forms.CharField()
    url = forms.URLField()
    comment = forms.CharField()

這個表單包含三個默認的TextInput Widget,以默認的方式渲染 —— 沒有CSS 類、沒有額外的屬性。 這表示每一個Widget 的輸入框將渲染得如出一轍:

>>> f = CommentForm(auto_id=False)
>>> f.as_table()
<tr><th>Name:</th><td><input type="text" name="name" required /></td></tr>
<tr><th>Url:</th><td><input type="url" name="url" required /></td></tr>
<tr><th>Comment:</th><td><input type="text" name="comment" required /></td></tr>

在真正得網頁中,你可能不想讓每一個Widget 看上去都同樣。 你可能想要給comment 一個更大的輸入元素,你可能想讓‘name’ Widget 具備一些特殊的CSS 類。 能夠指定‘type’ 屬性使用的是新式的HTML5 輸入類型。 在建立Widget 時使用Widget.attrs 參數能夠實現:

class CommentForm(forms.Form):
    name = forms.CharField(widget=forms.TextInput(attrs={'class': 'special'}))
    url = forms.URLField()
    comment = forms.CharField(widget=forms.TextInput(attrs={'size': '40'}))

Django 將在渲染的輸出中包含額外的屬性:

>>> f = CommentForm(auto_id=False)
>>> f.as_table()
<tr><th>Name:</th><td><input type="text" name="name" class="special" required /></td></tr>
<tr><th>Url:</th><td><input type="url" name="url" required /></td></tr>
<tr><th>Comment:</th><td><input type="text" name="comment" size="40" required /></td></tr>

(3)基於小部件類

Widget 和MultiWidget 是全部built-in widgets 的基類,並可用於自定義Widget 的基類。

Widget

class  Widget(attrs=None)[source]

這是個抽象類,它不能夠渲染,可是提供基本的屬性attrs。 你能夠在自定義的Widget 中實現或覆蓋render() 方法。

ATTRS T0> 

包含渲染後的Widget 將要設置的HTML 屬性。

>>> from django import forms
>>> name = forms.TextInput(attrs={'size': 10, 'title': 'Your name',})
>>> name.render('name', 'A name')
'<input title="Your name" type="text" name="name" value="A name" size="10" required />'

若是你給一個屬性賦值True 或False,它將渲染成一個HTML5 風格的布爾屬性:

>>> name = forms.TextInput(attrs={'required': True})
>>> name.render('name', 'A name')
'<input name="name" type="text" value="A name" required />'
>>>
>>> name = forms.TextInput(attrs={'required': False})
>>> name.render('name', 'A name')
'<input name="name" type="text" value="A name" />'
supports_microseconds T0> 

屬性默認爲True。 若是設置爲False,則datetimetime值的微秒部分將被設置爲0

format_value(value)[source]

清除並返回一個用於小部件模板的值。 value不能保證是有效的輸入,所以子類的實現應該防護性地編程。

在Django更改1.10:

在舊版本中,此方法是名爲_format_value()的私有API。 舊的名稱將工做,直到Django 2.0。

get_contextnamevalueattrs[source] 
Django中的新功能1.11。

返回在渲染窗口小部件模板時要使用的值的字典。 默認狀況下,該字典包含一個單一的鍵'widget',它是包含如下鍵的小部件的字典表示形式:

  • 'name'name參數中的字段的名稱。
  • 'is_hidden':一個布爾值,表示該小部件是否被隱藏。
  • 'required':一個布爾值,表示是否須要此窗口小部件的字段。
  • 'value':由format_value()返回的值。
  • 'attrs':要在已渲染的小部件上設置HTML屬性。 attrs屬性和attrs參數的組合。
  • 'template_name'self.template_name的值。

Widget子類能夠經過覆蓋此方法來提供自定義上下文值。

id_for_label(id_)[source]

給定該字段的ID,返回此小部件的HTML ID屬性,以供<label>使用。 若是ID不可用,則返回None

這個鉤子是必要的,由於一些小部件具備多個HTML元素,所以具備多個ID。 在這種狀況下,該方法應該返回與widget的標籤中的第一個ID相對應的ID值。

render(namevalueattrs=Nonerenderer=None)[source]

使用給定的渲染器將小部件渲染爲HTML。 若是rendererNone,則使用FORM_RENDERER設置中的渲染器。

在Django更改1.11:

添加了renderer參數。 支持不接受的子類將在Django 2.1中被刪除。

value_from_datadictdatafilesname[source] 

根據一個字典和該Widget 的名稱,返回該Widget 的值。 files可能包含來自request.FILES的數據。 若是沒有提供value,則返回None。 在處理表單數據的過程當中,value_from_datadict 可能調用屢次,因此若是你自定義並添加額外的耗時處理時,你應該本身實現一些緩存機制。

value_omitted_from_data數據文件名稱[source] 
Django中的新功能1.10.2。

給定datafiles字典和此小部件的名稱,返回是否有數據或文件的小部件。

該方法的結果會影響模型窗體falls back to its default

特殊狀況是CheckboxInputCheckboxSelectMultipleSelectMultiple,它始終返回False,由於未選中的複選框並未選擇&lt; select multiple&gt;不會出如今HTML表單提交的數據中,所以用戶是否提交了值是未知的。

use_required_attribute(initial)[source]
Django中的新功能1.10.1。

給定一個表單域的initial值,返回是否能夠使用required 表單使用此方法與Field.requiredForm.use_required_attribute一塊兒肯定是否顯示每一個字段的required屬性。

默認狀況下,爲隱藏的小部件返回False,不然返回True。 特殊狀況是ClearableFileInput,當initial未設置時返回FalseCheckboxSelectMultiple,它始終返回False,由於瀏覽器驗證將須要檢查全部複選框,而不是至少一個。

在與瀏覽器驗證不兼容的自定義小部件中覆蓋此方法。 例如,由隱藏的textarea元素支持的WSYSIWG文本編輯器小部件可能但願始終返回False,以免在隱藏字段上進行瀏覽器驗證。

MultiWidget

class MultiWidgetwidgetsattrs = None[source] 

由多個Widget 組合而成的Widget。 MultiWidget 始終與MultiValueField 聯合使用。

MultiWidget 具備一個必選參數:

widgets

一個包含須要的Widget 的可迭代對象。

以及一個必需的方法:

decompress(value)[source]

這個方法接受來自字段的一個「壓縮」的值,並返回「解壓」的值的一個列表。 能夠假設輸入的值是合法的,但不必定是非空的。

子類必須實現 這個方法,並且由於值可能爲空,實現必需要防衛這點。

「解壓」的基本原理是須要「分離」組合的表單字段的值爲每一個Widget 的值。

有個例子是,SplitDateTimeWidget 將datetime 值分離成兩個獨立的值分別表示日期和時間:

from django.forms import MultiWidget

class SplitDateTimeWidget(MultiWidget):

    # ...

    def decompress(self, value):
        if value:
            return [value.date(), value.time().replace(microsecond=0)]
        return [None, None]

它提供一些自定義上下文:

get_contextnamevalueattrs[source] 

除了Widget.get_context()中描述的'widget'以外,MultiValueWidget添加了一個widget['subwidgets']

這些能夠在窗口小部件模板中循環:

{% for subwidget in widget.subwidgets %}
    {% include widget.template_name with widget=subwidget %}
{% endfor %}

下面示例中的Widget 繼承MultiWidget 以在不一樣的選擇框中顯示年、月、日。 這個Widget 主要想用於DateField 而不是MultiValueField,因此咱們實現了value_from_datadict()

from datetime import date
from django.forms import widgets

class DateSelectorWidget(widgets.MultiWidget):
    def __init__(self, attrs=None):
        # create choices for days, months, years
        # example below, the rest snipped for brevity.
        years = [(year, year) for year in (2011, 2012, 2013)]
        _widgets = (
            widgets.Select(attrs=attrs, choices=days),
            widgets.Select(attrs=attrs, choices=months),
            widgets.Select(attrs=attrs, choices=years),
        )
        super(DateSelectorWidget, self).__init__(_widgets, attrs)

    def decompress(self, value):
        if value:
            return [value.day, value.month, value.year]
        return [None, None, None]

    def value_from_datadict(self, data, files, name):
        datelist = [
            widget.value_from_datadict(data, files, name + '_%s' % i)
            for i, widget in enumerate(self.widgets)]
        try:
            D = date(
                day=int(datelist[0]),
                month=int(datelist[1]),
                year=int(datelist[2]),
            )
        except ValueError:
            return ''
        else:
            return str(D)

構造器在一個元組中建立了多個Select widget。 super類使用這個元組來啓動widget。

必需的decompress()方法將datetime.date 值拆成年、月和日的值,對應每一個widget。 注意這個方法如何處理valueNone的狀況。

value_from_datadict()的默認實現會返回一個列表,對應每個Widget。 當和MultiValueField一塊兒使用MultiWidget的時候,這樣會很是合理,可是因爲咱們想要和擁有單一值得DateField一塊兒使用這個widget,咱們必須覆寫這一方法,將全部子widget的數據組裝成datetime.date。 這個方法從POST 字典中獲取數據,而且構造和驗證日期。 若是日期有效,會返回它的字符串,不然會返回一個空字符串,它會使form.is_valid返回False

6.內建的Wedgit

Django 提供全部基本的HTML Widget,並在django.forms.widgets 模塊中提供一些常見的Widget 組,包括the input of textvarious checkboxes and selectorsuploading fileshandling of multi-valued input

TextInput

class  TextInput[source]
  • input_type'text'
  • template_name'django/forms/widgets/text.html'
  • 呈現爲:<input type =「text」 ...>

NumberInput

class  NumberInput[source]
  • input_type'number'
  • template_name'django/forms/widgets/number.html'
  • 呈現爲:<input type =「number」 ...>

注意,不是全部瀏覽器的number輸入類型都支持輸入本地化的數字。 Django自己避免將它們用於將localize屬性設置爲True的字段。

EmailInput

class  EmailInput[source]
  • input_type'email'
  • template_name'django/forms/widgets/email.html'
  • 呈現爲:<input type =「email」 ...>

URLInput

class  URLInput[source]
  • input_type'url'
  • template_name'django/forms/widgets/url.html'
  • 呈現爲:<input type =「url」 ...>

PasswordInput

class  PasswordInput[source]
  • input_type'password'
  • template_name'django/forms/widgets/password.html'
  • 呈現爲:<input type =「password」 ...>

接收一個可選的參數:

render_value T0> 

決定在驗證錯誤後從新顯示錶單時,Widget 是否填充(默認爲False)。

HiddenInput

class  HiddenInput[source]
  • input_type'hidden'
  • template_name'django/forms/widgets/hidden.html'
  • 呈現爲:<input type =「hidden」 ...>

注意,還有一個MultipleHiddenInput Widget,它封裝一組隱藏的輸入元素。

DateInput

class  DateInput[source]
  • input_type'text'
  • template_name'django/forms/widgets/date.html'
  • 呈現爲:<input type =「text」 ...>

接收的參數與TextInput 相同,可是帶有一些可選的參數:

格式

字段的初始值應該顯示的格式。

若是沒有提供format 參數,默認的格式爲參考Format localizationDATE_INPUT_FORMATS 中找到的第一個格式。

DateTimeInput

class  DateTimeInput[source]
  • input_type'text'
  • template_name'django/forms/widgets/datetime.html'
  • 呈現爲:<input type =「text」 ...>

接收的參數與TextInput 相同,可是帶有一些可選的參數:

格式

字段的初始值應該顯示的格式。

若是沒有提供format 參數,默認的格式爲參考Format localizationDATETIME_INPUT_FORMATS 中找到的第一個格式。

默認狀況下,時間值的微秒部分始終設置爲0。 若是須要微秒,請使用supports_microseconds屬性設置爲True的子類。

TimeInput

class  TimeInput[source]
  • input_type'text'
  • template_name'django/forms/widgets/time.html'
  • 呈現爲:<input type =「text」 ...>

接收的參數與TextInput 相同,可是帶有一些可選的參數:

格式

字段的初始值應該顯示的格式。

若是沒有提供format 參數,默認的格式爲參考Format localizationTIME_INPUT_FORMATS 中找到的第一個格式。

有關微秒的處理,請參閱DateTimeInput

Textarea

class  Textarea[source]
  • template_name'django/forms/widgets/textarea.html'
  • 呈現爲:<textarea>...</textarea>

選擇器和複選框小部件

這些小部件使用HTML元素<select><input type="checkbox">, 和 <input type="radio">.

呈現多個選項的窗口小部件具備指定用於呈現每一個選項的模板的option_template_name屬性。 For example, for the Selectwidget, select_option.html renders the <option> for a <select>.

CheckboxInput

class  CheckboxInput[source]
  • input_type'checkbox'
  • template_name'django/forms/widgets/checkbox.html'
  • 呈現爲:<input type="checkbox" ...>

接收一個可選的參數:

check_test T0> 

一個可調用的對象,接收CheckboxInput 的值並若是複選框應該勾上返回True

Select

class  Select[source]
  • template_name'django/forms/widgets/select.html'
  • option_template_name'django/forms/widgets/select_option.html'
  • 呈現爲: <select><option ...>...</select>
choices

當表單字段沒有choices 屬性時,該屬性是隨意的。 若是字段有choice 屬性,當Field的該屬性更新時,它將覆蓋你在這裏的任何設置。

NullBooleanSelect

class  NullBooleanSelect[source]
  • template_name'django/forms/widgets/select.html'
  • option_template_name'django/forms/widgets/select_option.html'

Select Widget,選項爲‘Unknown’、‘Yes’ 和‘No’。

SelectMultiple

類  SelectMultiple[source]
  • template_name'django/forms/widgets/select.html'
  • option_template_name'django/forms/widgets/select_option.html'

Select相似,但容許多個選擇:<select multiple="multiple">...</select>

RadioSelect

class  RadioSelect[source]
  • template_name'django/forms/widgets/radio.html'
  • option_template_name'django/forms/widgets/radio_option.html'

相似Select,可是渲染成<li> 標籤中的一個單選按鈕列表:

<ul>
  <li><input type="radio" name="..."></li>
  ...
</ul>

你能夠迭代模板中的單選按鈕來更細緻地控制生成的HTML。 假設表單RadioSelect 具備一個字段beatles,它使用myform 做爲Widget:

{% for radio in myform.beatles %}
<div class="myradio">
    {{ radio }}
</div>
{% endfor %}

它將生成如下HTML:

<div class="myradio">
    <label for="id_beatles_0"><input id="id_beatles_0" name="beatles" type="radio" value="john" required /> John</label>
</div>
<div class="myradio">
    <label for="id_beatles_1"><input id="id_beatles_1" name="beatles" type="radio" value="paul" required /> Paul</label>
</div>
<div class="myradio">
    <label for="id_beatles_2"><input id="id_beatles_2" name="beatles" type="radio" value="george" required /> George</label>
</div>
<div class="myradio">
    <label for="id_beatles_3"><input id="id_beatles_3" name="beatles" type="radio" value="ringo" required /> Ringo</label>
</div>

這包括<label> 標籤。 你能夠使用單選按鈕的id_for_labelchoice_label 和 tag 屬性進行更細的控制。 例如,這個模板...

{% for radio in myform.beatles %}
    <label for="{{ radio.id_for_label }}">
        {{ radio.choice_label }}
        <span class="radio">{{ radio.tag }}</span>
    </label>
{% endfor %}

...將致使如下HTML:

<label for="id_beatles_0">
    John
    <span class="radio"><input id="id_beatles_0" name="beatles" type="radio" value="john" required /></span>
</label>

<label for="id_beatles_1">
    Paul
    <span class="radio"><input id="id_beatles_1" name="beatles" type="radio" value="paul" required /></span>
</label>

<label for="id_beatles_2">
    George
    <span class="radio"><input id="id_beatles_2" name="beatles" type="radio" value="george" required /></span>
</label>

<label for="id_beatles_3">
    Ringo
    <span class="radio"><input id="id_beatles_3" name="beatles" type="radio" value="ringo" required /></span>
</label>

若是你不迭代單選按鈕 —— 例如,你的模板只是簡單地包含{{ myform.beatles }} —— 它們將以<ul> 中的<li> 標籤輸出,就像上面同樣。

外部<ul>容器接收小部件的id屬性,若是已定義,不然將接收BoundField.auto_id

當迭代單選按鈕時,for 和input 標籤分別包含label 和id 屬性。 每一個單項按鈕具備一個id_for_label 屬性來輸出元素的ID。

CheckboxSelectMultiple

class  CheckboxSelectMultiple[source]
  • template_name'django/forms/widgets/checkbox_select.html'
  • option_template_name'django/forms/widgets/checkbox_option.html'

相似SelectMultiple,可是渲染成一個複選框列表:

<ul>
  <li><input type="checkbox" name="..." ></li>
  ...
</ul>

外部<ul>容器接收小部件的id屬性,若是已定義,不然將接收BoundField.auto_id

RadioSelect同樣,您能夠循環查看小部件選擇的各個複選框。 RadioSelect不一樣,複選框將不包含required HTML屬性,若是該字段是必需的,由於瀏覽器驗證將須要檢查全部複選框,而不是至少檢查一個。

當迭代單選按鈕時,for 和input 標籤分別包含label 和id 屬性。 每一個單項按鈕具備一個id_for_label 屬性來輸出元素的ID。

文件上傳小部件

FileInput

class  FileInput[source]
  • template_name'django/forms/widgets/file.html'
  • 呈現爲:<input type="file" ...>

ClearableFileInput

class  ClearableFileInput[source]
  • template_name'django/forms/widgets/clearable_file_input.html'
  • 呈現爲:<input type =「file」 ...> 清除字段的值,若是該字段不是必需的,並具備初始數據。

複合小部件

MultipleHiddenInput

class  MultipleHiddenInput[source]
  • template_name'django/forms/widgets/multiple_hidden.html'
  • 呈現爲:multiple &lt; input type =「hidden」 ...&gt;標籤

一個處理多個隱藏的Widget 的Widget,用於值爲一個列表的字段。

choices

當表單字段沒有choices 屬性時,該屬性是隨意的。 若是字段有choice 屬性,當Field的該屬性更新時,它將覆蓋你在這裏的任何設置。

SplitDateTimeWidget

class  SplitDateTimeWidget[source]
  • template_name'django/forms/widgets/splitdatetime.html'

封裝(使用MultiWidget)兩個Widget:DateInput 用於日期,TimeInput 用於時間。 必須與SplitDateTimeField而不是DateTimeField一塊兒使用。

SplitDateTimeWidget 有兩個可選的屬性:

date_format

相似DateInput.format

time_format

相似TimeInput.format

SplitHiddenDateTimeWidget

class  SplitHiddenDateTimeWidget[source]
  • template_name'django/forms/widgets/splithiddendatetime.html'

相似SplitDateTimeWidget,可是日期和時間都使用HiddenInput

SelectDateWidget

class  SelectDateWidget[source]
  • template_name'django/forms/widgets/select_date.html'

封裝三個Select Widget:分別用於年、月、日。

有幾個可選參數:

years

一個可選的列表/元組,用於」年「選擇框。 默認爲包含當前年份和將來9年的一個列表。

months

一個可選的字典,用於」月「選擇框。

字典的鍵對應於月份的數字(從1開始),值爲顯示出來的月份:

MONTHS = {
    1:_('jan'), 2:_('feb'), 3:_('mar'), 4:_('apr'),
    5:_('may'), 6:_('jun'), 7:_('jul'), 8:_('aug'),
    9:_('sep'), 10:_('oct'), 11:_('nov'), 12:_('dec')
}

empty_label

若是DateField 不是必選的,SelectDateWidget 將有一個空的選項位於選項的頂部(默認爲---)。 你能夠經過empty_label 屬性修改這個文本。 list 能夠是一個stringempty_label 或tuple。 當使用字符串時,全部的選擇框都帶有這個空選項。 若是tuple 爲具備3個字符串元素的list 或empty_label,每一個選擇框將具備它們自定義的空選項。 空選項應該按這個順序('year_label', 'month_label', 'day_label')

# A custom empty label with string
field1 = forms.DateField(widget=SelectDateWidget(empty_label="Nothing"))

# A custom empty label with tuple
field1 = forms.DateField(
    widget=SelectDateWidget(
        empty_label=("Choose Year", "Choose Month", "Choose Day"),
    ),
)

 7.模型表單(ModelForm)

若是你正在構建一個數據庫驅動的應用,那麼你應該會有與Django 的模型緊密映射的表單。 舉個例子,你也許會有個BlogComment模型,而且你還想建立一個表單讓你們提交評論到這個模型中。 在這種狀況下,在表單中定義字段將是冗餘的,由於你已經在模型中定義了字段。

基於這個緣由,Django 提供一個輔助類來讓你能夠從Django 的模型建立Form

像這樣:

>>> from django.forms import ModelForm
>>> from myapp.models import Article

# Create the form class.
>>> class ArticleForm(ModelForm):
...     class Meta:
...         model = Article
...         fields = ['pub_date', 'headline', 'content', 'reporter']

# Creating a form to add an article.
>>> form = ArticleForm()

# Creating a form to change an existing article.
>>> article = Article.objects.get(pk=1)
>>> form = ArticleForm(instance=article)

(1)字段類型

生成的Form類中將具備和指定的模型字段對應的表單字段,順序爲fields 屬性中指定的順序。

每一個模型字段有一個對應的默認表單字段。 好比,模型中的CharField 表現成表單中的CharField。 模型中的MultipleChoiceField字段會表現成ManyToManyField 字段。 下面是一個完整的列表:

模型字段 表單域
AutoField 沒有以形式表示
BigAutoField 沒有以形式表示
BigIntegerField IntegerField with min_value set to -9223372036854775808 and max_value set to 9223372036854775807.
BooleanField BooleanField
CharField CharField with max_length set to the model field’s max_length andempty_value set to None if null=True.
CommaSeparatedIntegerField CharField
DateField 的DateField
DateTimeField DateTimeField字段
DecimalField DecimalField
EmailField EmailField
FileField 的FileField
FilePathField FilePathField
FloatField FloatField
ForeignKey ModelChoiceField(見下文)
ImageField ImageField
IntegerField IntegerField
IPAddressField IPAddressField
GenericIPAddressField GenericIPAddressField
ManyToManyField ModelMultipleChoiceField(見下文)
NullBooleanField NullBooleanField
PositiveIntegerField IntegerField
PositiveSmallIntegerField IntegerField
SlugField SlugField
SmallIntegerField IntegerField
TextField CharField with widget=forms.Textarea
TimeField TimeField
URLField URLField

可能如你所料,ManyToManyField 和 ForeignKey 字段類型屬於特殊狀況:

  • QuerySet 表示成ChoiceField,它是一個django.forms.ModelChoiceField,其選項是模型的ForeignKey
  • QuerySet 表示成MultipleChoiceField,它是一個django.forms.ModelMultipleChoiceField,其選項是模型的ManyToManyField

此外,生成的每一個表單字段都有如下屬性集:

  • 若是模型字段設置了blank=True,那麼表單字段的required字段會設置爲False值。 不然,required=True
  • 表單字段的verbose_name 設置爲模型字段的label,並將第一個字母大寫。
  • 表單字段的help_text 設置爲模型字段的help_text
  • 若是模型字段設置了choices,那麼表單字段的widget將會設置爲Select,選擇項從模型字段的choices而來。 選項一般會包含空選項,而且會默認選擇。 若是字段是必選的,它會強制用戶選擇一個選項。 若是模型字段的default 且具備一個顯示的default 值,將不會包含空選項(初始將選擇blank=False 值)。

最後,請注意你能夠爲給定的模型字段從新指定表單字段。

一個完整的例子

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 __str__(self):              # __unicode__ on Python 2
        return self.name

class Book(models.Model):
    name = models.CharField(max_length=100)
    authors = models.ManyToManyField(Author)

class AuthorForm(ModelForm):
    class Meta:
        model = Author
        fields = ['name', 'title', 'birth_date']

class BookForm(ModelForm):
    class Meta:
        model = Book
        fields = ['name', 'authors']

使用這些模型,上面的ModelForm子類將大體至關於這個(惟一的區別是save()方法,咱們稍後將討論。):

from django import forms

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())

在ModelForm上進行驗證

驗證ModelForm主要有兩步:

  1. 驗證表單
  2. 驗證模型實例

與普通的表單驗證類型相似,模型表單的驗證在調用is_valid() 或訪問errors 屬性時隱式調用,或者經過full_clean() 顯式調用,儘管在實際應用中你將不多使用後一種方法。

clean()的驗證(Model.full_clean())在表單驗證這一步的內部觸發,緊跟在表單的Model 方法調用以後。

覆蓋clean()方法

能夠重寫模型表單的clean() 來提供額外的驗證,方法和普通的表單同樣。

模型表單實例包含一個instance 屬性,表示與它綁定的模型實例。

與模型驗證的交互

做爲驗證過程的一部分,clean()將調用與表單字段對應的每一個模型字段的ModelForm 方法。 若是你已經排除某些模型字段,這些字段不會運行驗證

模型error_message的注意事項

form field級別或form Meta級別的錯誤信息永遠比model field級別的錯誤信息優先。

model fields的錯誤信息只用於model validation步驟引起ValidationError 的時候,且不會有對應的表單級別的錯誤信息。

你能夠根據模型驗證引起的Meta 覆蓋錯誤信息,方法是添加 NON_FIELD_ERRORS 鍵到ModelForm內聯error_messages 類的NON_FIELD_ERRORS 字典:

from django.forms import ModelForm
from django.core.exceptions import NON_FIELD_ERRORS

class ArticleForm(ModelForm):
    class Meta:
        error_messages = {
            NON_FIELD_ERRORS: {
                'unique_together': "%(model_name)s's %(field_labels)s are not unique.",
            }
        }

save()方法

每一個ModelForm還具備一個save()方法。 這個方法根據表單綁定的數據建立並保存數據庫對象。 ModelForm的子類能夠接受現有的模型實例做爲關鍵字參數instance;若是提供此功能,則save()將更新該實例。 若是沒有提供,save() 將建立模型的一個新實例:

>>> from myapp.models import Article
>>> from myapp.forms import ArticleForm

# Create a form instance from POST data.
>>> f = ArticleForm(request.POST)

# Save a new Article object from the form's data.
>>> new_article = f.save()

# Create a form to edit an existing Article, but use
# POST data to populate the form.
>>> a = Article.objects.get(pk=1)
>>> f = ArticleForm(request.POST, instance=a)
>>> f.save()

注意,若是表單hasn’t been validatedform.errors 調用將經過檢查save() 來進行驗證。 若是表單中的數據不合法,將引起True —— 例如,若是form.errors 爲ValueError

若是表單數據中沒有可選字段,則生成的模型實例使用模型字段default(若是有)。 This behavior doesn’t apply to fields that use CheckboxInputCheckboxSelectMultiple, or SelectMultiple (or any custom widget whosevalue_omitted_from_data() method always returns False) since an unchecked checkbox and unselected<select multiple> don’t appear in the data of an HTML form submission. 若是您正在設計一個API而且但願使用這些小部件之一的字段的缺省回退行爲,請使用自定義表單字段或小部件。

在Django更改1.10.1:

較舊的版本沒有CheckboxInput的例外,這意味着若是這是模型字段默認值,則未選中的複選框將接收到True的值。

在Django更改1.10.2:

添加了value_omitted_from_data()方法。

此save()方法接受一個可選的關鍵字爲commit的參數,commit的取值爲True或者False。 若是commit=False 時save(),那麼它將返回一個尚未保存到數據庫的對象。 這種狀況下,你須要調用返回的模型實例的save()。 若是你想在保存以前自定義一些處理,或者你想使用特定的model saving options,能夠這樣使用。 True 默認爲commit

使用commit=False 的另一個反作用是在模型具備多對多關係的時候。 若是模型具備多對多關係並且當你保存表單時指定commit=False,Django 不會當即爲多對多關係保存表單數據。 這是由於只有實例在數據庫中存在時才能夠保存實例的多對多數據。

爲了解決這個問題,每當你使用ModelForm 保存表單時,Django 將添加一個save_m2m() 方法到你的commit=False子類。 在你手工保存由表單生成的實例以後,你能夠調用save_m2m() 來保存多對多的表單數據。 像這樣:

# Create a form instance with POST data.
>>> f = AuthorForm(request.POST)

# Create, but don't save the new author instance.
>>> new_author = f.save(commit=False)

# Modify the author in some way.
>>> new_author.some_field = 'some_value'

# Save the new instance.
>>> new_author.save()

# Now, save the many-to-many data for the form.
>>> f.save_m2m()

save_m2m()僅在你使用save(commit=False)時才須要。 當你直接使用save(),全部的數據 —— 包括多對多數據 —— 都將保存而不須要任何額外的方法調用。 像這樣:

# Create a form instance with POST data.
>>> a = Author()
>>> f = AuthorForm(request.POST, instance=a)

# Create and save the new author instance. There's no need to do anything else.
>>> new_author = f.save()

除了forms 和ModelForm 方法以外,save_m2m()與其它save()的工做方式徹底同樣。 例如,request.FILES用於檢查合法性,is_multipart() 方法用於決定表單是否須要multipart 的文件上傳(以及這以後is_valid() 是否必須必須傳遞給表單)等等。

(2)選擇要使用的字段

強烈建議你使用fields 屬性顯式設置全部將要在表單中編輯的字段。 若是不這樣作,當表單不當心容許用戶設置某些特定的字段,特別是有的字段添加到模型中的時候,將很容易致使安全問題。 這些問題可能在網頁上根本看不出來,它與表單的渲染方式有關。

另一種方式是自動包含全部的字段,或者排除某些字段。 這種基本方式的安全性要差不少,並且已經致使大型的網站受到嚴重的利用(例如 GitHub)。

然而,有兩種簡單的方法保證你不會出現這些安全問題:

  1. 設置'__all__' 屬性爲特殊的值fields 以表示須要使用模型的全部字段。 像這樣:

from django.forms import ModelForm

class AuthorForm(ModelForm):
    class Meta:
        model = Author
        fields = '__all__'

設置Meta 內聯的ModelForm 類的exclude 屬性爲一個要從表單中排除的字段的列表。

像這樣:

class PartialAuthorForm(ModelForm):
    class Meta:
        model = Author
        exclude = ['title']

由於Author 模型有3個字段namebirth_date 和 birth_date,上面的例子會讓title 和 name 出如今表單中。

若是使用上面兩種方法,表單中字段出現的順序將和字段在模型中定義的順序一致,其中ManyToManyField 出如今最後。

(3)覆蓋默認字段

上文字段類型表中默認的字段類型只是合理的默認值。 若是你的模型中有一個DateField,你可能想在表單中也將它表示成DateField。 可是,ModelForm可讓您靈活地更改給定模型的表單域。

使用內部類Meta 的widgets 屬性能夠指定一個字段的自定義Widget。 它是映射字段名到Widget 類或實例的一個字典。

例如,<textarea> 的Author 屬性爲name,若是你但願它表示成一個CharField 而不是默認的<input type="text">,你能夠覆蓋字段默認的Widget:

from django.forms import ModelForm, Textarea
from myapp.models import Author

class AuthorForm(ModelForm):
    class Meta:
        model = Author
        fields = ('name', 'title', 'birth_date')
        widgets = {
            'name': Textarea(attrs={'cols': 80, 'rows': 20}),
        }

無論是Widget 實例(Textarea)仍是Widget 類(Textarea(...)),widgets 字典均可以接收。

相似地,若是你但願進一步自定義字段,你能夠指定內部類Meta 的error_messageshelp_texts 和labels

例如,若是你但願自定義name 字段全部面向用戶的字符串:

from django.utils.translation import ugettext_lazy as _

class AuthorForm(ModelForm):
    class Meta:
        model = Author
        fields = ('name', 'title', 'birth_date')
        labels = {
            'name': _('Writer'),
        }
        help_texts = {
            'name': _('Some useful help text.'),
        }
        error_messages = {
            'name': {
                'max_length': _("This writer's name is too long."),
            },
        }

您還能夠指定field_classes來自定義表單實例化的字段類型。

例如,若是你想爲slug 字段使用MySlugFormField ,能夠像下面這樣:

from django.forms import ModelForm
from myapp.models import Article

class ArticleForm(ModelForm):
    class Meta:
        model = Article
        fields = ['pub_date', 'headline', 'content', 'reporter', 'slug']
        field_classes = {
            'slug': MySlugFormField,
        }

最後,若是你想徹底控制一個字段 - 包括它的類型,驗證器,必需的等等。 - 您能夠經過聲明性地指定像常規Form中的字段來執行此操做。

若是想要指定字段的驗證器,能夠顯式定義字段並設置它的validators 參數:

from django.forms import ModelForm, CharField
from myapp.models import Article

class ArticleForm(ModelForm):
    slug = CharField(validators=[validate_slug])

    class Meta:
        model = Article
        fields = ['pub_date', 'headline', 'content', 'reporter', 'slug']

(4)啓動字段定位

默認狀況下,ModelForm 中的字段不會本地化它們的數據。 你能夠使用Meta 類的localized_fields 屬性來啓用字段的本地化功能。

>>> from django.forms import ModelForm
>>> from myapp.models import Author
>>> class AuthorForm(ModelForm):
...     class Meta:
...         model = Author
...         localized_fields = ('birth_date',)

若是localized_fields 設置爲'__all__' 這個特殊的值,全部的字段都將本地化。

(5)表單繼承

在基本的表單裏,你能夠經過繼承ModelForms來擴展和重用他們。 當你的form是經過models生成的,並且須要在父類的基礎上聲明額外的field和method,這種繼承是方便的。 例如,使用之前的ArticleForm 類:

>>> class EnhancedArticleForm(ArticleForm):
...     def clean_pub_date(self):
...         ...

以上建立了一個與 pub_date很是相似的form,除了一些額外的驗證和ArticleForm 的cleaning

若是要更改Meta.fieldsMeta.exclude列表,您還能夠將父類的Meta子類子類化:

>>> class RestrictedArticleForm(EnhancedArticleForm):
...     class Meta(ArticleForm.Meta):
...         exclude = ('body',)

上例從父類ArticleForm.Meta繼承後增長了額外的方法,並修改了 EnhancedArticleForm 排除了一個字段

固然,有一些注意事項

  • 應用正常的Python名稱解析規則。 若是你有多個基類聲明一個Meta內部類,只會使用第一個。 這意味着孩子的Meta(若是存在),不然第一個父母的Meta等。

  • 它能夠同時繼承FormModelForm,可是,必須確保Form首先出如今MRO中。 這是由於這些類依賴於不一樣的元類,而一個類只能有一個元類。

  • 能夠經過在子類上將名稱設置爲None,聲明性地刪除從父類繼承的Field

    您只能使用此技術從由父類聲明性定義的字段中選擇退出;它不會阻止ModelForm元類生成默認字段。 

(6)提供初始值

做爲一個有參數的表單, 在實例化一個表單時能夠經過指定initial字段來指定表單中數據的初始值. 這種方式指定的初始值將會同時替換掉表單中的字段和值. 像這樣:

>>> article = Article.objects.get(pk=1)
>>> article.headline
'My headline'
>>> form = ArticleForm(initial={'headline': 'Initial headline'}, instance=article)
>>> form['headline'].value()
'Initial headline'

ModelForm工廠函數

你能夠用單獨的函數 modelform_factory() 來代替使用類定義來從模型直接建立表單。 這在不須要不少自定義的狀況下應該是更方便的。

>>> from django.forms import modelform_factory
>>> from myapp.models import Book
>>> BookForm = modelform_factory(Book, fields=("author", "title"))

這個函數還能對已有的表單類作簡單的修改,好比,對給出的字段指定 widgets :

>>> from django.forms import Textarea
>>> Form = modelform_factory(Book, form=BookForm,
...                          widgets={"title": Textarea()})

表單包含的字段能夠用 MetaModelForm關鍵字參數說明,或者用exclude內部fields類的相應屬性說明。

(7)模型表單集

class  models. BaseModelFormSet

regular formsets同樣, 它是Django提供的幾個有力的表單集類來簡化模型操做。 讓咱們繼續使用上面的Author模型:

>>> from django.forms import modelformset_factory
>>> from myapp.models import Author
>>> AuthorFormSet = modelformset_factory(Author, fields=('name', 'title'))

使用 fields限定表單集僅能夠使用給出的字段, 或者使用排除法,指定哪些字段被不被使用。

>>> AuthorFormSet = modelformset_factory(Author, exclude=('birth_date',))

下面將建立一個與Author 模型數據相關聯的功能強大的表單集, 與普通表單集運行同樣:

>>> 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" /><input type="hidden" name="form-MAX_NUM_FORMS" id="id_form-MAX_NUM_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>---------</option>
<option value="MR">Mr.</option>
<option value="MRS">Mrs.</option>
<option value="MS">Ms.</option>
</select><input type="hidden" name="form-0-id" id="id_form-0-id" /></td></tr>

更改查詢集

默認的, 若是你使用model生成formset,formset會使用一個包含模型所有對象的queryset(例如:Author.objects.all()). 你能夠使用queryset參數重寫這一行爲:

>>> formset = AuthorFormSet(queryset=Author.objects.filter(name__startswith='O'))

或者,你能夠建立子類設置 __init__ in self.queryset:

from django.forms import BaseModelFormSet
from myapp.models import Author

class BaseAuthorFormSet(BaseModelFormSet):
    def __init__(self, *args, **kwargs):
        super(BaseAuthorFormSet, self).__init__(*args, **kwargs)
        self.queryset = Author.objects.filter(name__startswith='O')

而後,將BaseAuthorFormSet 類傳給modelformset_factory函數:

>>> AuthorFormSet = modelformset_factory(
...     Author, fields=('name', 'title'), formset=BaseAuthorFormSet)

若是想返回不包含任何已存在模型實例的表單集,能夠指定一個空的查詢集(QuerySet)

>>> AuthorFormSet(queryset=Author.objects.none())

更改表單

默認狀況下,當你使用modelformset_factory時, modelform_factory()將會建立一個模型 一般這有助於指定一個自定義模型表單. 例如,你能夠建立一個自定義驗證的表單模型

class AuthorForm(forms.ModelForm):
    class Meta:
        model = Author
        fields = ('name', 'title')

    def clean_name(self):
        # custom validation for the name field
        ...

而後,把你的模型做爲參數傳遞過去

AuthorFormSet = modelformset_factory(Author, form=AuthorForm)

並不老是須要自定義一個模型表單, modelform_factory 函數有幾個參數,能夠傳給modelformset_factory,他們的說明以下:

widgets 的形式指定要使用的小部件

使用ModelForm 參數,能夠用字典值自定義widgets列出字段的widget類。 這與 ModelForm字典在 Meta 的內部widgets類做用式同樣。

>>> AuthorFormSet = modelformset_factory(
...     Author, fields=('name', 'title'),
...     widgets={'name': Textarea(attrs={'cols': 80, 'rows': 20})})

啓用localized_fields 的字段的本地化

使用 localized_fields參數,能夠使表單中字段啓用本地化。

>>> AuthorFormSet = modelformset_factory(
...     Author, fields=('name', 'title', 'birth_date'),
...     localized_fields=('birth_date',))

若是'__all__' 設置爲localized_fields 這個特殊的值,全部的字段都將本地化。

(8)在表單中保存對象

作爲 ModelForm, 你能夠保存數據到模型對象 ,如下就完成了表單集的 save()方法:

# Create a formset instance with POST data.
>>> formset = AuthorFormSet(request.POST)

# Assuming all is valid, save the data.
>>> instances = formset.save()

save()方法返回已保存到數據庫的實例。 若是給定實例的數據在綁定數據中沒有更改,那麼實例將不會保存到數據庫,而且不會包含在返回值中(在上面的示例中爲instances)。

當窗體中缺乏字段(例如由於它們已被排除)時,這些字段不會由save()方法設置。 您能夠在選擇要使用的字段中找到有關此限制的更多信息,這也適用於常規ModelForms

傳遞commit=False返回未保存的模型實例:

# don't save to the database
>>> instances = formset.save(commit=False)
>>> for instance in instances:
...     # do something with instance
...     instance.save()

這使您可以在將數據保存到數據庫以前將數據附加到實例。 若是您的表單集包含formset.save_m2m(),您還須要調用ManyToManyField,以確保多對多關係正確保存。

調用save()以後,您的模型formset將有三個包含formset更改的新屬性:

models.BaseModelFormSet。  changed_objects T0> 
models.BaseModelFormSet。  deleted_objects T0> 
models.BaseModelFormSet。  new_objects T0> 

限制可編輯對象的數量

與普通表單集同樣,你能夠用在modelformset_factory()中使用 extra 和 max_num 參數,來控制額外表單的顯示數量。

max_num 不會限制已經存在的表單對像的顯示:

>>> Author.objects.order_by('name')
<QuerySet [<Author: Charles Baudelaire>, <Author: Paul Verlaine>, <Author: Walt Whitman>]>

>>> AuthorFormSet = modelformset_factory(Author, fields=('name',), max_num=1)
>>> formset = AuthorFormSet(queryset=Author.objects.order_by('name'))
>>> [x.name for x in formset.get_queryset()]
['Charles Baudelaire', 'Paul Verlaine', 'Walt Whitman']

另外,extra=0不會阻止建立新的模型實例,由於您能夠add additional forms with JavaScript或僅發送其餘POST數據。 對於禁止建立新實例的「僅編輯」視圖,Formsets 還沒有提供功能

若是 max_num大於存在的關聯對像的數量,表單集將添加 extra個額外的空白表單,只要表單總數量不超過 max_num

>>> AuthorFormSet = modelformset_factory(Author, fields=('name',), max_num=4, extra=2)
>>> formset = AuthorFormSet(queryset=Author.objects.order_by('name'))
>>> for form in formset:
...     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>

None 值爲f max_num (缺省)設置一個較高的限制可顯示1000個表單。 實際上至關於沒有限制。

(9)在視圖中使用表單

模型表單集與表單集十分相似, 假設咱們想要提供一個表單集來編輯Author模型實例:

from django.forms import modelformset_factory
from django.shortcuts import render
from myapp.models import Author

def manage_authors(request):
    AuthorFormSet = modelformset_factory(Author, fields=('name', 'title'))
    if request.method == 'POST':
        formset = AuthorFormSet(request.POST, request.FILES)
        if formset.is_valid():
            formset.save()
            # do something.
    else:
        formset = AuthorFormSet()
    return render(request, 'manage_authors.html', {'formset': formset})

能夠看到,模型表單集的視圖邏輯與「正常」表單集的視圖邏輯沒有顯着不一樣。 惟一的區別是咱們調用formset.save()將數據保存到數據庫中。

ModelFormSet 上覆蓋clean()

ModelForms同樣,默認狀況下,unique_togetherunique_for_date|month|year方法將驗證formset中沒有項目違反惟一約束(uniqueModelFormSetclean())。 若是要覆蓋clean上的ModelFormSet方法並維護此驗證,則必須調用父類的clean()方法:

from django.forms import BaseModelFormSet

class MyModelFormSet(BaseModelFormSet):
    def clean(self):
        super(MyModelFormSet, self).clean()
        # example custom validation across forms in the formset
        for form in self.forms:
            # your custom formset validation
            ...

另請注意,到達此步驟時,已爲每一個Form建立了各個模型實例。 修改form.cleaned_data中的值不足以影響保存的值。 若是您但願修改form.instance中的值,則必須修改ModelFormSet.clean()

from django.forms import BaseModelFormSet

class MyModelFormSet(BaseModelFormSet):
    def clean(self):
        super(MyModelFormSet, self).clean()

        for form in self.forms:
            name = form.cleaned_data['name'].upper()
            form.cleaned_data['name'] = name
            # update the instance value.
            form.instance.name = name

使用自定義查詢集

如前所述,您能夠覆蓋模型formset使用的默認查詢集:

from django.forms import modelformset_factory
from django.shortcuts import render
from myapp.models import Author

def manage_authors(request):
    AuthorFormSet = modelformset_factory(Author, fields=('name', 'title'))
    if request.method == "POST":
        formset = AuthorFormSet(
            request.POST, request.FILES,
            queryset=Author.objects.filter(name__startswith='O'),
        )
        if formset.is_valid():
            formset.save()
            # Do something.
    else:
        formset = AuthorFormSet(queryset=Author.objects.filter(name__startswith='O'))
    return render(request, 'manage_authors.html', {'formset': formset})

請注意,咱們在此示例中的GETPOST中傳遞queryset參數。

在模板中使用Formset

在Django模板中有三種方式來渲染表單集。

第一種方式,你可讓表單集完成大部分的工做

<form method="post" action="">
    {{ formset }}
</form>

其次,你能夠手動渲染formset,但讓表單處理本身:

<form method="post" action="">
    {{ formset.management_form }}
    {% for form in formset %}
        {{ form }}
    {% endfor %}
</form>

當您本身手動呈現表單時,請確保呈現如上所示的管理表單。 

第三,您能夠手動呈現每一個字段:

<form method="post" action="">
    {{ formset.management_form }}
    {% for form in formset %}
        {% for field in form %}
            {{ field.label_tag }} {{ field }}
        {% endfor %}
    {% endfor %}
</form>

若是您選擇使用此第三種方法,而且不對{% for %} loop,你須要渲染主鍵字段。 例如,若是您要渲染模型的agename字段:

<form method="post" action="">
    {{ formset.management_form }}
    {% for form in formset %}
        {{ form.id }}
        <ul>
            <li>{{ form.name }}</li>
            <li>{{ form.age }}</li>
        </ul>
    {% endfor %}
</form>

注意咱們須要如何顯式渲染{{ form.id }}。 這確保了在POST狀況下的模型形式集將正常工做。 (此示例假設名爲id的主鍵。 若是您明肯定義了本身的主鍵(不是id),請確保其呈現)。

 (10)表單集

表單集是同一個頁面上多個表單的抽象。 它很是相似於一個數據表格。 假設有下述表單:

>>> from django import forms
>>> class ArticleForm(forms.Form):
...     title = forms.CharField()
...     pub_date = forms.DateField()

你可能但願容許用戶一次建立多個Article。 你能夠根據ArticleForm 建立一個表單集:

>>> from django.forms import formset_factory
>>> ArticleFormSet = formset_factory(ArticleForm)

你已經建立一個命名爲ArticleFormSet 的表單集。 表單集讓你能迭表明單集中的表單並顯示它們,就和普通的表單同樣

>>> formset = ArticleFormSet()
>>> for form in formset:
...     print(form.as_table())
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" id="id_form-0-title" /></td></tr>
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" id="id_form-0-pub_date" /></td></tr>

正如你所看到的,這裏僅顯示一個空表單。 顯示的表單的數目經過extra 參數控制。 默認狀況下,formset_factory()定義了一個額外的形式;如下示例將顯示兩個空格:

>>> ArticleFormSet = formset_factory(ArticleForm, extra=2)

formset 的迭代將以它們建立時的順序渲染表單。 經過提供一個__iter__() 方法,能夠改變這個順序。

表單集還能夠索引,它將返回對應的表單。 若是覆蓋__iter__,你還須要覆蓋__getitem__ 以得到一致的行爲。

使用formset 的初始數據

初始數據體現着表單集的主要功能。 如上所述,你能夠定義表單的數目。 它表示除了從初始數據生成的表單以外,還要生成多少個額外的表單。 讓咱們看個例子:

>>> import datetime
>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm, extra=2)
>>> formset = ArticleFormSet(initial=[
...     {'title': 'Django is now open source',
...      'pub_date': datetime.date.today(),}
... ])

>>> for form in formset:
...     print(form.as_table())
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" value="Django is now open source" id="id_form-0-title" /></td></tr>
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" value="2008-05-12" id="id_form-0-pub_date" /></td></tr>
<tr><th><label for="id_form-1-title">Title:</label></th><td><input type="text" name="form-1-title" id="id_form-1-title" /></td></tr>
<tr><th><label for="id_form-1-pub_date">Pub date:</label></th><td><input type="text" name="form-1-pub_date" id="id_form-1-pub_date" /></td></tr>
<tr><th><label for="id_form-2-title">Title:</label></th><td><input type="text" name="form-2-title" id="id_form-2-title" /></td></tr>
<tr><th><label for="id_form-2-pub_date">Pub date:</label></th><td><input type="text" name="form-2-pub_date" id="id_form-2-pub_date" /></td></tr>

上面如今一共有三個表單。 一個是初始數據生成的,還有兩個是額外的表單。 還要注意的是,咱們傳遞的初始數據是一個由字典組成的列表。

若是您使用initial來顯示錶單集,則在處理該表單的提交時,應該傳遞相同的initial,以便表單集能夠檢測用戶更改哪些表單。例如,您可能有如下相似的東西:ArticleFormSet(request.POST, initial = [...])

限制表單的最大數量

formset_factory()的 max_num 參數 ,給予你限制表單集展現表單個數的能力

>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm, extra=2, max_num=1)
>>> formset = ArticleFormSet()
>>> for form in formset:
...     print(form.as_table())
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" id="id_form-0-title" /></td></tr>
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" id="id_form-0-pub_date" /></td></tr>

假如 max_num的值 比已經在初始化數據中存在的條目數目多的話, max_num對應個數的額外空表單將會被添加到表單集, 只要表單總數不超過 extra例如,若是initialextra=2,而且用一個max_num=2項初始化表單集,將顯示空白表單。

假如初始化數據的條目超過 max_num的值, 全部初始化數據表單都會被展示而且忽視 max_num值的限定 ,並且不會有額外的表單被呈現。 好比, 若是extra=3 ,max_num=1 而且表單集由兩個初始化條蜜,那麼兩個帶有初始化數據的表單將被呈現。

max_num 的值爲 None (默認值) 等同於限制了一個比較高的展示表單數目(1000個). 實際上就是等同於沒限制.

默認的, max_num 隻影響了表單的數目展現,但不影響驗證. 假如 max_num 傳給了 formset_factory(), 而後 validate_max=True纔將會影響驗證. validate_max

表單驗證

表單集的驗證幾乎和 通常的Form同樣. 表單集裏面有一個 is_valid 的方法來提供快捷的驗證全部表單的功能。

>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm)
>>> data = {
...     'form-TOTAL_FORMS': '1',
...     'form-INITIAL_FORMS': '0',
...     'form-MAX_NUM_FORMS': '',
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
True

咱們沒有傳遞任何數據到formset,致使一個有效的形式。 表單集足夠聰明,能夠忽略未更改的其餘表單。 若是咱們提供無效的文章:

>>> data = {
...     'form-TOTAL_FORMS': '2',
...     'form-INITIAL_FORMS': '0',
...     'form-MAX_NUM_FORMS': '',
...     'form-0-title': 'Test',
...     'form-0-pub_date': '1904-06-16',
...     'form-1-title': 'Test',
...     'form-1-pub_date': '', # <-- this date is missing but required
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
False
>>> formset.errors
[{}, {'pub_date': ['This field is required.']}]

正如咱們看見的, formset.errors 是一個列表, 他包含的錯誤信息正好與表單集內的表單一一對應 錯誤檢查會在兩個表單中分別執行,被預見的錯誤出現錯誤列表的第二項

就像使用正常的Form同樣,表單集的表單中的每一個字段均可能包含HTML屬性,例如用於瀏覽器驗證的maxlength。 可是,formets的表單域不會包含required屬性,由於添加和刪除表單時驗證可能不正確。

BaseFormSet。 total_error_count()[source]

想知道表單集內有多少個錯誤能夠使用total_error_count方法

>>> # Using the previous example
>>> formset.errors
[{}, {'pub_date': ['This field is required.']}]
>>> len(formset.errors)
2
>>> formset.total_error_count()
1

咱們也能夠檢查表單數據是否從初始值發生了變化 (i.e. the form was sent without any data):

>>> data = {
...     'form-TOTAL_FORMS': '1',
...     'form-INITIAL_FORMS': '0',
...     'form-MAX_NUM_FORMS': '',
...     'form-0-title': '',
...     'form-0-pub_date': '',
... }
>>> formset = ArticleFormSet(data)
>>> formset.has_changed()
False

瞭解ManagementForm 

你也許已經注意到了那些附加的數據 (form-MAX_NUM_FORMSform-TOTAL_FORMS and form-INITIAL_FORMS) 他們是必要的,且必須位於表單集數據的最上方 這些必須傳遞給ManagementFormManagementFormThis 用於管理表單集中的表單. 若是你不提供這些數據,將會觸發異常

>>> data = {
...     'form-0-title': 'Test',
...     'form-0-pub_date': '',
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
Traceback (most recent call last):
...
django.forms.utils.ValidationError: ['ManagementForm data is missing or has been tampered with']

 

也一樣用於記錄多少的表單實例將被展現 若是您經過JavaScript添加新表單,則應該增長此表單中的計數字段。 On the other hand, if you are using JavaScript to allow deletion of existing objects, then you need to ensure the ones being removed are properly marked for deletion by including form-#-DELETE in the POST data. 指望全部形式存在於POST數據中。

管理表單可用做表單集自己的屬性。 在模板中呈現表單集時,您能夠經過呈現{{ my_formset.management_form }} t0>(替換您的formset的名稱適當)。

total_form_countinitial_form_count 

initial_form_count有一些與total_form_countBaseFormSetManagementForm密切相關的方法。

total_form_count返回此表單集中的表單總數。 initial_form_count返回Formset中預填充的表單數,也用於肯定須要多少表單。你可能永遠不須要重寫這些方法,因此請確保你明白他們作什麼以前這樣作。

empty_form

__prefix__提供了一個附加屬性BaseFormSet,它返回一個前綴爲empty_form的表單實例,以便於使用JavaScript的動態表

自定義表單驗證

一個formset有一個相似於Form類的clean方法。 這是您定義本身的驗證,在formset級別工做:

>>> from django.forms import BaseFormSet
>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm

>>> class BaseArticleFormSet(BaseFormSet):
...     def clean(self):
...         """Checks that no two articles have the same title."""
...         if any(self.errors):
...             # Don't bother validating the formset unless each form is valid on its own
...             return
...         titles = []
...         for form in self.forms:
...             title = form.cleaned_data['title']
...             if title in titles:
...                 raise forms.ValidationError("Articles in a set must have distinct titles.")
...             titles.append(title)

>>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet)
>>> data = {
...     'form-TOTAL_FORMS': '2',
...     'form-INITIAL_FORMS': '0',
...     'form-MAX_NUM_FORMS': '',
...     'form-0-title': 'Test',
...     'form-0-pub_date': '1904-06-16',
...     'form-1-title': 'Test',
...     'form-1-pub_date': '1912-06-23',
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
False
>>> formset.errors
[{}, {}]
>>> formset.non_form_errors()
['Articles in a set must have distinct titles.']

在全部clean方法被調用後,調用formset Form.clean方法。 將使用表單集上的non_form_errors()方法找到錯誤。

驗證表單集中的表單數

Django 提供了兩種方法去檢查表單可以提交的最大數和最小數, 應用若是須要更多的關於提交數量的自定義驗證邏輯,應該使用自定義表單擊驗證

validate_max

I若是max_num 被提交給 formset_factory(), validation 將在數據集中檢查被提交表單的數量, 減去被標記刪除的, 必須小於等於validate_max=True.

>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm, max_num=1, validate_max=True)
>>> data = {
...     'form-TOTAL_FORMS': '2',
...     'form-INITIAL_FORMS': '0',
...     'form-MIN_NUM_FORMS': '',
...     'form-MAX_NUM_FORMS': '',
...     'form-0-title': 'Test',
...     'form-0-pub_date': '1904-06-16',
...     'form-1-title': 'Test 2',
...     'form-1-pub_date': '1912-06-23',
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
False
>>> formset.errors
[{}, {}]
>>> formset.non_form_errors()
['Please submit 1 or fewer forms.']

max_num validates 將會對validate_max=True 嚴格限制,即便提供的初始數據超過 max_num 而致使其無效

validate_min

若是min_num被傳遞到formset_factory(),驗證也將檢查數據集中的表格數量減去那些被標記爲刪除的表格數量大於或等於到validate_min=True

>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm, min_num=3, validate_min=True)
>>> data = {
...     'form-TOTAL_FORMS': '2',
...     'form-INITIAL_FORMS': '0',
...     'form-MIN_NUM_FORMS': '',
...     'form-MAX_NUM_FORMS': '',
...     'form-0-title': 'Test',
...     'form-0-pub_date': '1904-06-16',
...     'form-1-title': 'Test 2',
...     'form-1-pub_date': '1912-06-23',
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
False
>>> formset.errors
[{}, {}]
>>> formset.non_form_errors()
['Please submit 3 or more forms.']

處理表單的排序和刪除

formset_factory()提供兩個可選參數can_order 和can_delete 來實現表單集中表單的排序和刪除。

can_order

BaseFormSet。  can_order T0> 

默認值:False

使你建立能排序的表單集。

>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm, can_order=True)
>>> formset = ArticleFormSet(initial=[
...     {'title': 'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
...     {'title': 'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
... ])
>>> for form in formset:
...     print(form.as_table())
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" value="Article #1" id="id_form-0-title" /></td></tr>
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" value="2008-05-10" id="id_form-0-pub_date" /></td></tr>
<tr><th><label for="id_form-0-ORDER">Order:</label></th><td><input type="number" name="form-0-ORDER" value="1" id="id_form-0-ORDER" /></td></tr>
<tr><th><label for="id_form-1-title">Title:</label></th><td><input type="text" name="form-1-title" value="Article #2" id="id_form-1-title" /></td></tr>
<tr><th><label for="id_form-1-pub_date">Pub date:</label></th><td><input type="text" name="form-1-pub_date" value="2008-05-11" id="id_form-1-pub_date" /></td></tr>
<tr><th><label for="id_form-1-ORDER">Order:</label></th><td><input type="number" name="form-1-ORDER" value="2" id="id_form-1-ORDER" /></td></tr>
<tr><th><label for="id_form-2-title">Title:</label></th><td><input type="text" name="form-2-title" id="id_form-2-title" /></td></tr>
<tr><th><label for="id_form-2-pub_date">Pub date:</label></th><td><input type="text" name="form-2-pub_date" id="id_form-2-pub_date" /></td></tr>
<tr><th><label for="id_form-2-ORDER">Order:</label></th><td><input type="number" name="form-2-ORDER" id="id_form-2-ORDER" /></td></tr>

它會給每一個表單添加一個字段, 這個新字段名爲ORDER,是一個forms.IntegerField。 它根據初始數據,爲這些表單自動生成數值。 下面讓咱們看一下,若是用戶改變這個值會發生什麼變化:

>>> data = {
...     'form-TOTAL_FORMS': '3',
...     'form-INITIAL_FORMS': '2',
...     'form-MAX_NUM_FORMS': '',
...     'form-0-title': 'Article #1',
...     'form-0-pub_date': '2008-05-10',
...     'form-0-ORDER': '2',
...     'form-1-title': 'Article #2',
...     'form-1-pub_date': '2008-05-11',
...     'form-1-ORDER': '1',
...     'form-2-title': 'Article #3',
...     'form-2-pub_date': '2008-05-01',
...     'form-2-ORDER': '0',
... }

>>> formset = ArticleFormSet(data, initial=[
...     {'title': 'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
...     {'title': 'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
... ])
>>> formset.is_valid()
True
>>> for form in formset.ordered_forms:
...     print(form.cleaned_data)
{'pub_date': datetime.date(2008, 5, 1), 'ORDER': 0, 'title': 'Article #3'}
{'pub_date': datetime.date(2008, 5, 11), 'ORDER': 1, 'title': 'Article #2'}
{'pub_date': datetime.date(2008, 5, 10), 'ORDER': 2, 'title': 'Article #1'}

can_delete

BaseFormSet。  can_delete T0> 

默認值:False

使你建立一個表單集,能夠選擇刪除一些表單。

>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm, can_delete=True)
>>> formset = ArticleFormSet(initial=[
...     {'title': 'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
...     {'title': 'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
... ])
>>> for form in formset:
...     print(form.as_table())
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" value="Article #1" id="id_form-0-title" /></td></tr>
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" value="2008-05-10" id="id_form-0-pub_date" /></td></tr>
<tr><th><label for="id_form-0-DELETE">Delete:</label></th><td><input type="checkbox" name="form-0-DELETE" id="id_form-0-DELETE" /></td></tr>
<tr><th><label for="id_form-1-title">Title:</label></th><td><input type="text" name="form-1-title" value="Article #2" id="id_form-1-title" /></td></tr>
<tr><th><label for="id_form-1-pub_date">Pub date:</label></th><td><input type="text" name="form-1-pub_date" value="2008-05-11" id="id_form-1-pub_date" /></td></tr>
<tr><th><label for="id_form-1-DELETE">Delete:</label></th><td><input type="checkbox" name="form-1-DELETE" id="id_form-1-DELETE" /></td></tr>
<tr><th><label for="id_form-2-title">Title:</label></th><td><input type="text" name="form-2-title" id="id_form-2-title" /></td></tr>
<tr><th><label for="id_form-2-pub_date">Pub date:</label></th><td><input type="text" name="form-2-pub_date" id="id_form-2-pub_date" /></td></tr>
<tr><th><label for="id_form-2-DELETE">Delete:</label></th><td><input type="checkbox" name="form-2-DELETE" id="id_form-2-DELETE" /></td></tr>

can_order相似,這爲每一個名爲DELETE的表單添加一個新字段,是一個forms.BooleanField。 以下,你能夠經過deleted_forms來獲取標記刪除字段的數據:

>>> data = {
...     'form-TOTAL_FORMS': '3',
...     'form-INITIAL_FORMS': '2',
...     'form-MAX_NUM_FORMS': '',
...     'form-0-title': 'Article #1',
...     'form-0-pub_date': '2008-05-10',
...     'form-0-DELETE': 'on',
...     'form-1-title': 'Article #2',
...     'form-1-pub_date': '2008-05-11',
...     'form-1-DELETE': '',
...     'form-2-title': '',
...     'form-2-pub_date': '',
...     'form-2-DELETE': '',
... }

>>> formset = ArticleFormSet(data, initial=[
...     {'title': 'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
...     {'title': 'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
... ])
>>> [form.cleaned_data for form in formset.deleted_forms]
[{'DELETE': True, 'pub_date': datetime.date(2008, 5, 10), 'title': 'Article #1'}]

若是你使用 ModelFormSet,調用 formset.save() 將刪除那些有刪除標記的表單的模型實例。

若是你調用formset.save(commit=False), 對像將不會被自動刪除。 你須要調用formset.deleted_objects每一個對像的 delete() 來真正刪除他們。

將自定義參數傳遞給表單集

有時,您的表單類會使用自定義參數,例如MyArticleForm。 在實例化表單集時能夠傳遞此參數:

>>> from django.forms import BaseFormSet
>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm

>>> class MyArticleForm(ArticleForm):
...     def __init__(self, *args, **kwargs):
...         self.user = kwargs.pop('user')
...         super(MyArticleForm, self).__init__(*args, **kwargs)

>>> ArticleFormSet = formset_factory(MyArticleForm)
>>> formset = ArticleFormSet(form_kwargs={'user': request.user})

form_kwargs也可能取決於具體的窗體實例。 formset基類提供了一個get_form_kwargs方法。 該方法採用單個參數 - 表單中的表單的索引。 empty_form的索引爲None

>>> from django.forms import BaseFormSet
>>> from django.forms import formset_factory

>>> class BaseArticleFormSet(BaseFormSet):
...     def get_form_kwargs(self, index):
...         kwargs = super(BaseArticleFormSet, self).get_form_kwargs(index)
...         kwargs['custom_kwarg'] = index
...         return kwargs

在視圖和模板中使用表單集

在視圖中使用表單集就像使用標準的Form 類同樣簡單, 惟一要作的就是確信你在模板中處理表單。 讓咱們看一個簡單視圖:

from django.forms import formset_factory
from django.shortcuts import render
from myapp.forms import ArticleForm

def manage_articles(request):
    ArticleFormSet = formset_factory(ArticleForm)
    if request.method == 'POST':
        formset = ArticleFormSet(request.POST, request.FILES)
        if formset.is_valid():
            # do something with the formset.cleaned_data
            pass
    else:
        formset = ArticleFormSet()
    return render(request, 'manage_articles.html', {'formset': formset})

manage_articles.html 模板也能夠像這樣:

<form method="post" action="">
    {{ formset.management_form }}
    <table>
        {% for form in formset %}
        {{ form }}
        {% endfor %}
    </table>
</form>

不過,上面能夠用一個快捷寫法,讓表單集來分發管理表單:

<form method="post" action="">
    <table>
        {{ formset }}
    </table>
</form>

上面表單集調用 as_table 方法。

手動呈現can_deletecan_order 

若是手動在模板中渲染字段,則能夠使用{{ form.DELETE }}呈現can_delete參數/ T5> T2>:

<form method="post" action="">
    {{ formset.management_form }}
    {% for form in formset %}
        <ul>
            <li>{{ form.title }}</li>
            <li>{{ form.pub_date }}</li>
            {% if formset.can_delete %}
                <li>{{ form.DELETE }}</li>
            {% endif %}
        </ul>
    {% endfor %}
</form>

相似地,若是表單集有能力(can_order=True),則能夠使用{{ form.ORDER t4> }}

在視圖中使用多個表單集

能夠在視圖中使用多個表單集, 表單集從表單中借鑑了不少方法 你能夠使用 prefix 給每一個表單字段添加前綴,以容許多個字段傳遞給視圖,而不發生命名衝突 讓咱們看看能夠怎麼作

from django.forms import formset_factory
from django.shortcuts import render
from myapp.forms import ArticleForm, BookForm

def manage_articles(request):
    ArticleFormSet = formset_factory(ArticleForm)
    BookFormSet = formset_factory(BookForm)
    if request.method == 'POST':
        article_formset = ArticleFormSet(request.POST, request.FILES, prefix='articles')
        book_formset = BookFormSet(request.POST, request.FILES, prefix='books')
        if article_formset.is_valid() and book_formset.is_valid():
            # do something with the cleaned_data on the formsets.
            pass
    else:
        article_formset = ArticleFormSet(prefix='articles')
        book_formset = BookFormSet(prefix='books')
    return render(request, 'manage_articles.html', {
        'article_formset': article_formset,
        'book_formset': book_formset,
    })

你能夠以正常的方式渲染模板。 記住 prefix 在POST請求和非POST 請求中均需設置,以便他能渲染和執行正確

(11)表單和字段驗證

表單驗證發生在數據驗證以後。 若是你想自定義這個過程,有不一樣的地方能夠進行更改,每一個都有不一樣的用途。 表單處理過程當中要運行三種類別的驗證方法。 它們一般在你調用表單的is_valid() 方法時執行。 還有其餘一些事情也能夠觸發清理和驗證(訪問errors屬性或直接調用full_clean()),但一般不須要它們。

通常狀況下,若是處理的數據有問題,每一個類別的驗證方法都會引起ValidationError,並將相關信息傳遞給ValidationErrorSee below中引起ValidationError 的最佳實踐。 若是沒有引起ValidationError,這些方法應該返回驗證後的(規整化的)數據的Python 對象。

大部分應該能夠使用validators 完成,它們能夠很容易地重用。 Validators 是簡單的函數(或可調用對象),它們接收一個參數並對非法的輸入拋出ValidationError。 Validators 在字段的to_python 和validate 方法調用以後運行。

表單的驗證分爲幾個步驟,能夠自定義或覆蓋:

  • Field上的to_python()方法是每次驗證的第一步。 它強制該值爲正確的數據類型,並引起ValidationError,若是這是不可能的。 這個方法從Widget 接收原始的值並返回轉換後的值。 例如,一個FloatField將數據轉換成一個Python float或者提起一個ValidationError

  • Field上的validate()方法處理不適合驗證器的字段特定驗證。 它須要一個被強制爲正確的數據類型的值,並在任何錯誤上引起ValidationError。 這個方法不返回任何東西且不該該改變任何值。 當你遇到不能夠或不想放在validator 中的驗證邏輯時,應該覆蓋它來處理驗證。

  • Field上的run_validators()方法運行全部字段的驗證器,並將全部錯誤聚合到單個ValidationError中。 你應該不須要覆蓋這個方法。

  • Field子類的clean()方法負責運行to_python()validate()run_validators()以正確的順序傳播錯誤。 若是任什麼時候刻、任何方法引起ValidationError,驗證將中止並引起這個錯誤。 這個方法返回驗證後的數據,這個數據在後面將插入到表單的 cleaned_data 字典中。

  • 在表單子類中調用clean_<fieldname>()方法,其中<fieldname>替換爲表單域屬性的名稱。 這個方法完成於特定屬性相關的驗證,這個驗證與字段的類型無關。 這個方法沒有任何傳入的參數。 你須要查找clean() 中該字段的值,記住此時它已是一個Python 對象而不是表單中提交的原始字符串(它位於cleaned_data 中是由於字段的self.cleaned_data 方法已經驗證過一次數據)。

    例如,若是你想驗證名爲clean_serialnumber() 的serialnumber 的內容是否惟一, CharField 將是實現這個功能的理想之處。你須要的不是一個特別的字段(它只是一個CharField),而是一個特定於表單字段特定驗證,並規整化數據。

    此方法的返回值將替換cleaned_data中的現有值,所以它必須是來自cleaned_data的字段值(即便此方法未更改)或新的清潔價值。

  • 表單子類的clean()方法能夠執行須要訪問多個表單字段的驗證。 這是您能夠在哪裏進行檢查,例如「若是提供了字段A,字段B必須包含有效的電子郵件地址」。 這個方法能夠返回一個徹底不一樣的字典,該字典將用做cleaned_data

    由於字段的驗證方法在調用clean() 時會運行,你還能夠訪問表單的errors 屬性,它包含驗證每一個字段時的全部錯誤。

    注意,你覆蓋的Form.clean() 引起的任何錯誤將不會與任何特定的字段關聯。 它們位於一個特定的「字段」(叫作__all__)中,若是須要能夠經過 non_field_errors() 方法訪問。 若是你想添加一個特定字段的錯誤到表單中,須要調用 add_error()

    還要注意,覆蓋clean() 子類的ModelForm 方法須要特殊的考慮。 (更多信息參見ModelForm documentation)。

這些方法按以上給出的順序執行,一次驗證一個字段。 也就是說,對於表單中的每一個字段(按它們在表單定義中出現的順序),先運行Field.clean() ,而後運行clean_<fieldname>()。 每一個字段的這兩個方法都執行完以後,最後運行Form.clean() 方法,不管前面的方法是否拋出過異常。

下面有上面每一個方法的示例。

咱們已經提到過,全部這些方法均可以拋出ValidationError。 對於任何字段,若是Field.clean()方法引起了一個ValidationError,則不會調用任何字段特定的清除方法。 可是,剩餘的字段的驗證方法仍然會執行。

ValidationError 

爲了讓錯誤信息更加靈活或容易重寫,請考慮下面的準則:給構造函數提供一個富有描述性的錯誤碼code

# Good
ValidationError(_('Invalid value'), code='invalid')

# Bad
ValidationError(_('Invalid value'))

不要將變量強加到消息中;使用佔位符和構造函數的params參數:

# Good
ValidationError(
    _('Invalid value: %(value)s'),
    params={'value': '42'},
)

# Bad
ValidationError(_('Invalid value: %s') % value)

使用字典參數而不要用位置參數。 這使得重寫錯誤信息時不用考慮變量的順序或者徹底省略它們:

# Good
ValidationError(
    _('Invalid value: %(value)s'),
    params={'value': '42'},
)

# Bad
ValidationError(
    _('Invalid value: %s'),
    params=('42',),
)

gettext 封裝錯誤消息使得它能夠翻譯:

# Good
ValidationError(_('Invalid value'))

# Bad
ValidationError('Invalid value')

全部的準則放在一塊兒就是:

raise ValidationError(
    _('Invalid value: %(value)s'),
    code='invalid',
    params={'value': '42'},
)

若是你想編寫可重用的表單、表單字段和模型字段,遵照這些準則是很是必要的。

若是你在驗證的最後(例如,表單的clean() 方法)且知道永遠 不須要從新錯誤信息,雖然不提倡但你仍然能夠選擇重寫不詳細的信息:

ValidationError(_('Invalid value: %s') % value)

Form.errors.as_data() 和Form.errors.as_json() 方法很大程度上受益於code(利用params 名和ValidationError 字典)。

提升多個錯誤

若是在一個驗證方法中檢查到多個錯誤而且但願將它們都反饋給表單的提交者,能夠傳遞一個錯誤的列表給ValidationError 構造函數。

和上面同樣,建議傳遞的列表中的params 實例都帶有 code 和ValidationError,可是傳遞一個字符串列表也能夠工做:

# Good
raise ValidationError([
    ValidationError(_('Error 1'), code='error1'),
    ValidationError(_('Error 2'), code='error2'),
])

# Bad
raise ValidationError([
    _('Error 1'),
    _('Error 2'),
])

在實踐中使用驗證

前面幾節解釋在通常狀況下表單的驗證是如何工做的。 由於有時直接看功能在實際中的應用會更容易掌握,下面是一些列小例子,它們用到前面的每一個功能。

使用驗證器

Django 的表單(以及模型)字段支持使用簡單的函數和類用於驗證,它們叫作Validator。 Validator 是可調用對象或函數,它接收一個值,若是該值合法則什麼也不返回,不然拋出ValidationError。 它們能夠經過字段的validators 參數傳遞給字段的構造函數,或者定義在Field 類的default_validators 屬性中。

簡單的Validator 能夠用於在字段內部驗證值,讓咱們看下Django 的SlugField

from django.forms import CharField
from django.core import validators

class SlugField(CharField):
    default_validators = [validators.validate_slug]

正如你所看到的,SlugField 只是一個帶有自定義Validator 的CharField,它們驗證提交的文本符合某些字符規則。 這也能夠在字段定義時實現,因此:

slug = forms.SlugField()

等同於:

slug = forms.CharField(validators=[validators.validate_slug])

常見的情形,例如驗證郵件地址和正則表達式,能夠使用Django 中已經存在的Validator 類處理。 例如,validators.validate_slug 是RegexValidator 的一個實例,它構造時的第一個參數爲:^[-a-zA-Z0-9_]+$。 writing validators 一節能夠查到已經存在的Validator 以及如何編寫Validator 的一個示例。

表單域默認清除

讓咱們首先建立一個自定義的表單字段,它驗證其輸入是一個由逗號分隔的郵件地址組成的字符串。 完整的類像這樣:

from django import forms
from django.core.validators import validate_email

class MultiEmailField(forms.Field):
    def to_python(self, value):
        """Normalize data to a list of strings."""
        # Return an empty list if no input was given.
        if not value:
            return []
        return value.split(',')

    def validate(self, value):
        """Check if value consists only of valid emails."""
        # Use the parent's handling of required fields, etc.
        super(MultiEmailField, self).validate(value)
        for email in value:
            validate_email(email)

使用這個字段的每一個表單都將在處理該字段數據以前運行這些方法。 這個驗證特定於該類型的字段,與後面如何使用它無關。

讓咱們來建立一個簡單的ContactForm 來向你演示如何使用這個字段:

class ContactForm(forms.Form):
    subject = forms.CharField(max_length=100)
    message = forms.CharField()
    sender = forms.EmailField()
    recipients = MultiEmailField()
    cc_myself = forms.BooleanField(required=False)

只須要簡單地使用MultiEmailField,就和其它表單字段同樣。 當調用表單的to_python() 方法時,MultiEmailField.clean() 方法將做爲驗證過程的一部分運行,它將調用自定義的is_valid() 和validate() 方法。

清理特定字段屬性

繼續前面的例子,假設在ContactForm中,咱們要確保recipients字段始終包含地址"fred@example.com" 這是對咱們表單特定的驗證,因此咱們不想把它放在通常的MultiEmailField類中。 相反,咱們寫一個在recipients字段上運行的清理方法,像這樣:

from django import forms

class ContactForm(forms.Form):
    # Everything as before.
    ...

    def clean_recipients(self):
        data = self.cleaned_data['recipients']
        if "fred@example.com" not in data:
            raise forms.ValidationError("You have forgotten about Fred!")

        # Always return a value to use as the new cleaned data, even if
        # this method didn't change it.
        return data

清理和驗證相互依賴的字段

假設咱們向聯繫表單添加了另外一個要求:若是cc_myself字段是True,則subject必須包含單詞"help" 咱們一次在多個字段上執行驗證,所以表單的clean()方法是一個很好的選擇。 請注意,咱們正在談論這裏的表單上的clean()方法,而較早的咱們在一個字段上寫了一個clean()方法。 在肯定哪些地方進行驗證時,保持領域和形式差別很重要。 字段是單個數據點,表單是字段的集合。

在調用表單clean() 方法的時候,全部字段的驗證方法已經執行完(前兩節),因此self.cleaned_data 填充的是目前爲止已經合法的數據。 因此你須要記住這個事實,你須要驗證的字段可能沒有經過初試的字段檢查。

在這一步,有兩種方法報告錯誤。 最簡單的方法是在表單的頂端顯示錯誤。 你能夠在ValidationError 方法中拋出clean() 來建立錯誤。 像這樣:

from django import forms

class ContactForm(forms.Form):
    # Everything as before.
    ...

    def clean(self):
        cleaned_data = super(ContactForm, self).clean()
        cc_myself = cleaned_data.get("cc_myself")
        subject = cleaned_data.get("subject")

        if cc_myself and subject:
            # Only do something if both fields are valid so far.
            if "help" not in subject:
                raise forms.ValidationError(
                    "Did not send for 'help' in the subject despite "
                    "CC'ing yourself."
                )

在這段代碼中,若是拋出驗證錯誤,表單將在表單的頂部顯示(一般是)描述該問題的一個錯誤信息。

在示例代碼中調用super(ContactForm, self).clean()能夠確保父類中的任何驗證邏輯都被維護。 If your form inherits another that doesn’t return a cleaned_data dictionary in its clean() method (doing so is optional), then don’t assigncleaned_data to the result of the super() call and use self.cleaned_data instead:

def clean(self):
    super(ContactForm, self).clean()
    cc_myself = self.cleaned_data.get("cc_myself")
    ...

報告驗證錯誤的第二種方法可能包括將錯誤消息分配給其中一個字段。 在這種狀況下,讓咱們在表單的顯示中分別關聯一個錯誤信息到「subject」 和「cc_myself」 行。 在實際應用中要當心,由於它可能致使表單的輸出變得使人困惑。 咱們只是向你展現這裏能夠怎麼作,在特定的狀況下,須要你和你的設計人員肯定什麼是好的方法。 咱們的新代碼(代替前面的示例)像這樣:

from django import forms

class ContactForm(forms.Form):
    # Everything as before.
    ...

    def clean(self):
        cleaned_data = super(ContactForm, self).clean()
        cc_myself = cleaned_data.get("cc_myself")
        subject = cleaned_data.get("subject")

        if cc_myself and subject and "help" not in subject:
            msg = "Must put 'help' in subject when cc'ing yourself."
            self.add_error('cc_myself', msg)
            self.add_error('subject', msg)

add_error() 的第二個參數能夠是一個簡單的字符串,但更傾向是ValidationError 的一個實例。 更多細節參見Raising ValidationError。 注意,add_error() 將從cleaned_data 中刪除相應的字段。

用戶認證系統

1.概述

Django認證系統同時處理認證和受權。 簡單地講,認證驗證一個用戶是否它們聲稱的那我的,受權決定一個經過了認證的用戶被容許作什麼。 這裏的詞語「認證」同時指代這兩項任務。

認證系統包含:

  • 用戶
  • 權限:二元(是/否)標誌指示一個用戶是否能夠作一個特定的任務。
  • 組:對多個用戶運用標籤和權限的一種通用的方式。
  • 一個可配置的密碼哈希系統
  • 用戶登陸或內容顯示的表單和視圖
  • 一個可插拔的後臺系統

Django中的認證系統致力於變得很是通用,但它不提供在web認證系統中某些常見的功能。 某些常見問題的解決方法已經在第三方包中實現:

  • 密碼強度檢查
  • 登陸嘗試的制約
  • 第三方認證(例如OAuth)

安裝

認證的支持做爲Django的一個contrib模塊,打包於django.contrib.auth中。 默認狀況下,要求的配置已經包含在django-admin startproject 生成的settings.py 中,它們的組成包括INSTALLED_APPS 設置中的兩個選項:

  1. 'django.contrib.auth'包含認證框架的核心和默認的模型。
  2. 'django.contrib.contenttypes'是Django內容類型系統,它容許權限與你建立的模型關聯。

MIDDLEWARE設置中的這些條目:

  1. SessionMiddleware跨請求管理sessions
  2. AuthenticationMiddleware使用會話將用戶與請求關聯起來。

有了這些設置,運行manage.py migrate命令將爲認證相關的模型建立必要的數據庫表併爲你的應用中定義的任意模型建立權限。

 2.使用認證系統

 這篇文檔解釋默認配置下Django認證系統的使用。 這些配置已經逐步能夠知足大部分常見項目的須要,能夠處理範圍很是普遍的任務,且具備一套細緻的密碼和權限實現。 對於須要與默認配置不一樣需求的項目,Django支持extension and customization認證。

Django的認證同時提供認證和受權,並一般統一稱爲認證系統,由於這些功能某些地方是耦合的。

User對象

User對象是認證系統的核心。 它們一般表示與你的站點進行交互的用戶,並用於啓用限制訪問、註冊用戶信息和給建立者關聯內容等。 在Django的認證框架中只存在一種類型的用戶,所以諸如'superusers'或管理員'staff'用戶只是具備特殊屬性集的user對象,而不是不一樣類型的user對象。

默認user的基本屬性有:

完整的參考請參閱full API documentation,如下的內容更偏重特定的任務。

建立用戶

建立users最直接的方法是使用create_user()輔助函數:

>>> from django.contrib.auth.models import User
>>> user = User.objects.create_user('john', 'lennon@thebeatles.com', 'johnpassword')

# 到這裏,user 這一個User對象已經保存於
# 數據庫中了。 # 你能夠繼續修改它的屬性。
# 若是你想要修改其餘字段。
>>> user.last_name = 'Lennon'
>>> user.save()

若是你已經安裝了Django admin,你也能夠create users interactively.

建立超級用戶

使用createsuperuser命令建立superusers:

$ python manage.py createsuperuser --username=joe --email=joe@example.com

將會提示你輸入一個密碼。 在你輸入一個密碼後,該user將會當即建立。 若是您離開--username--email選項,它將提示您輸入這些值。

更改密碼

Django不會在user模型上存儲原始的(明文)密碼,而只是一個哈希(完整的細節參見documentation of how passwords are managed)。 由於這個緣由,不要嘗試直接操做user的password屬性。 這也是爲何建立一個user時要使用輔助函數。

若要修改一個用戶的密碼,你有幾種選擇:

manage.py changepassword *username*提供了一種從命令行更改用戶密碼的方法。 它提示你修改一個給定user的密碼,你必須輸入兩次。 若是它們匹配,新的密碼將會當即修改。 若是你沒有提供user,命令行將嘗試修改與當前系統用戶匹配的用戶名的密碼。

你也能夠經過程序修改密碼,使用set_password()

>>> from django.contrib.auth.models import User
>>> u = User.objects.get(username='john')
>>> u.set_password('new password')
>>> u.save()

若是你安裝了Django admin,你還能夠在authentication system’s admin pages修改user的密碼。

Django還提供viewsforms用於容許user修改他們本身密碼。

更改用戶密碼將會註銷全部會話。 詳細信息請參閱Session invalidation on password change

認證用戶

authenticate(request=None**credentials)[source]

使用authenticate()來驗證一組憑據。 它以credentials爲關鍵字參數,默認爲usernamepassword,根據每一個認證的後端進行檢查,若是credentials對某個後端有效則返回一個User對象。 若是credentials對任何後端都無效,或者若是後端引起了PermissionDenied,則返回None。 像這樣:

from django.contrib.auth import authenticate
user = authenticate(username='john', password='secret')
if user is not None:
    # A backend authenticated the credentials
else:
    # No backend authenticated the credentials

request是可選的HttpRequest,它在認證後端的authenticate()方法上傳遞。

權限和受權

Django自己提供了一個簡單的權限系統。 它提供了一種爲特定用戶和用戶組分配權限的方法。

它被Django的admin站點使用,但歡迎你在你本身的代碼中使用。

Django admin 站點使用以下的權限:

  • 擁有該類型對象"add"權限的用戶才能夠訪問"add"表單以及添加一個該類型對象。
  • 查看修改列表、查看「change」表單以及修改一個對象的權利只限於具備該類型對象的「change」權限的用戶擁有。
  • 用戶必須在一個對象上具備「delete」權限,才能刪除這個對象。

權限不但能夠根據每一個對象的類型,並且能夠根據特定的對象實例設置。 經過使用ModelAdmin類提供的has_add_permission()has_change_permission()has_delete_permission()方法,能夠針對相同類型的不一樣對象實例自定義權限。

User對象具備兩個多對多的字段:groupsuser_permissions。 User對象能夠用和其它Django模型同樣的方式訪問它們相關聯的對象:

myuser.groups.set([group_list])
myuser.groups.add(group, group, ...)
myuser.groups.remove(group, group, ...)
myuser.groups.clear()
myuser.user_permissions.set([permission_list])
myuser.user_permissions.add(permission, permission, ...)
myuser.user_permissions.remove(permission, permission, ...)
myuser.user_permissions.clear()

默認權限

django.contrib.auth在你的INSTALLED_APPS設置中列出時,它將確保爲你安裝的應用中的每一個Django模型建立3個默認的權限 – add、change和delete。

當你運行manage.py migrate時,將建立這些權限;在django.contrib.auth添加到INSTALLED_APPS以後,首次運行migrate時,將爲全部先前安裝的模型建立默認權限,以及當時安裝的任何新模型。 以後,每次運行manage.py migrate,它將爲新的模型建立默認的權限(建立權限的函數與post_migrate信號鏈接)。

假設你有個app_label叫作foo的應用,這個應用有一個名爲Bar的模型,要測試基本的權限,你應該使用:

  • 添加:user.has_perm('foo.add_bar')
  • 更改:user.has_perm('foo.change_bar')
  • 刪除:user.has_perm('foo.delete_bar')

不多直接訪問Permission模型。

Groups

django.contrib.auth.models.Group模型是用戶分類的一種通用的方式,經過這種方式你能夠應用權限或其它標籤到這些用戶。 一個用戶能夠屬於任意多個組。

組中某個用戶自動具備賦給那個組的權限。 例如,若是組Site editors具備權限 can_edit_home_page,那麼該組中的任何用戶都具備該權限。

除權限以外,組仍是給用戶分類的一種方便的方法以給他們某些標籤或擴展的功能。 例如,你能夠建立一個組'Special users',而後你能夠這樣寫代碼,給他們訪問你的站點僅限會員的部分,或者給他們發僅限於會員的郵件。

以編程方式建立權限

雖然custom permissions能夠定義在模型的Meta類中,但你也能夠直接建立權限。 例如,您能夠在myapp中爲BlogPost模型建立can_publish權限:

from myapp.models import BlogPost
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType

content_type = ContentType.objects.get_for_model(BlogPost)
permission = Permission.objects.create(
    codename='can_publish',
    name='Can Publish Posts',
    content_type=content_type,
)

而後該權限能夠經過user_permissions屬性分配給一個User,或者經過permissions屬性分配給Group

權限的緩存

在第一次獲取權限用於檢查後,模型的後端將在該用戶對象上緩存這些權限。 這對於常見的請求-響應週期一般沒問題,由於一般在添加權限後不會當即檢查權限(例如在管理後臺中)。 若是你要添加權限並當即檢查它們,例如在測試中或視圖中,最簡單的解決方案是從數據庫從新獲取用戶。 像這樣:

from django.contrib.auth.models import Permission, User
from django.contrib.contenttypes.models import ContentType
from django.shortcuts import get_object_or_404

from myapp.models import BlogPost

def user_gains_perms(request, user_id):
    user = get_object_or_404(User, pk=user_id)
    # any permission check will cache the current set of permissions
    user.has_perm('myapp.change_blogpost')

    content_type = ContentType.objects.get_for_model(BlogPost)
    permission = Permission.objects.get(
        codename='change_blogpost',
        content_type=content_type,
    )
    user.user_permissions.add(permission)

    # Checking the cached permission set
    user.has_perm('myapp.change_blogpost')  # False

    # Request new instance of User
    # Be aware that user.refresh_from_db() won't clear the cache.
    user = get_object_or_404(User, pk=user_id)

    # Permission cache is repopulated from the database
    user.has_perm('myapp.change_blogpost')  # True

    ...

Web請求中的認證

Django使用會話和中間件來攔截認證系統到請求對象中。

它們在每一個請求上提供一個request.user屬性,表示當前的用戶。 若是當前的用戶沒有登入,該屬性將設置成AnonymousUser的一個實例,不然它將是User的實例。

你能夠使用is_authenticated將它們區分開,以下所示:

if request.user.is_authenticated:
    # Do something for authenticated users.
    ...
else:
    # Do something for anonymous users.
    ...

如何登陸用戶

若是你有一個認證了的用戶,你想把它附帶到當前的會話中 - 這能夠經過login()函數完成。

login(requestuserbackend=None)[source]

從視圖中登入一個用戶,請使用login()。 它接受一個HttpRequest對象和一個User對象。 login()使用Django的session框架來將用戶的ID保存在session中。

請注意,匿名會話期間的任何數據集在用戶登陸後都會保留在會話中。

下面的示例向你演示如何使用authenticate() 和login()

from django.contrib.auth import authenticate, login

def my_view(request):
    username = request.POST['username']
    password = request.POST['password']
    user = authenticate(request, username=username, password=password)
    if user is not None:
        login(request, user)
        # Redirect to a success page.
        ...
    else:
        # Return an 'invalid login' error message.
        ...

在舊版本中,當你手工登錄一個用戶時,在調用login()以前必須authenticate()成功認證這個用戶。 如今你能夠使用新的backend參數設置後端。

選擇驗證後端

用戶登陸時,用戶的ID和用於身份驗證的後端保存在用戶的會話中。 這容許相同的身份驗證後端在未來的請求中獲取用戶的詳細信息。 要保存在會話中的認證後端選擇以下:

  1. 使用可選的backend參數的值(若是提供)。
  2. 使用user.backend屬性的值(若是存在)。 這容許配對authenticate()login()authenticate()設置user.backend屬性用戶對象返回。
  3. 若是隻有一個,請使用AUTHENTICATION_BACKENDS中的backend
  4. 不然,引起異常。

在狀況1和2中,backend參數或user.backend屬性的值應爲點號導入路徑字符串(如AUTHENTICATION_BACKENDS的字符串),而不是實際的類。

如何登出用戶

logout(request)[source]

若要登出一個已經經過django.contrib.auth.login()登入的用戶,能夠在你的視圖中使用django.contrib.auth.logout()。 它接收一個HttpRequest對象且沒有返回值。 例如:

from django.contrib.auth import logout

def logout_view(request):
    logout(request)
    # Redirect to a success page.

注意,即便用戶沒有登入,logout()也不會拋出任何錯誤。

當您調用logout()時,當前請求的會話數據將被完全清除。 全部存在的數據都將清除。 這是爲了防止另一我的使用相同的Web瀏覽器登入並訪問前一個用戶的會話數據。 若是你想在用戶登出以後能夠當即訪問放入會話中的數據,請在調用django.contrib.auth.logout()以後放入。

限制對登陸用戶的訪問

原始方式

限制訪問頁面的簡單原始方法是檢查request.user.is_authenticated,並重定向到登陸頁面:

from django.conf import settings
from django.shortcuts import redirect

def my_view(request):
    if not request.user.is_authenticated:
        return redirect('%s?next=%s' % (settings.LOGIN_URL, request.path))
    # ...

...或顯示錯誤信息:

from django.shortcuts import render

def my_view(request):
    if not request.user.is_authenticated:
        return render(request, 'myapp/login_error.html')
    # ...

login_required裝飾器

login_required(redirect_field_name='next'login_url=None)[source]

做爲一個快捷方式,你能夠使用便捷的login_required()裝飾器:

from django.contrib.auth.decorators import login_required

@login_required
def my_view(request):
    ...

login_required()完成下面的事情:

  • 若是用戶沒有登陸,則重定向到settings.LOGIN_URL,傳遞查詢字符串中的當前絕對路徑。 例如:/accounts/login/?next=/polls/3/
  • 若是用戶已經登入,則正常執行視圖。 視圖的代碼能夠安全地假設用戶已經登入。

默認狀況下,在成功認證後用戶應該被重定向的路徑存儲在查詢字符串的一個叫作"next"的參數中。 若是對該參數你傾向使用一個不一樣的名字,login_required()帶有一個可選的redirect_field_name參數:

from django.contrib.auth.decorators import login_required

@login_required(redirect_field_name='my_redirect_field')
def my_view(request):
    ...

注意,若是你提供一個值給redirect_field_name,你很是可能同時須要自定義你的登陸模板,由於存儲重定向路徑的模板上下文變量將使用"next"值做爲它的鍵,而不是默認的redirect_field_name

login_required()還帶有一個可選的login_url參數。 例如:

from django.contrib.auth.decorators import login_required

@login_required(login_url='/accounts/login/')
def my_view(request):
    ...

注意,若是你沒有指定login_url參數,你須要確保settings.LOGIN_URL與你的登陸視圖正確關聯。 例如,使用默認值,能夠添加下面幾行到你的URLconf中:

from django.contrib.auth import views as auth_views

url(r'^accounts/login/$', auth_views.LoginView.as_view()),

settings.LOGIN_URL同時還接收視圖函數名和named URL patterns。 這容許你自由地從新映射你的URLconf中的登陸視圖而不用更新設置。

LoginRequired mixin

使用class-based views時,能夠使用LoginRequiredMixin實現與login_required相同的行爲。 此mixin應位於繼承列表中最左側的位置。

class  LoginRequiredMixin

若是視圖正在使用此mixin,那麼根據raise_exception參數,未經身份驗證的用戶的全部請求將被重定向到登陸頁面或顯示HTTP 403 Forbidden錯誤。

您能夠設置AccessMixin的任何參數來自定義未受權用戶的處理:

from django.contrib.auth.mixins import LoginRequiredMixin

class MyView(LoginRequiredMixin, View):
    login_url = '/login/'
    redirect_field_name = 'redirect_to'

限制對經過測試的登陸用戶的訪問

要根據某些權限或某些其餘測試來限制訪問權限,您能夠執行與上一節中所述基本相同的操做。

簡單的方法就是在視圖中直接運行你對request.user的測試。 例如,視圖檢查用戶的郵件屬於特定的地址(例如@example.com),若不是,則重定向到登陸頁面。

from django.shortcuts import redirect

def my_view(request):
    if not request.user.email.endswith('@example.com'):
        return redirect('/login/?next=%s' % request.path)
    # ...

user_passes_test(test_funclogin_url=Noneredirect_field_name='next')[source]

你能夠用方便的 False 裝飾器,當回調函數返回 user_passes_test 時會執行一個重定向操做:

from django.contrib.auth.decorators import user_passes_test

def email_check(user):
    return user.email.endswith('@example.com')

@user_passes_test(email_check)
def my_view(request):
    ...

user_passes_test() 要求一個以User 對象爲參數的回調函數,若用戶容許訪問此視圖,返回 True。 注意,user_passes_test()不會自動檢查 User 是否爲匿名對象。

user_passes_test()接收兩個額外的參數:

LOGIN_URL
讓你指定那些沒有經過檢查的用戶要重定向至哪裏。 若不指定其值,它多是默認的 settings.LOGIN_URL
redirect_field_name
login_required()的參數相同。 把它設置爲 None 來把它從 URL 中移除,當你想把通不過檢查的用戶重定向到沒有next page 的非登陸頁面時。

像這樣:

@user_passes_test(email_check, login_url='/login/')
def my_view(request):
    ...

class UserPassesTestMixin

當使用class-based views時,能夠使用UserPassesTestMixin來執行此操做。

test_func()

您必須覆蓋類的test_func()方法來提供執行的測試。 此外,您能夠設置AccessMixin的任何參數來自定義未受權用戶的處理:

from django.contrib.auth.mixins import UserPassesTestMixin

class MyView(UserPassesTestMixin, View):

    def test_func(self):
        return self.request.user.email.endswith('@example.com')

get_test_func()

您也能夠覆蓋get_test_func()方法以使mixin對其檢查使用不一樣命名的函數(而不是test_func())。

permission_required裝飾器

permission_required(permlogin_url=Noneraise_exception=False)[source]

檢查一個用戶是否有指定的權限是相對常見的需求。 爲此,Django爲這種狀況提供了一個快捷方式:permission_required()裝飾器。:

from django.contrib.auth.decorators import permission_required

@permission_required('polls.can_vote')
def my_view(request):
    ...

就像has_perm()方法同樣,權限名稱採用"<app label>.<permission codename>"的形式,(例如polls.can_vote表示polls應用中一個模型的權限)。

裝飾器也能夠採起可迭代的權限,在這種狀況下,用戶必須具備全部權限才能訪問視圖。

請注意,permission_required()還須要一個可選的login_url參數:

from django.contrib.auth.decorators import permission_required

@permission_required('polls.can_vote', login_url='/loginpage/')
def my_view(request):
    ...

login_required()裝飾器同樣,login_url默認爲settings.LOGIN_URL

若是提供了 raise_exception 參數,裝飾器拋出PermissionDenied異常,使用 the 403 (HTTP Forbidden) view而不是重定向到登陸頁面。

若是你想使用raise_exception,還可讓你的用戶有機會先登陸,你能夠添加login_required()裝飾器:

from django.contrib.auth.decorators import login_required, permission_required

@login_required
@permission_required('polls.can_vote', raise_exception=True)
def my_view(request):
    ...

PermissionRequiredMixin mixin 

要對class-based views應用權限檢查,能夠使用PermissionRequiredMixin

class  PermissionRequiredMixin

這個mixin,就像permission_required裝飾器同樣,檢查訪問視圖的用戶是否具備全部給定的權限。 您應該使用permission_required參數指定權限(或許可的迭代):

from django.contrib.auth.mixins import PermissionRequiredMixin

class MyView(PermissionRequiredMixin, View):
    permission_required = 'polls.can_vote'
    # Or multiple of permissions:
    permission_required = ('polls.can_open', 'polls.can_edit')

您能夠設置AccessMixin的任何參數來自定義未受權用戶的處理。

您也能夠覆蓋這些方法:

get_permission_required()

返回由mixin使用的許可名稱的可迭代。 默認爲permission_required屬性,若有必要,轉換爲元組。

has_permission()

返回一個布爾值,表示當前用戶是否具備執行裝飾視圖的權限。 默認狀況下,返回使用get_permission_required()返回的權限列表調用has_perms()的結果。

3.自定義認證

Django自帶的認證系統足夠應付大多數狀況,但你可能有特殊的需求,現成的默認認證系統不能知足。 自定義本身的項目的權限系統須要瞭解Django中哪些部分是可以擴展或替換的。 這個文檔提供瞭如何定製權限系統的細節。

認證後端系統是可擴展的,可用於User模型存儲的用戶名和密碼與Django的默認不一樣的服務進行認證。

你可爲你的模型提供自定義權限,它們能夠經過Django認證系統進行檢查。

你能夠擴展默認的User模型,或用徹底自定義的模型替換

指定認證後端

在底層,Django維護一個「認證後端」的列表。 當調用django.contrib.auth.authenticate()時 — 如何登入一個用戶中所描述的 — Django 會嘗試全部的認證後端進行認證。 若是第一個認證方法失敗,Django 將嘗試第二個,以此類推,直至試完全部的認證後臺。

使用的認證後臺經過AUTHENTICATION_BACKENDS 設置指定。 這應該是指向Python類的Python路徑名的列表,它們知道如何進行身份驗證。 這些類能夠位於Python 路徑上任何地方。

默認狀況下,AUTHENTICATION_BACKENDS 設置爲:

['django.contrib.auth.backends.ModelBackend']

這個基本的認證後臺會檢查Django 的用戶數據庫並查詢內建的權限。 它不會經過任何的速率限制機制防禦暴力破解。 你能夠在自定義的認證後端中實現本身的速率控制機制,或者使用大部分Web 服務器提供的機制。

AUTHENTICATION_BACKENDS 的順序很重要,因此若是用戶名和密碼在多個後臺中都是合法的,Django 將在第一個匹配成功後中止處理。

若是後臺引起PermissionDenied 異常,認證將當即失敗。 Django 不會檢查後面的認證後臺。

一旦用戶被認證過,Django會在用戶的session中存儲他使用的認證後端,而後在session有效期中一直會爲該用戶提供此後端認證。 這種高效意味着驗證源被緩存基於per-session基礎, 因此若是你改變 AUTHENTICATION_BACKENDS, 若是你須要迫使用戶從新認證,須要清除掉 session 數據. 一個簡單的方式是使用這個方法:Session.objects.all().delete().

編寫認證後端

認證後端是一個類,它實現兩個必需方法:get_user(user_id)authenticate(request, **credentials),以及一組可選的與權限相關的認證方法

get_user方法使用一個user_id,能夠是用戶名,數據庫ID等等,但必須是用戶對象的主鍵,並返回一個用戶對象。

authenticate方法須要一個request參數和一些憑據做爲關鍵字參數。 大多數狀況下,代碼以下︰

class MyBackend(object):
    def authenticate(self, request, username=None, password=None):
        # Check the username/password and return a user.
        ...

固然,它也能夠接收token的方式做爲參數,例如:

class MyBackend(object):
    def authenticate(self, request, token=None):
        # Check the token and return a user.
        ...

不管哪一種方式,authenticate()應該檢查它得到的憑據,若是憑據有效,則返回與這些憑據匹配的用戶對象。 若是不合法,則返回 None.

request is an HttpRequest and may be None if it wasn’t provided to authenticate() (例如密碼在後端).

Django管理員與Django User object緊密耦合。 處理這種狀況的最好方法是爲您的後端存在的每一個用戶建立一個Django User對象(例如,在LDAP目錄,外部SQL數據庫等中) 你能夠先寫一個腳原本作這件事, 或者用你的 authenticate 方法在用戶登錄的時候完成這件事。

這裏有一個例子,後臺對你定義在 settings.py 文件裏的用戶和密碼進行驗證,而且在用第一次驗證的時候建立一個 User 對象:

from django.conf import settings
from django.contrib.auth.hashers import check_password
from django.contrib.auth.models import User

class SettingsBackend(object):
    """
    Authenticate against the settings ADMIN_LOGIN and ADMIN_PASSWORD.

    Use the login name and a hash of the password. 像這樣:

    ADMIN_LOGIN = 'admin'
    ADMIN_PASSWORD = 'pbkdf2_sha256$30000$Vo0VlMnkR4Bk$qEvtdyZRWTcOsCnI/oQ7fVOu1XAURIZYoOZ3iq8Dr4M='
    """

    def authenticate(self, request, username=None, password=None):
        login_valid = (settings.ADMIN_LOGIN == username)
        pwd_valid = check_password(password, settings.ADMIN_PASSWORD)
        if login_valid and pwd_valid:
            try:
                user = User.objects.get(username=username)
            except User.DoesNotExist:
                # Create a new user. There's no need to set a password
                # because only the password from settings.py is checked.
                user = User(username=username)
                user.is_staff = True
                user.is_superuser = True
                user.save()
            return user
        return 沒有

    def get_user(self, user_id):
        try:
            return User.objects.get(pk=user_id)
        except User.DoesNotExist:
            return 沒有

在自定義後端處理受權

自定義驗證後端能提供本身的權限。

當認證後端完成了這些功能 (get_group_permissions()get_all_permissions()has_perm(), and has_module_perms()) 那麼user model就會給它授予相對應的許可。

提供給用戶的權限將是全部後端返回的全部權限的超集。 也就是說,只要任意一個backend授予了一個user權限,django就給這個user這個權限。

若是後端在has_perm()has_module_perms()中引起PermissionDenied異常,受權將當即失敗,Django不會檢查接下來的後端認證。

上述的簡單backend能夠至關容易的完成授予admin權限。

class SettingsBackend(object):
    ...
    def has_perm(self, user_obj, perm, obj=None):
        return user_obj.username == settings.ADMIN_LOGIN

在上例中,授予了用戶全部訪問權限。 注意, 因爲django.contrib.auth.models.User 同名函數將接收一樣的參數,認證後臺接收到的 user_obj,有多是匿名用戶 anonymous

一個完整的認證過程,能夠參考 auth_permission類,它位於django/contrib/auth/backends.py,ModelBackend是默認的認證後臺,而且大多數狀況下會對ModelBackend表進行查詢。 若是你想對後臺API提供自定義行爲,你能夠利用Python繼承的優點,繼承ModelBackend並自定義後臺API

受權匿名用戶

匿名用戶是指不通過身份驗證即他們有沒有提供有效的身份驗證細節。 然而,這並不必定意味着他們不被受權作任何事情。 在最基本的層面上,大多數網站受權匿名用戶瀏覽大部分網站,許多網站容許匿名發表評論等。

Django 的權限框架沒有一個地方來存儲匿名用戶的權限。 然而,傳遞給身份驗證後端的用戶對象多是django.contrib.auth.models.AnonymousUser 對象,該對象容許後端指定匿名用戶自定義的受權行爲。 這對可重用應用的做者是頗有用的, 由於他能夠委託全部的請求, 例如控制匿名用戶訪問,給這個認證後端, 而不須要設置它

受權非活動用戶

非活動用戶是將is_active字段設置爲False的用戶。 ModelBackendRemoteUserBackend身份驗證後端禁止這些用戶進行身份驗證。若是自定義用戶模型沒有is_active字段,則全部用戶都將被容許進行身份驗證。

若是要讓非活動用戶進行身份驗證,能夠使用AllowAllUsersModelBackendAllowAllUsersRemoteUserBackend

對權限系統中的匿名用戶的支持容許匿名用戶具備執行某些操做的權限的狀況,而未被認證的用戶不具備。

不要忘記在本身的後端權限方法中測試用戶的is_active屬性。

在Django更改1.10:

在舊版本中,ModelBackend容許非活動用戶進行身份驗證。

處理對象權限

django的權限框架對對象權限有基礎的支持, 儘管在它的核心沒有實現它. 這意味着對象權限檢查將始終返回 False 或空列表 (取決於檢查的行爲)。 一個認證後端將傳遞關鍵字參數obj 和 user_obj 給每個對象相關的認證方法, 而且可以返回適當的對象級別的權限.

自定義權限

要爲給定模型對象建立自定義權限,請使用permissions model Meta attribute

此示例任務模型建立三個自定義權限,即用戶是否能夠對您的應用程序任務實例執行操做:

class Task(models.Model):
    ...
    class Meta:
        permissions = (
            ("view_task", "Can see available tasks"),
            ("change_task_status", "Can change the status of tasks"),
            ("close_task", "Can remove a task by setting its status as closed"),
        )

惟一的作法是在運行manage.py migrate時建立這些額外的權限(建立權限的功能鏈接到post_migrate信號)。 當用戶嘗試訪問應用程序提供的功能(查看任務,更改任務狀態,關閉任務)時,您的代碼負責檢查這些權限的值。 繼續上面的示例,如下檢查用戶是否能夠查看任務:

user.has_perm('app.view_task')

擴展示有的User模型

有兩種方法來擴展默認的User模型,而不用替換你本身的模型。 若是你須要的只是行爲上的改變,而不須要對數據庫中存儲的內容作任何改變,你能夠建立基於User 的proxy model。 代理模型提供的功能包括默認的排序、自定義管理器以及自定義模型方法。

若是您但願存儲與User相關的信息,則能夠使用OneToOneField到包含其餘信息字段的模型。 這種 one-to-one 模型通常被稱爲資料模型(profile model),它一般被用來存儲一些有關網站用戶的非驗證性( non-auth )資料。 例如,你能夠建立一個員工模型 (Employee model):

from django.contrib.auth.models import User

class Employee(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    department = models.CharField(max_length=100)

假設一個員工Fred Smith 既有User 模型又有Employee 模型,你能夠使用Django 標準的關聯模型訪問相關聯的信息:

>>> u = User.objects.get(username='fsmith')
>>> freds_department = u.employee.department

要將我的資料模型的字段添加到管理後臺的用戶頁面中,請在應用程序的UserAdmin定義一個InlineModelAdmin(對於本示例,咱們將使用StackedInline )並將其添加到admin.py類並向User類註冊的:

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import User

from my_user_profile_app.models import Employee

# Define an inline admin descriptor for Employee model
# which acts a bit like a singleton
class EmployeeInline(admin.StackedInline):
    model = Employee
    can_delete = False
    verbose_name_plural = 'employee'

# Define a new User admin
class UserAdmin(BaseUserAdmin):
    inlines = (EmployeeInline, )

# Re-register UserAdmin
admin.site.unregister(User)
admin.site.register(User, UserAdmin)

這些配置文件模型在任何狀況下都不是特別的 - 它們只是Django模型,與用戶模型發生一對一的連接。 所以,當建立用戶時,它們不會自動建立,但能夠使用django.db.models.signals.post_save來適當地建立或更新相關模型。

使用相關模型會產生其餘查詢或聯接來檢索相關數據。 根據您的需求,包含相關字段的自定義用戶模型多是您更好的選擇,可是,與項目應用程序中的默認用戶模型的現有關係可能有助於額外的數據庫加載。

替換User模型

某些類型的項目可能有特殊的認證需求,Django內建的User模型不可能老是適用。 例如,在某些網站上使用郵件地址而不是用戶名做爲身份的標識可能更合理。

經過提供一個值給AUTH_USER_MODEL設置,指向自定義的模型,Django容許你覆蓋默認的User模型:

AUTH_USER_MODEL = 'myapp.MyUser'

這個點式路徑包含Django應用的名稱(必須位於你的INSTALLED_APPS中),和要用做User模型的Django模型的名稱。

在項目開始時使用自定義User模型

若是你正在開始一個新項目,強烈建議你設置一個自定義用戶模型,即便默認的User模型對你已經足夠可用。 下面的模型的行爲與默認的用戶模型相同,可是未來若是須要你能夠自定義它:

from django.contrib.auth.models import AbstractUser

class User(AbstractUser):
    pass

不要忘記將AUTH_USER_MODEL指向它。 在建立任何遷移或首次運行manage.py migrate以前執行此操做。

另外,在應用程序的admin.py中註冊該模型:

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import User

admin.site.register(User, UserAdmin)

在項目中期修改成自定義的User模型

建立數據庫表以後,更改AUTH_USER_MODEL是很是困難的,由於它會影響外鍵和多對多關係。

此更改沒法自動完成,須要手動修復模式、從舊用戶表移動數據、並可能須要手動從新應用某些遷移。 有關步驟的概述,請參見#25313

因爲Django對於可交換模型的動態依賴特性的限制,必須在其應用的第一次遷移(一般稱爲0001_initial)中建立AUTH_USER_MODEL引用的模型;不然你會有依賴問題。

另外,當運行遷移時,你可能遇到一個CircularDependencyError,由於Django將沒法自動中斷因爲動態依賴關係的依賴關係循環。若是看到此錯誤,應該經過將你的用戶模型所依賴的模型移動到第二次遷移中來打破循環。 (You can try making two normal models that have a ForeignKey to each other and seeing how makemigrations resolves that circular dependency if you want to see how it’s usually done.)

可重用的應用和AUTH_USER_MODEL

可重用的應用不該實現自定義用戶模型。 一個項目可能使用多個應用,實現自定義用戶模型的兩個可重用應用不能一塊兒使用。 若是須要在應用中存儲用戶的信息,請使用ForeignKeyOneToOneFieldsettings.AUTH_USER_MODEL,以下所述。

引用User模型

若是直接引用User(例如,經過外鍵引用),在AUTH_USER_MODEL設置已更改成不一樣用戶模型的項目中,代碼將不能工做。

get_user_model()[source]

你應該使用django.contrib.auth.get_user_model() 來引用用戶模型,而不要直接引用User。 此方法將返回當前活動的用戶模型 — 若是指定了自定義用戶模型,不然返回User

在定義到用戶模型的外鍵或多對多關係時,應使用AUTH_USER_MODEL設置指定自定義模型。 像這樣:

from django.conf import settings
from django.db import models

class Article(models.Model):
    author = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
    )

當鏈接到用戶模型發送的信號時,應該使用AUTH_USER_MODEL設置指定自定義模型。 像這樣:

from django.conf import settings
from django.db.models.signals import post_save

def post_save_receiver(sender, instance, created, **kwargs):
    pass

post_save.connect(post_save_receiver, sender=settings.AUTH_USER_MODEL)

通常來講,在導入時執行的代碼中,使用AUTH_USER_MODEL設置來引用用戶模型是最簡單的,但Django也能夠調用get_user_model()導入模型,因此你能夠使用models.ForeignKey(get_user_model(), ...)

若是你的應用使用多個用戶模型進行測試,例如使用@override_settings(AUTH_USER_MODEL=...),並將get_user_model()的結果緩存在模塊級別變量中,你可能須要監聽setting_changed信號以清除緩存。 像這樣:

from django.apps import apps
from django.contrib.auth import get_user_model
from django.core.signals import setting_changed
from django.dispatch import receiver

@receiver(setting_changed)
def user_model_swapped(**kwargs):
    if kwargs['setting'] == 'AUTH_USER_MODEL':
        apps.clear_cache()
        from myapp import some_module
        some_module.UserModel = get_user_model()

4.API參考

User模型

字段

class  models. User
User 對象具備以下字段:
username     

         必選。 150個字符之內。 用戶名可能包含字母數字,_@+ . -個字符。

         對於許多用例,max_length應該是足夠的。 若是您須要較長的長度,請使用custom user model。 若是您使用具備utf8mb4編碼(推薦用於正確的Unicode支持)的MySQL,請           至少指定max_length=191,由於MySQL只能建立具備191個字符的惟一索引,默認。

first_name

可選(blank=True)。 少於等於30個字符。

last_name

可選(blank=True)。 少於等於30個字符。

email

可選(blank=True)。 郵箱地址。

password

必選。 密碼的哈希及元數據。 (Django 不保存原始密碼)。 原始密碼能夠無限長並且能夠包含任意字符。 參見password documentation

groups

Group 之間的多對多關係。

user_permissions

Permission 之間的多對多關係。

is_staff

布爾值。 指示用戶是否能夠訪問Admin 站點。

is_active

布爾值。 指示用戶的帳號是否激活。 咱們建議您將此標誌設置爲False而不是刪除賬戶;這樣,若是您的應用程序對用戶有任何外鍵,則外鍵不會中斷。

它不是用來控制用戶是否可以登陸。 不須要驗證後端來檢查is_active標誌,而是默認後端(ModelBackend)和RemoteUserBackend。 若是要容許非活動用戶登陸,您能夠使用AllowAllUsersModelBackendAllowAllUsersRemoteUserBackend。 在這種狀況下,您還須要自定義LoginView使用的AuthenticationForm,由於它拒絕了非活動用戶。 請注意,諸如has_perm()等權限檢查方法,Django管理員中的身份驗證所有返回爲非活動用戶的False

在Django更改1.10:

在舊版本中,ModelBackendRemoteUserBackend容許非活動用戶進行身份驗證。

is_superuser

布爾值。 指定這個用戶擁有全部的權限而不須要給他們分配明確的權限。

last_login

用戶最後一次登陸的時間。

date_joined

帳戶建立的時間。 當帳號建立時,默認設置爲當前的date/time。

屬性

class  models. User
is_authenticated

始終爲True(與AnonymousUser.is_authenticated相對,始終爲False)的只讀屬性。 這是區分用戶是否已經認證的一種方法。這並不表示任何權限,也不會檢查用戶是否處於活動狀態或是否具備有效的會話。 即便正常狀況下,您將在request.user上檢查此屬性,以瞭解它是否已由AuthenticationMiddleware填充(表示當前登陸的用戶),您應該知道對於任何User實例,此屬性爲True

is_anonymous

始終爲False的只讀屬性。 這是區別User 和AnonymousUser 對象的一種方法。 通常來講,您應該優先使用is_authenticated到此屬性。

在Django更改1.10:

在舊版本中,這是一種方法。 使用它做爲方法的向後兼容性支持將在Django 2.0中被刪除。

username_validator
Django中的新功能1.10。

指向用於驗證用戶名的驗證器實例。 Python 3上的默認值爲validators.UnicodeUsernameValidator和Python 3上的validators.ASCIIUsernameValidator

要更改默認用戶名驗證器,能夠將User模型子類化,並將此屬性設置爲不一樣的驗證器實例。 例如,要在Python 3上使用ASCII用戶名:

from django.contrib.auth.models import 用戶
from django.contrib.auth.validators import ASCIIUsernameValidator

class CustomUser(User):
    username_validator = ASCIIUsernameValidator()

    class Meta:
        proxy = True  # If no new field is added.

方法

class  models. User
get_username()

返回這個User 的username。 因爲能夠將User模型交換出來,您應該使用此方法,而不是直接引用用戶名屬性。

get_full_name()

返回first_name 和last_name,之間帶有一個空格。

get_short_name()

返回first_name

set_password(raw_password)

設置用戶的密碼爲給定的原始字符串,並負責密碼的哈希。 不會保存User 對象。

None 爲raw_password 時,密碼將設置爲一個不可用的密碼,和使用set_unusable_password() 的效果同樣。

check_password(raw_password)

Returns True if the given raw string is the correct password for the user. (它負責在比較時密碼的哈希)。

set_unusable_password()

標記用戶爲沒有設置密碼。 它與密碼爲空的字符串不同。 check_password() 對這種用戶永遠不會返回True。 不會保存User對象。

若是你的認證發生在外部例如LDAP 目錄時,可能須要這個函數。

has_usable_password()

若是對這個用戶調用過set_unusable_password(),則返回False

get_group_permissions(obj=None)

返回一個用戶當前擁有的權限的set,經過用戶組

若是傳入obj,則僅返回此特定對象的組權限。http://python.usyiyi.cn/translate/django_182/ref/contrib/auth.html#

get_all_permissions(obj=None)

經過組和用戶權限返回用戶擁有的一組權限字符串。

若是傳入obj,則僅返回此特定對象的權限。

has_perm(permobj=None)

若是用戶具備指定的權限,則返回True,其中perm的格式爲"<app label>.<permission codename>"。 (請參閱有關permissions)。 若是用戶沒有激活,這個方法將永遠返回 False

若是傳入obj,此方法將不會檢查模型的權限,而是檢查此特定對象。

has_perms(perm_listobj=None)

Returns True if the user has each of the specified permissions, where each perm is in the format"<app label>.<permission codename>"若是用戶沒有激活,這個方法將永遠返回 False

若是傳入obj,此方法將不會檢查模型的權限,而是檢查特定對象。

has_module_perms(package_name)

若是用戶具備給出的package_name(Django應用的標籤)中的任何一個權限,則返回True。 若是用戶沒有激活,這個方法將永遠返回

相關文章
相關標籤/搜索