13.Django1.11.6文檔


第一步 入門

檢查版本

python -m django --version

創建第一個項目

django-admin startproject mysite

運行

 python manage.py runserver

更改端口

python manage.py runserver 8080

更改IP

python manage.py runserver 0:8000

1.創建app

創建投票應用

python manage.py startapp polls

polls/views.py

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中添加include

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.”

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),                  # /articles/2005/03/
    url(r'^articles/([0-9]{4})/([0-9]{2})/([0-9]+)/$', views.article_detail),        # /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。 如果用戶沒有激活,這個方法將永遠返回False

email_user(subjectmessagefrom_email=None**kwargs)

發生郵件給這個用戶。 如果None 為from_email,Django 將使用DEFAULT_FROM_EMAIL。 任何**kwargs 都將傳遞給底層的send_mail() 調用。

管理器方法

class  models. UserManager

User 模型有一個自定義的管理器,它具有以下輔助方法(除了BaseUserManager 提供的方法之外):

create_user(usernameemail=Nonepassword=None**extra_fields)

創建、保存並返回一個User

username 和password 設置為給出的值。 email 的域名部分將自動轉換成小寫,返回的User 對象將設置is_active 為True

如果沒有提供password,將調用 set_unusable_password()

The extra_fields keyword arguments are passed through to the User’s __init__ method to allow setting arbitrary fields on a custom user model.

參見Creating users 中的示例用法。

create_superuser(usernameemailpassword**extra_fields)

create_user() 相同,但是設置is_staff 和is_superuser 為True

AnonymousUser對象

class  models. AnonymousUser

django.contrib.auth.models.AnonymousUser 類實現了django.contrib.auth.models.User 接口,但具有下面幾個不同點:

在實際應用中,你自己可能不需要使用AnonymousUser 對象,它們用於Web 請求,在下節會講述。

Permission模型

class  models. Permission

字段

Permission 對象有以下字段:

class  models. Permission
name

必選。 255個字符或者更少. 例如: 'Can vote'.

content_type

必選。 django_content_type數據庫表的引用,其中包含每個已安裝模型的記錄。

codename

必選。 小於等於100個字符. 例如: 'can_vote'.

方法

Permission對象具有類似任何其他Django model的標准數據訪問方法。

Group模型

class  models. Group

字段

Group 對象有以下字段:

class  models. Group
name

必選。 80個字符以內。 允許任何字符. 例如: 'Awesome Users'.

permissions

多對多字段到Permission

group.permissions.set([permission_list])
group.permissions.add(permission, permission, ...)
group.permissions.remove(permission, permission, ...)
group.permissions.clear()

驗證

class  validators. ASCIIUsernameValidator
Django中的新功能1.10。

僅允許使用ASCII字母和數字的字段驗證器,除@之外, .+-_。 Python 2上的User.username的默認驗證器。

class  validators. UnicodeUsernameValidator
Django中的新功能1.10。

允許Unicode字符的字段驗證器,除@之外, .+-_。 Python 3上的User.username的默認驗證器。

登入和登出信號

auth框架使用以下signals,可用於在用戶登錄或注銷時通知。

user_logged_in()

當用戶成功登錄時發送。

與此信號一起發送的參數:

sender
剛剛登錄的用戶的類。
request
當前的 HttpRequest實例。
user
剛剛登錄的用戶實例。
user_logged_out()

在調用logout方法時發送。

sender
如上所述:剛剛注銷的用戶的類或 None,如果用戶未通過身份驗證。
request
當前的 HttpRequest實例。
user
如果用戶未通過身份驗證,剛剛注銷的用戶實例或 None
user_login_failed()

當用戶登錄失敗時發送

sender
用於認證的模塊的名稱。
credentials
包含傳遞給authenticate()或您自己的自定義身份驗證后端的用戶憑據的關鍵字參數的字典。 匹配一組“敏感”模式(包括密碼)的憑證不會作為信號的一部分發送到清除中。
request
HttpRequest對象,如果提供給 authenticate()
在Django更改1.11:

添加了request參數。

認證后端

這一節詳細講述Django自帶的認證后端。 關於如何使用它們以及如何編寫你自己的認證后端,參見用戶認證指南中的其它認證源一節

可用的認證后端

以下是django.contrib.auth.backends中可以使用的后端:

class  ModelBackend

這是Django使用的默認認證后台。 它使用由用戶標識和密碼組成的憑據進行認證。 對於Django的默認用戶模型,用戶的標識是用戶名,對於自定義的用戶模型,它通過USERNAME_FIELD 字段表示(參見Customizing Users and authentication)。

它還處理 User 和PermissionsMixin 定義的權限模型。

has_perm()get_all_permissions()get_user_permissions(), 和get_group_permissions() 允許一個對象作為特定權限參數來傳遞, 如果條件是 if obj is not None. 后端除了返回一個空的permissions 外,並不會去完成他們。

authenticate(requestusername=Nonepassword=None**kwargs)

通過調用User.check_password 驗證password 和username。 如果kwargs 沒有提供,它會使用CustomUser.USERNAME_FIELD 關鍵字從username 中獲取username。 返回一個認證過的User 或None

request is an HttpRequest and may be None if it wasn’t provided to authenticate() (which passes it on to the backend).

在Django更改1.11:

添加了request參數。

get_user_permissions(user_objobj=None)

返回user_obj具有的自己用戶權限的權限字符串集合。 如果is_anonymousis_activeFalse,則返回空集。

get_group_permissions(user_objobj=None)

返回user_obj從其所屬組的權限中獲取的權限字符集。 如果is_anonymousis_activeFalse,則返回空集。

get_all_permissions(user_objobj=None)

返回user_obj的權限字符串集,包括用戶權限和組權限。 如果is_anonymousis_activeFalse,則返回空集。

has_perm(user_objpermobj=None)

使用get_all_permissions()檢查user_obj是否具有權限字符串perm。 如果用戶不是is_active,則返回False

has_module_perms(user_objapp_label)

返回user_obj是否對應用app_label有任何權限。

user_can_authenticate()
Django中的新功能1.10。

返回是否允許用戶進行身份驗證。 To match the behavior of AuthenticationForm which prohibits inactive users from logging in, this method returns False for users with is_active=False不允許使用is_active字段的自定義用戶模型。

class  AllowAllUsersModelBackend
Django 1.10中新增。

ModelBackend相同,但是不會拒絕非激活的用戶,因為user_can_authenticate()始終返回True

使用此后端時,你可能會需要覆蓋confirm_login_allowed()方法來自定義LoginView使用的AuthenticationForm,因為它拒絕了非激活的用戶。

class  RemoteUserBackend

使用這個后端來處理Django的外部認證。 它使用在request.META['REMOTE_USER']中傳遞的用戶名進行身份驗證。 請參閱REMOTE_USER的認證文檔。

如果你需要更多的控制,你可以創建你自己的驗證后端,繼承這個類,並重寫這些屬性或方法:

RemoteUserBackend. create_unknown_user

TrueFalse。 確定是否創建用戶對象(如果尚未在數據庫中)默認為True

RemoteUserBackend. authenticate(requestremote_user)

作為remote_user傳遞的用戶名被認為是可信的。 此方法只需返回具有給定用戶名的用戶對象,如果create_unknown_userTrue則創建新的用戶對象。

如果create_unknown_userUser,並且在數據庫中找不到具有給定用戶名的None對象,則返回False

request is an HttpRequest and may be None if it wasn’t provided to authenticate() (which passes it on to the backend).

RemoteUserBackend. clean_username(username)

在使用它獲取或創建用戶對象之前,請對username執行任何清除(例如剝離LDAP DN信息)。 返回已清除的用戶名。

RemoteUserBackend. configure_user(user)

配置新創建的用戶。 此方法在創建新用戶后立即調用,並可用於執行自定義設置操作,例如根據LDAP目錄中的屬性設置用戶的組。 返回用戶對象。

RemoteUserBackend. user_can_authenticate()
Django中的新功能1.10。

返回是否允許用戶進行身份驗證。 對於is_active=False的用戶,此方法返回False。 不允許使用is_active字段的自定義用戶模型。

class  AllowAllUsersRemoteUserBackend
Django 1.10中新增。

ModelBackend相同,但是不會拒絕非激活的用戶,因為user_can_authenticate()始終返回True

實用功能

get_user(request)[source]

返回與給定的request會話關聯的用戶模型實例。

它檢查存儲在會話中的身份驗證后端是否存在於AUTHENTICATION_BACKENDS中。 如果是這樣,它使用后端的get_user()方法來檢索用戶模型實例,然后通過調用用戶模型的get_session_auth_hash()方法驗證會話。

如果存儲在會話中的身份驗證后端不再在AUTHENTICATION_BACKENDS中返回AnonymousUser的實例,如果后端的get_user()

管理后台

 管理后台站點

 Django最強大的部分之一是自動生成的管理后台界面。 它從你的模型中讀取元數據,以提供一個快速的、以模型為中心的界面,信任的用戶可以在這里管理你網站上的內容。 建議管理后台僅作為組織的一個內部管理工具使用。 它不是為了建立你的整個前端。

管理站點有許多hook用於定制,但要注意試圖專門使用這些hook。 如果你需要提供一個更加以流程為中心的界面,它抽象出數據庫表和字段的實現細節,那么可能需要編寫自己的視圖。

在本文中,我們將討論如何激活、使用和定制Django的管理后台界面。

概述

通過使用startproject創建的默認項目模版中,管理后台已啟用。

下面的一些要求作為參考:

  1. 添加 'django.contrib.admin'INSTALLED_APPS 設置中.
  2. admin有四個依賴 — django.contrib.authdjango.contrib.contenttypesdjango.contrib.messagesdjango.contrib.sessions。 如果這些應用沒有在INSTALLED_APPS列表中, 那你要添加它們。
  3. 添加django.contrib.auth.context_processors.authdjango.contrib.messages.context_processors.messagesTEMPLATES中定義的DjangoTemplates后端的'context_processors'選項中,並添加django.contrib.auth.middleware.AuthenticationMiddlewaredjango.contrib.messages.middleware.MessageMiddlewareMIDDLEWARE中。 默認情況下它們都已經添加,除非你手動調整過設置,否則不需要自己添加。
  4. 確定你的應用中的哪些模型在管理后台界面中應該可以編輯。
  5. 給上面的每個模型創建一個ModelAdmin類,封裝模型自定義的管理后台功能和選項。
  6. 實例化AdminSite並且告訴它你的每一個模型和ModelAdmin類。
  7. AdminSite實例hook到URLconf。

做完這些步驟之后,通過訪問你hook進的URL(默認是/admin/),將能夠使用你的Django管理后台站點。 如果你需要創建一個登錄用戶,可以使用createsuperuser命令。

1.ModelAdmin對象

class  ModelAdmin[source]

ModelAdmin類是模型在管理后台界面中的表示形式。 通常,它們保存在你的應用中的名為admin.py的文件里。 讓我們來看一個關於ModelAdmin類非常簡單的例子:

from django.contrib import admin
from myproject.myapp.models import Author

class AuthorAdmin(admin.ModelAdmin):
    pass
admin.site.register(Author, AuthorAdmin)

register裝飾器

register(*modelssite=django.admin.sites.site)[source]

還可以用一個裝飾器來注冊您的ModelAdmin類(這里有關裝飾器的詳細信息,請參考python中的相關說明)

from django.contrib import admin
from .models import Author

@admin.register(Author)
class AuthorAdmin(admin.ModelAdmin):
    pass

如果你使用的不是默認的AdminSite,那么這個裝飾器可以接收一些ModelAdmin作為參數,以及一個可選的關鍵字參數 site :(這里使用裝飾器來注冊需要注冊的類和模塊的,請特別留意緊跟裝飾器后面關於ModelAdmin的聲明,前面是Author,后面是PersonAdmin,我的理解是后一種情況 下注冊的類都可以用PersonAdmin來作為接口):

from django.contrib import admin
from .models import Author, Reader, Editor
from myproject.admin_site import custom_admin_site

@admin.register(Author, Reader, Editor, site=custom_admin_site)
class PersonAdmin(admin.ModelAdmin):
    pass

在python2中,如果您在類的__init__())方法中引用了模型的admin類,則不能使用此裝飾器,例如,super(PersonAdmin, self).__ init __(* args, ** kwargs)。 但是,在Python3中,通過使用super().__ init __(* args, ** kwargs) t2 >可以避免這個問題; 在python2中,你必須使用admin.site.register()而不能使用裝飾器方式。

發現admin文件

當你將 'django.contrib.admin'加入到INSTALLED_APPS 設置中, Django就會自動搜索每個應用的admin模塊並將其導入。

class  apps. AdminConfig

這是 admin的默認AppConfig 類. 它在 Django 啟動時調用autodiscover() .

class  apps. SimpleAdminConfig

這個類和 AdminConfig的作用一樣,除了它不調用autodiscover().

autodiscover()[source]

這個函數嘗試導入每個安裝的應用中的admin 模塊。 這些模塊用於注冊模型到Admin 中。

通常,當Django啟動時,您將不需要直接調用此函數作為AdminConfig調用該函數。

如果您正在使用自定義 AdminSite,則通常會將所有ModelAdmin子類導入到代碼中,並將其注冊到自定義AdminSite。 在這種情況下, 為了禁用auto-discovery,在你的INSTALLED_APPS 設置中,應該用 'django.contrib.admin'代替'django.contrib.admin.apps.SimpleAdminConfig' 。

ModelAdmin的選項

ModelAdmin 非常靈活。 它有幾個選項來處理自定義界面。 所有的選項都在 ModelAdmin 子類中定義:

from django.contrib import admin

class AuthorAdmin(admin.ModelAdmin):
    date_hierarchy = 'pub_date'
ModelAdmin. actions

在修改列表頁面可用的操作列表。 詳細信息請查看Admin actions .

ModelAdmin. actions_on_top
ModelAdmin. actions_on_bottom

控制actions的下拉框出現在頁面的位置。 默認情況下,管理員更改列表顯示頁面頂部的操作(actions_on_top True; actions_on_bottom t4 > False)。

ModelAdmin. actions_selection_counter

控制選擇計數器是否緊挨着下拉菜單action 默認的admin 更改列表將會顯示它 (actions_selection_counter True).

ModelAdmin. date_hierarchy

把 date_hierarchy 設置為在你的model 中的DateField或DateTimeField的字段名,然后更改列表頁面將包含這個字段基於日期的下拉導航。

例如:

date_hierarchy = 'pub_date'

您也可以使用__查找在相關模型上指定一個字段,例如:

date_hierarchy = 'author__pub_date'

這將根據現有數據智能地填充自己,例如,如果所有的數據都是一個月里的, 它將只顯示天級別的數據.

ModelAdmin.empty_value_display

此屬性將覆蓋空的字段(None,空字符串等)的默認顯示值。 默認值為-(破折號)。 像這樣:

from django.contrib import admin

class AuthorAdmin(admin.ModelAdmin):
    empty_value_display = '-empty-'

您還可以覆蓋empty_value_display的所有管理頁面的AdminSite.empty_value_display,或者對於特定字段,例如:

from django.contrib import admin

class AuthorAdmin(admin.ModelAdmin):
    fields = ('name', 'title', 'view_birth_date')

    def view_birth_date(self, obj):
        return obj.birth_date

    view_birth_date.empty_value_display = '???'

ModelAdmin.exclude

如果設置了這個屬性,它表示應該從表單中去掉的字段列表。

例如,讓我們來考慮下面的模型:

from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=100)
    title = models.CharField(max_length=3)
    birth_date = models.DateField(blank=True, null=True)

如果你希望title 模型的表單只包含name 和Author 字段, 你應該顯式說明fields 或exclude,像這樣:

from django.contrib import admin

class AuthorAdmin(admin.ModelAdmin):
    fields = ('name', 'title')

class AuthorAdmin(admin.ModelAdmin):
    exclude = ('birth_date',)

由於Author 模型只有三個字段,birth_datetitle和 name,上述聲明產生的表單將包含完全相同的字段。

ModelAdmin. fields

使用fields選項可以在“添加”和“更改”頁面上的表單中進行簡單的布局更改,例如僅顯示可用字段的一個子集,修改其順序或將其分組為行。 例如,可以定義一個簡單的管理表單的版本使用django.contrib.flatpages.models.FlatPage 模塊像下面這樣:

class FlatPageAdmin(admin.ModelAdmin):
    fields = ('url', 'title', 'content')

在上面的例子中, 只有字段contenttitle 和 url 將會在表單中順序的顯示. fields能夠包含在 ModelAdmin.readonly_fields 中定義的作為只讀顯示的值

對於更復雜的布局需求,請參閱fieldsets選項。

不同於 list_displayfields 選項 只包含model中的字段名或者通過form指定的表單。 只有當它們列在readonly_fields中,它才能包含callables

要在同一行顯示多個字段, 就把那些字段打包在一個元組里。 在此示例中,urltitle字段將顯示在同一行上,content字段將在其自己的行下顯示:

class FlatPageAdmin(admin.ModelAdmin):
    fields = (('url', 'title'), 'content')

如果editable=Truefieldsets 選項都不存在, Django將會默認顯示每一個不是 fields 並且 AutoField的字段, 在單一的字段集,和在模塊中定義的字段有相同的順序

ModelAdmin. fieldsets

設置fieldsets 控制管理“添加”和 “更改” 頁面的布局.

fieldsets 是一個以二元元組為元素的列表, 每一個二元元組代表一個在管理表單的 <fieldset> <fieldset> 是表單的一部分.)

二元元組的格式是 (name, field_options), 其中 name 是一個字符串相當於 fieldset的標題, field_options 是一個關於 fieldset的字典信息,一個字段列表包含在里面。

一個完整的例子, 來自於django.contrib.flatpages.models.FlatPage 模塊:

from django.contrib import admin

class FlatPageAdmin(admin.ModelAdmin):
    fieldets = (
        (None, {
            'fields': ('url', 'title', 'content', 'sites')
        }),
        ('Advanced options', {
            'classes': ('collapse',),
            'fields': ('registration_required', 'template_name'),
        }),
    )

在管理界面的結果看起來像這樣:

如果editable=Truefields 選項都不存在, Django將會默認顯示每一個不是 fieldsets 並且 AutoField的字段, 在單一的字段集,和在模塊中定義的字段有相同的順序。

field_options 字典有以下關鍵字:

  • fields

    字段名元組將顯示在該fieldset. 此鍵必選.

    例如:

{
'fields': ('first_name', 'last_name', 'address', 'city', 'state'),
}

就像fields 選項, 顯示多個字段在同一行, 包裹這些字段在一個元組. 在這個例子中, first_name 和 last_name 字段將顯示在同一行:

{
'fields': (('first_name', 'last_name'), 'address', 'city', 'state'),
}
  • fields 能夠包含定義在readonly_fields 中顯示的值作為只讀.

    如果添加可調用的名稱到fields中,相同的規則適用於fields選項: 可調用的必須在 readonly_fields列表中.

  • classes

    包含要應用於字段集的額外CSS類的列表或元組。

    例如:

{
'classes': ('wide', 'extrapretty'),
}
  • 通過默認的管理站點樣式表定義的兩個有用的classes 是 collapse 和 wideFieldsets 使用 collapse 樣式將會在初始化時展開並且替換掉一個 “click to expand” 鏈接. Fieldsets 使用 wide 樣式將會有額外的水平空格.

  • description

    一個可選擇額外文本的字符串顯示在每一個fieldset的頂部,在fieldset頭部的底下. 字符串沒有被TabularInline 渲染由於它的布局.

    記住這個值不是 HTML-escaped 當它顯示在管理接口中時. 如果你願意,這允許你包括HTML。 另外,你可以使用純文本和 django.utils.html.escape() 避免任何HTML特殊字符。

ModelAdmin. filter_horizontal

默認的, ManyToManyField 會在管理站點上顯示一個<select multiple>.(多選框). 但是,當選擇多個時多選框非常難用. 添加一個 ManyToManyField到該列表將使用一個漂亮的低調的JavaScript中的“過濾器”界面,允許搜索選項。 選和不選選項框並排出現。 參考filter_vertical 使用垂直界面。

ModelAdmin. filter_vertical

filter_horizontal相同,但使用過濾器界面的垂直顯示,其中出現在所選選項框上方的未選定選項框。

ModelAdmin. form

默認情況下, 會根據你的模型動態創建一個ModelForm。 它被用來創建呈現在添加/更改頁面上的表單。 你可以很容易的提供自己的ModelForm 來重寫表單默認的添加/修改行為。 或者,你可以使用ModelAdmin.get_form() 方法自定義默認的表單,而不用指定一個全新的表單。

 ModelAdmin.formfield_overrides

這個屬性通過一種臨時的方案來覆蓋現有的模型中Field (字段)類型在admin site中的顯示類型。 formfield_overrides 在類初始化的時候通過一個字典類型的變量來對應模型字段類型與實際重載類型的關系。

因為概念有點抽象,所以讓我們來舉一個具體的例子。 formfield_overrides 常被用於讓一個已有的字段顯示為自定義控件。 所以,試想一下我們寫了一個 RichTextEditorWidget 然后我們想用它來代替<textarea>用於輸入大段文字。 下面就是我們如何做到這樣的替換。

from django.db import models
from django.contrib import admin

# Import our custom widget and our model from where they're defined
from myapp.widgets import RichTextEditorWidget
from myapp.models import MyModel

class MyModelAdmin(admin.ModelAdmin):
    formfield_overrides = {
        models.TextField: {'widget': RichTextEditorWidget},
    }

注意字典的鍵是一個實際的字段類型,而不是一個具體的字符。 該值是另一個字典;這些參數將被傳遞給表單域的__init__()方法。

ModelAdmin. inlines

請參見下面的InlineModelAdmin對象以及ModelAdmin.get_formsets_with_inlines()

ModelAdmin. list_display

使用list_display 去控制哪些字段會顯示在Admin 的修改列表頁面中。

例如:

list_display = ('first_name', 'last_name')

如果你沒有設置__unicode__(),Admin 站點將只顯示一列表示每個對象的__str__() (Python 2 中是list_display)。

list_display中,你有4種賦值方式可以使用:

  • 模型的字段。 像這樣: 

class PersonAdmin(admin.ModelAdmin):
    list_display = ('first_name', 'last_name')

一個接受對象實例作為參數的可調用對象。 像這樣:

def upper_case_name(obj):
    return ("%s %s" % (obj.first_name, obj.last_name)).upper()
upper_case_name.short_description = 'Name'

class PersonAdmin(admin.ModelAdmin):
    list_display = (upper_case_name,)

一個表示ModelAdmin 中某個屬性的字符串。 行為與可調用對象相同。 像這樣:

class PersonAdmin(admin.ModelAdmin):
    list_display = ('upper_case_name',)

    def upper_case_name(self, obj):
        return ("%s %s" % (obj.first_name, obj.last_name)).upper()
    upper_case_name.short_description = 'Name'

表示模型中某個屬性的字符串。 它的行為與可調用對象幾乎相同,但這時的self 是模型實例。 這里是一個完整的模型示例︰

from django.db import models
from django.contrib import admin

class Person(models.Model):
    name = models.CharField(max_length=50)
    birthday = models.DateField()

    def decade_born_in(self):
        return self.birthday.strftime('%Y')[:3] + "0's"
    decade_born_in.short_description = 'Birth decade'

class PersonAdmin(admin.ModelAdmin):
    list_display = ('name', 'decade_born_in')

關於list_display 要注意的幾個特殊情況︰

  • 如果字段是一個__unicode__(),Django 將展示相關對象的__str__() (Python 2 上是ForeignKey)。

  • 不支持ManyToManyField 字段, 因為這將意味着對表中的每一行執行單獨的SQL 語句。 如果盡管如此你仍然想要這樣做,請給你的模型一個自定義的方法,並將該方法名稱添加到 list_display。 list_display 的更多自定義方法請參見下文)。

  • 如果該字段為True 或NullBooleanField,Django 會顯示漂亮的"on"或"off"圖標而不是BooleanField 或False

  • 如果給出的字符串是模型、ModelAdmin 的一個方法或可調用對象,Django 將默認轉義HTML輸出。 要轉義用戶輸入並允許自己的未轉義標簽,請使用format_html()

    下面是一個完整的示例模型︰

from django.db import models
from django.contrib import admin
from django.utils.html import format_html

class Person(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    color_code = models.CharField(max_length=6)

    def colored_name(self):
        return format_html(
            '<span style="color: #{};">{} {}</span>',
            self.color_code,
            self.first_name,
            self.last_name,
        )

class PersonAdmin(admin.ModelAdmin):
    list_display = ('first_name', 'last_name', 'colored_name')
  • 自1.9版以來已棄用 在舊版本中,您可以在方法中添加一個allow_tags屬性來防止自動轉義。 因為使用format_html()format_html_join()mark_safe()更安全,因此此屬性已被棄用。

  • 正如一些例子已經證明,當使用可調用,模型方法或ModelAdmin方法時,您可以通過向可調用添加short_description屬性來自定義列的標題。

  • 如果一個字段的值是None,一個空字符串,或沒有元素的iterable,Django將顯示-(破折號)。 您可以使用AdminSite.empty_value_display重寫此項:

from django.contrib import admin

admin.site.empty_value_display = '(None)'

您也可以使用ModelAdmin.empty_value_display

class PersonAdmin(admin.ModelAdmin):
    empty_value_display = 'unknown'

或在現場一級:

class PersonAdmin(admin.ModelAdmin):
    list_display = ('name', 'birth_date_view')

    def birth_date_view(self, obj):
         return obj.birth_date

    birth_date_view.empty_value_display = 'unknown'

如果給出的字符串是模型、True 的一個方法或一個返回 True 或False 的可調用的方法,然后賦值給方法的boolean 屬性一個ModelAdmin值, Django 將顯示漂亮的"on"或"off"圖標,。

下面是一個完整的示例模型︰

from django.db import models
from django.contrib import admin

class Person(models.Model):
    first_name = models.CharField(max_length=50)
    birthday = models.DateField()

    def born_in_fifties(self):
        return self.birthday.strftime('%Y')[:3] == '195'
    born_in_fifties.boolean = True

class PersonAdmin(admin.ModelAdmin):
    list_display = ('name', 'born_in_fifties')

list_display(Python 2 上是__unicode__())方法在__str__() 中同樣合法,就和任何其他模型方法一樣,所以下面這樣寫完全OK︰

list_display = ('__str__', 'some_other_field')

通常情況下,list_display 的元素如果不是實際的數據庫字段不能用於排序(因為 Django 所有的排序都在數據庫級別)。

然而,如果list_display 元素表示數據庫的一個特定字段,你可以通過設置 元素的admin_order_field 屬性表示這一事實。

像這樣:

from django.db import models
from django.contrib import admin
from django.utils.html import format_html

class Person(models.Model):
    first_name = models.CharField(max_length=50)
    color_code = models.CharField(max_length=6)

    def colored_first_name(self):
        return format_html(
            '<span style="color: #{};">{}</span>',
            self.color_code,
            self.first_name,
        )

    colored_first_name.admin_order_field = 'first_name'

class PersonAdmin(admin.ModelAdmin):
    list_display = ('first_name', 'colored_first_name')

上面的示例告訴Django 在Admin 中按照按first_name 排序時依據colored_first_name 字段。

要表示按照admin_order_field 降序排序,你可以在該字段名稱前面使用一個連字符前綴。 使用上面的示例,這會看起來像︰

colored_first_name.admin_order_field = '-first_name'

admin_order_field支持查詢查詢,以按相關模型的值進行排序。 此示例包括列表顯示中的“作者名字”列,並允許以名字排序:

class Blog(models.Model):
    title = models.CharField(max_length=255)
    author = models.ForeignKey(Person, on_delete=models.CASCADE)

class BlogAdmin(admin.ModelAdmin):
    list_display = ('title', 'author', 'author_first_name')

    def author_first_name(self, obj):
        return obj.author.first_name

    author_first_name.admin_order_field = 'author__first_name'

list_display 的元素也可以是屬性。 不過請注意,由於方式屬性在Python 中的工作方式,在屬性上設置property() 只能使用 short_description 函數, 能使用@property 裝飾器。

像這樣:

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

    def my_property(self):
        return self.first_name + ' ' + self.last_name
    my_property.short_description = "Full name of the person"

    full_name = property(my_property)

class PersonAdmin(admin.ModelAdmin):
    list_display = ('full_name',)
  • <th> 中的字段名稱還將作為HTML 輸出的CSS 類, 形式為每個column-<field_name> 元素上具有list_display。 例如這可以用於在CSS 文件中設置列的寬度。

  • Django 會嘗試以下面的順序解釋list_display 的每個元素︰

    • 模型的字段。
    • 可調用對象。
    • 表示ModelAdmin 屬性的字符串。
    • 表示模型屬性的字符串。

    例如,如果first_name 既是模型的一個字段又是ModelAdmin 的一個屬性,使用的將是模型字段。

ModelAdmin.list_display_links

使用list_display_links可以控制list_display中的字段是否應該鏈接到對象的“更改”頁面。

默認情況下,更改列表頁將鏈接第一列 - list_display中指定的第一個字段 - 到每個項目的更改頁面。 但是list_display_links可讓您更改此設置:

  • 將其設置為None,根本不會獲得任何鏈接。

  • 將其設置為要將其列轉換為鏈接的字段列表或元組(格式與list_display相同)。

    您可以指定一個或多個字段。 只要這些字段出現在list_display中,Django不會關心多少(或多少)字段被鏈接。 唯一的要求是,如果要以這種方式使用list_display_links,則必須定義list_display

在此示例中,first_namelast_name字段將鏈接到更改列表頁面上:

class PersonAdmin(admin.ModelAdmin):
    list_display = ('first_name', 'last_name', 'birthday')
    list_display_links = ('first_name', 'last_name')

在此示例中,更改列表頁面網格將沒有鏈接:

class AuditEntryAdmin(admin.ModelAdmin):
    list_display = ('timestamp', 'message')
    list_display_links = None

ModelAdmin.list_editable

list_editable設置為模型上的字段名稱列表,這將允許在更改列表頁面上進行編輯。 也就是說,list_editable中列出的字段將在更改列表頁面上顯示為表單小部件,允許用戶一次編輯和保存多行。

ModelAdmin.list_filter

list_filter 設置激活激活Admin 修改列表頁面右側欄中的過濾器,如下面的屏幕快照所示︰

list_filter 應該是一個列表或元組,其每個元素應該是下面類型中的一種:

  • 字段名稱,其指定的字段應該是ManyToManyFieldIntegerFieldForeignKeyDateFieldCharFieldBooleanFieldDateTimeField,例如︰

class PersonAdmin(admin.ModelAdmin):
    list_filter = ('is_staff', 'company')

 list_filter 中的字段名稱也可以使用__ 查找跨關聯關系,例如︰

class PersonAdmin(admin.UserAdmin):
    list_filter = ('company__name',)

一個繼承自django.contrib.admin.SimpleListFilter 的類,你需要給它提供title 和 parameter_name 屬性並重寫lookupsqueryset 方法,例如︰

 

from datetime import date

from django.contrib import admin
from django.utils.translation import ugettext_lazy as _

class DecadeBornListFilter(admin.SimpleListFilter):
    # Human-readable title which will be displayed in the
    # right admin sidebar just above the filter options.
    title = _('decade born')

    # Parameter for the filter that will be used in the URL query.
    parameter_name = 'decade'

    def lookups(self, request, model_admin):
        """
        Returns a list of tuples. The first element in each
        tuple is the coded value for the option that will
        appear in the URL query. The second element is the
        human-readable name for the option that will appear
        in the right sidebar.
        """
        return (
            ('80s', _('in the eighties')),
            ('90s', _('in the nineties')),
        )

    def queryset(self, request, queryset):
        """
        Returns the filtered queryset based on the value
        provided in the query string and retrievable via
        `self.value()`.
        """
        # Compare the requested value (either '80s' or '90s')
        # to decide how to filter the queryset.
        if self.value() == '80s':
            return queryset.filter(birthday__gte=date(1980, 1, 1),
                                    birthday__lte=date(1989, 12, 31))
        if self.value() == '90s':
            return queryset.filter(birthday__gte=date(1990, 1, 1),
                                    birthday__lte=date(1999, 12, 31))

class PersonAdmin(admin.ModelAdmin):
    list_filter = (DecadeBornListFilter,)

一個元組,第一個元素是字段名稱,第二個元素是從繼承自django.contrib.admin.FieldListFilter 的一個類,例如︰

class PersonAdmin(admin.ModelAdmin):
    list_filter = (
        ('is_staff', admin.BooleanFieldListFilter),
    )

您可以使用RelatedOnlyFieldListFilter將相關模型的選擇限制在該關系中涉及的對象中:

class BookAdmin(admin.ModelAdmin):
    list_filter = (
        ('author', admin.RelatedOnlyFieldListFilter),
    )

假設author 是User 模型的一個ForeignKey,這將限制list_filter 的選項為編寫過書籍的用戶,而不是所有用戶。

 列表過濾器通常僅在過濾器有多個選擇時才會出現。 過濾器的has_output()方法控制是否顯示。

也可以指定自定義模板用於渲染列表篩選器︰

class FilterWithCustomTemplate(admin.SimpleListFilter):
    template = "custom_template.html"
ModelAdmin. list_max_show_all

設置list_max_show_all以控制在“顯示所有”管理更改列表頁面上可以顯示的項目數。 只有當總結果計數小於或等於此設置時,管理員才會在更改列表上顯示“顯示全部”鏈接。 默認情況下,設置為200

ModelAdmin. list_per_page

list_per_page 設置控制Admin 修改列表頁面每頁中顯示多少項。 默認設置為100

設置list_select_related以告訴Django在檢索管理更改列表頁面上的對象列表時使用select_related()。 這可以節省大量的數據庫查詢。

該值應該是布爾值,列表或元組。 默認值為False

當值為True時,將始終調用select_related()。 當值設置為False時,如果存在任何ForeignKey,Django將查看list_display並調用select_related() 。

如果您需要更細粒度的控制,請使用元組(或列表)作為list_select_related的值。 空元組將阻止Django調用select_related。 任何其他元組將直接傳遞到select_related作為參數。 像這樣:

class ArticleAdmin(admin.ModelAdmin):
    list_select_related = ('author', 'category')

將會調用select_related('author', 'category').

如果需要根據請求指定動態值,則可以實現get_list_select_related()方法。

ModelAdmin. ordering

設置ordering以指定如何在Django管理視圖中對對象列表進行排序。 這應該是與模型的ordering參數格式相同的列表或元組。

如果沒有提供,Django管理員將使用模型的默認排序。

如果您需要指定動態順序(例如,根據用戶或語言),您可以實施get_ordering()方法。

ModelAdmin. paginator

paginator類用於分頁。 默認情況下,使用django.core.paginator.Paginator。 如果自定義paginator類沒有與django.core.paginator.Paginator相同的構造函數接口,則還需要為ModelAdmin.get_paginator() 。

ModelAdmin. prepopulated_fields

prepopulated_fields設置為將字段名稱映射到其應預先填充的字段的字典:

class ArticleAdmin(admin.ModelAdmin):
    prepopulated_fields = {"slug": ("title",)}

設置時,給定字段將使用一些JavaScript來從分配的字段填充。 此功能的主要用途是自動從一個或多個其他字段生成SlugField字段的值。 生成的值是通過連接源字段的值,然后將該結果轉換為有效的字節(例如用空格替換破折號)來生成的。

prepopulated_fields 不能接受 DateTimeFieldForeignKeyOneToOneField, 和 ManyToManyField 字段.

ModelAdmin.  preserve_filters T0> 

管理員現在在創建,編輯或刪除對象后保留列表視圖中的過濾器。 您可以將此屬性設置為False,以恢復之前清除過濾器的行為。

ModelAdmin. radio_fields

默認情況下,Django的管理員為ForeignKey或者有choices集合的字段使用一個下拉菜單(<select>). 如果radio_fields中存在字段,Django將使用單選按鈕接口。 假設groupPerson模型上的 ForeignKey

class PersonAdmin(admin.ModelAdmin):
    radio_fields = {"group": admin.VERTICAL}

您可以選擇使用django.contrib.admin模塊中的VERTICALHORIZONTAL

除非是choices或設置了ForeignKey,否則不要在radio_fields中包含字段。

ModelAdmin. raw_id_fields

默認情況下,Django的管理員為ForeignKey的字段使用一個下拉菜單(<select>). 有時候你不想在下拉菜單中顯示所有相關實例產生的開銷。

raw_id_fields是一個字段列表,你希望將ForeignKey 或ManyToManyField 轉換成Input 窗口部件:

class ArticleAdmin(admin.ModelAdmin):
    raw_id_fields = ("newspaper",)

如果該字段是一個ForeignKeyInput raw_id_fields Widget 應該包含一個外鍵,或者如果字段是一個ManyToManyField 則應該是一個逗號分隔的值的列表。 raw_id_fields Widget 在字段旁邊顯示一個放大鏡按鈕,允許用戶搜索並選擇一個值︰

ModelAdmin.readonly_fields

默認情況下,管理后台將所有字段顯示為可編輯。 此選項中的任何字段(應為listtuple)將按原樣顯示其數據,不可編輯;它們也被排除在用於創建和編輯的ModelForm之外。 請注意,指定ModelAdmin.fieldsModelAdmin.fieldsets時,只讀字段必須包含進去才能顯示(否則將被忽略)。

如果在未通過ModelAdmin.fieldsModelAdmin.fieldsets定義顯式排序的情況下使用readonly_fields,則它們將在所有可編輯字段之后添加。

只讀字段不僅可以顯示模型字段中的數據,還可以顯示模型方法的輸出或ModelAdmin類本身的方法。 這與ModelAdmin.list_display的行為非常相似。 這提供了一種使用管理界面提供對正在編輯的對象的狀態的反饋的簡單方法,例如:

from django.contrib import admin
from django.utils.html import format_html_join
from django.utils.safestring import mark_safe

class PersonAdmin(admin.ModelAdmin):
    readonly_fields = ('address_report',)

    def address_report(self, instance):
        # assuming get_full_address() returns a list of strings
        # for each line of the address and you want to separate each
        # line by a linebreak
        return format_html_join(
            mark_safe('<br/>'),
            '{}',
            ((line,) for line in instance.get_full_address()),
        ) or mark_safe("<span class='errors'>I can't determine this address.</span>")

    # short_description的功能類似一個模型字段的verbose_name
    address_report.short_description = "Address"
ModelAdmin. save_as

設置save_as以在管理員更改表單上啟用“另存為”功能。

通常,對象有三個保存選項:“保存”,“保存並繼續編輯”和“保存並添加其他”。 如果save_asTrue,“保存並添加另一個”將被替換為創建新對象(使用新ID)而不是更新的“另存為”按鈕現有的對象。

默認情況下,save_as 設置為False

ModelAdmin. save_as_continue
Django中的新功能1.10。

save_as=True時,保存新對象后的默認重定向是該對象的更改視圖。 如果設置save_as_continue=False,則重定向將是更改列表視圖。

默認情況下,save_as_continue設置為True

ModelAdmin. save_on_top

設置save_on_top可在表單頂部添加保存按鈕。

通常,保存按鈕僅出現在表單的底部。 如果您設置save_on_top,則按鈕將同時顯示在頂部和底部。

默認情況下,save_on_top設置為False

ModelAdmin. search_fields

search_fields 設置啟用Admin 更改列表頁面上的搜索框。 此屬性應設置為每當有人在該文本框中提交搜索查詢將搜索的字段名稱的列表。

這些字段應該是某種文本字段,如CharField 或TextField。 你還可以通過查詢API 的"跟隨"符號進行ForeignKeyManyToManyField 上的關聯查找:

search_fields = ['foreign_key__related_fieldname']

例如,如果您有一個作者的博客條目,以下定義將允許通過作者的電子郵件地址搜索博客條目

search_fields = ['user__email']

如果有人在Admin 搜索框中進行搜索,Django 拆分搜索查詢為單詞並返回包含每個單詞的所有對象,不區分大小寫,其中每個單詞必須在至少一個search_fields。 例如,如果search_fields 設置為['first_name', 'last_name'],用戶搜索john lennon,Django 的行為將相當於下面的這個WHERE SQL 子句︰

WHERE (first_name ILIKE '%john%' OR last_name ILIKE '%john%')
AND (first_name ILIKE '%lennon%' OR last_name ILIKE '%lennon%')

若要更快和/或更嚴格的搜索,請在字典名稱前面加上前綴︰

使用'^'運算符來匹配從字段開始的起始位置。 例如,如果search_fields 設置為['^first_name', '^last_name'],用戶搜索john lennon 時,Django 的行為將等同於下面這個WHERE SQL 字句:

WHERE (first_name ILIKE 'john%' OR last_name ILIKE 'john%')
AND (first_name ILIKE 'lennon%' OR last_name ILIKE 'lennon%')

此查詢比正常'%john%' 查詢效率高,因為數據庫只需要檢查某一列數據的開始,而不用尋找整列數據。 另外,如果列上有索引,有些數據庫可能能夠對於此查詢使用索引,即使它是LIKE 查詢。

=

使用'='運算符不區分大小寫的精確匹配。 例如,如果search_fields 設置為['=first_name', '=last_name'],用戶搜索john lennon 時,Django 的行為將等同於下面這個WHERE SQL 字句:

WHERE (first_name ILIKE 'john' OR last_name ILIKE 'john')
AND (first_name ILIKE 'lennon' OR last_name ILIKE 'lennon')

注意,該查詢輸入通過空格分隔,所以根據這個示例,目前不能夠搜索first_name 精確匹配'john winston'(包含空格)的所有記錄。

@
使用'@'運算符執行全文匹配。 這就像默認的搜索方法,但使用索引。 目前這只適用於MySQL。

如果你需要自定義搜索,你可以使用ModelAdmin.get_search_results() 來提供附件的或另外一種搜索行為。

ModelAdmin. show_full_result_count

設置show_full_result_count以控制是否應在過濾的管理頁面上顯示對象的完整計數(例如99 結果 103 total))。 如果此選項設置為False,則像99 結果 (顯示 

默認情況下,show_full_result_count=True生成一個查詢,對表執行完全計數,如果表包含大量行,這可能很昂貴。

ModelAdmin. view_on_site

設置view_on_site以控制是否顯示“在網站上查看”鏈接。 此鏈接將帶您到一個URL,您可以在其中顯示已保存的對象。

此值可以是布爾標志或可調用的。 如果True(默認值),對象的get_absolute_url()方法將用於生成網址。

如果您的模型有get_absolute_url()方法,但您不想顯示“在網站上查看”按鈕,則只需將view_on_site設置為False

from django.contrib import admin

class PersonAdmin(admin.ModelAdmin):
    view_on_site = False

如果它是可調用的,它接受模型實例作為參數。 像這樣:

from django.contrib import admin
from django.urls import reverse

class PersonAdmin(admin.ModelAdmin):
    def view_on_site(self, obj):
        url = reverse('person-detail', kwargs={'slug': obj.slug})
        return 'https://example.com' + url

自定義模板選項

Overriding admin templates 一節描述如何重寫或擴展默認Admin 模板。 使用以下選項來重寫ModelAdmin 視圖使用的默認模板︰

ModelAdmin. add_form_template

add_view() 使用的自定義模板的路徑。

ModelAdmin. change_form_template

change_view() 使用的自定義模板的路徑。

ModelAdmin. change_list_template

changelist_view() 使用的自定義模板的路徑。

ModelAdmin. delete_confirmation_template

delete_view() 使用的自定義模板,用於刪除一個或多個對象時顯示一個確認頁。

ModelAdmin. delete_selected_confirmation_template

delete_selected 使用的自定義模板,用於刪除一個或多個對象時顯示一個確認頁。 參見actions documentation

ModelAdmin. object_history_template

history_view() 使用的自定義模板的路徑。

ModelAdmin. popup_response_template
Django中的新功能1.11。

response_add()response_change()response_delete()使用的自定義模板的路徑。

2.ModelAdmin的方法

 ModelAdmin.save_model(requestobjformchange)[source]

The save_model method is given the HttpRequest, a model instance, a ModelForm instance, and a boolean value based on whether it is adding or changing the object. 覆蓋此方法允許進行前或后保存操作。 使用Model.save()調用super().save_model()來保存對象。

例如,在保存之前將request.user附加到對象:

from django.contrib import admin

class ArticleAdmin(admin.ModelAdmin):
    def save_model(self, request, obj, form, change):
        obj.user = request.user
        super(ArticleAdmin, self).save_model(request, obj, form, change)
ModelAdmin. delete_model(requestobj)[source]

delete_model方法給出了HttpRequest和模型實例。 覆蓋此方法允許進行前或后刪除操作。 使用Model.delete()調用super().delete_model()來刪除對象。

ModelAdmin. save_formset(requestformformsetchange)[source]

ModelForm方法是給予HttpRequest,父save_formset實例和基於是否添加或更改父對象的布爾值。

例如,要將request.user附加到每個已更改的formset模型實例:

class ArticleAdmin(admin.ModelAdmin):
    def save_formset(self, request, form, formset, change):
        instances = formset.save(commit=False)
        for obj in formset.deleted_objects:
            obj.delete()
        for instance in instances:
            instance.user = request.user
            instance.save()
        formset.save_m2m()

ModelAdmin.get_ordering(request)

get_ordering方法將request作為參數,並且預期返回listtuple,以便類似於ordering屬性。 像這樣:

class PersonAdmin(admin.ModelAdmin):

    def get_ordering(self, request):
        if request.user.is_superuser:
            return ['name', 'rank']
        else:
            return ['name']

ModelAdmin.get_search_results(requestquerysetsearch_term)[source]

get_search_results方法將顯示的對象列表修改為與提供的搜索項匹配的對象列表。 它接受請求,應用當前過濾器的查詢集以及用戶提供的搜索項。 它返回一個包含被修改以實現搜索的查詢集的元組,以及一個指示結果是否可能包含重復項的布爾值。

默認實現搜索在ModelAdmin.search_fields中命名的字段。

此方法可以用您自己的自定義搜索方法覆蓋。 例如,您可能希望通過整數字段搜索,或使用外部工具(如Solr或Haystack)。您必須確定通過搜索方法實現的查詢集更改是否可能在結果中引入重復項,並在返回值的第二個元素中返回True

例如,要通過nameage搜索,您可以使用:

class PersonAdmin(admin.ModelAdmin):
    list_display = ('name', 'age')
    search_fields = ('name',)

    def get_search_results(self, request, queryset, search_term):
        queryset, use_distinct = super(PersonAdmin, self).get_search_results(request, queryset, search_term)
        try:
            search_term_as_int = int(search_term)
        except ValueError:
            pass
        else:
            queryset |= self.model.objects.filter(age=search_term_as_int)
        return queryset, use_distinct

這個實現比search_fields ('name', '= age') ,例如,這將導致數字字段的字符串比較... 要么 UPPER( “polls_choice”。 “票” ::文) UPPER( '4') 在PostgreSQL上。

ModelForm方法給出了HttpRequest,父save_related實例,內聯表單列表和一個布爾值,添加或更改。 在這里,您可以對與父級相關的對象執行任何預保存或后保存操作。 請注意,此時父對象及其形式已保存。

ModelAdmin. get_readonly_fields(requestobj=None)

list方法在添加表單上給予tupleobj(或HttpRequest),希望返回將以只讀形式顯示的字段名稱的get_readonly_fieldsNone,如上面在ModelAdmin.readonly_fields部分中所述。

ModelAdmin. get_prepopulated_fields(requestobj=None)

dictionary方法在添加表單上給予objHttpRequest(或get_prepopulated_fields),預期返回None,如上面在ModelAdmin.prepopulated_fields部分中所述。

ModelAdmin. get_list_display(request)[source]

list方法被賦予HttpRequest,並且希望返回字段名稱的get_list_displaytuple顯示在如上所述的ModelAdmin.list_display部分中的changelist視圖上。

The get_list_display_links method is given the HttpRequest and the list or tuple returned byModelAdmin.get_list_display()預期將返回更改列表上將鏈接到更改視圖的字段名稱的tuplelistNone,如上所述在ModelAdmin.list_display_links部分中。

ModelAdmin. get_exclude(requestobj=None)
Django中的新功能1.11。

The get_exclude method is given the HttpRequest and the obj being edited (or None on an add form) and is expected to return a list of fields, as described in ModelAdmin.exclude.

ModelAdmin. get_fields(requestobj=None)[source]

obj方法被賦予HttpRequestget_fields被編輯(或在添加表單上None),希望返回字段列表,如上面在ModelAdmin.fields部分中所述。

ModelAdmin. get_fieldsets(requestobj=None)

<fieldset>方法是在添加表單上給予objHttpRequest(或get_fieldsets),期望返回二元組列表,其中每個二元組在管理表單頁面上表示None,如上面在ModelAdmin.fieldsets部分。

ModelAdmin. get_list_filter(request)[source]

HttpRequest方法被賦予get_list_filter,並且期望返回與list_filter屬性相同類型的序列類型。

The get_list_select_related method is given the HttpRequest and should return a boolean or list as ModelAdmin.list_select_related does.

ModelAdmin. get_search_fields(request)[source]

HttpRequest方法被賦予get_search_fields,並且期望返回與search_fields屬性相同類型的序列類型。

ModelAdmin. get_inline_instances(requestobj=None)[source]

list方法在添加表單上給予tupleobj(或HttpRequest),預期會返回get_inline_instancesNoneInlineModelAdmin對象,如下面的InlineModelAdmin部分所述。 例如,以下內容將返回內聯,而不進行基於添加,更改和刪除權限的默認過濾:

class MyModelAdmin(admin.ModelAdmin):
    inlines = (MyInline,)

    def get_inline_instances(self, request, obj=None):
        return [inline(self.model, self.admin_site) for inline in self.inlines]

如果覆蓋此方法,請確保返回的內聯是inlines中定義的類的實例,或者在添加相關對象時可能會遇到“錯誤請求”錯誤。

ModelAdmin. get_urls()[source]

get_urls 的ModelAdmin 方法返回ModelAdmin 將要用到的URLs,方式與URLconf 相同。 因此,你可以用URL dispatcher 中所述的方式擴展它們︰

class MyModelAdmin(admin.ModelAdmin):
    def get_urls(self):
        urls = super(MyModelAdmin, self).get_urls()
        my_urls = [
            url(r'^my_view/$', self.my_view),
        ]
        return my_urls + urls

    def my_view(self, request):
        # ...
        context = dict(
           # Include common variables for rendering the admin template.
           self.admin_site.each_context(request),
           # Anything else you want in the context...
           key=value,
        )
        return TemplateResponse(request, "sometemplate.html", context)

如果你想要使用Admin 的布局,可以從admin/base_site.html 擴展︰

{% extends "admin/base_site.html" %}
{% block content %}
...
{% endblock %}

但是, 上述定義的函數self.my_view 將遇到兩個問題:

  •  執行任何權限檢查,所以會向一般公眾開放。
  • 提供任何HTTP頭的詳細信息以防止緩存。 這意味着,如果頁面從數據庫檢索數據,而且緩存中間件處於活動狀態,頁面可能顯示過時的信息。

因為這通常不是你想要的,Django 提供一個方便的封裝函數來檢查權限並標記視圖為不可緩存的。 這個包裝器在ModelAdmin實例中是AdminSite.admin_view()(即self.admin_site.admin_view);使用它像這樣:

class MyModelAdmin(admin.ModelAdmin):
    def get_urls(self):
        urls = super(MyModelAdmin, self).get_urls()
        my_urls = [
            url(r'^my_view/$', self.admin_site.admin_view(self.my_view))
        ]
        return my_urls + urls

請注意上述第5行中的被封裝的視圖︰

url(r'^my_view/$', self.admin_site.admin_view(self.my_view))

此包裝將保護self.my_view未經授權的訪問,並將應用django.views.decorators.cache.never_cache()裝飾器,以確保緩存不緩存中間件是活動的。

如果該頁面是可緩存的,但你仍然想要執行權限檢查,你可以傳遞cacheable=True 的AdminSite.admin_view() 參數︰

url(r'^my_view/$', self.admin_site.admin_view(self.my_view, cacheable=True))

ModelAdmin視圖具有model_admin屬性。 其他AdminSite視圖具有admin_site屬性。

ModelAdmin. get_form(requestobj=None**kwargs)[source]

返回Admin中添加和更改視圖使用的ModelForm 類,請參閱add_view() 和 change_view()

其基本的實現是使用modelform_factory() 來子類化form,修改如fields 和exclude屬性。 所以,舉個例子,如果你想要為超級用戶提供額外的字段,你可以換成不同的基類表單,就像這樣︰

class MyModelAdmin(admin.ModelAdmin):
    def get_form(self, request, obj=None, **kwargs):
        if request.user.is_superuser:
            kwargs['form'] = MySuperuserForm
        return super(MyModelAdmin, self).get_form(request, obj, **kwargs)

你也可以簡單地直接返回一個自定義的ModelForm 類。

ModelAdmin. get_formsets_with_inlines(requestobj=None)[source]

產量(FormSetInlineModelAdmin)對用於管理添加和更改視圖。

例如,如果您只想在更改視圖中顯示特定的內聯,則可以覆蓋get_formsets_with_inlines,如下所示:

class MyModelAdmin(admin.ModelAdmin):
    inlines = [MyInline, SomeOtherInline]

    def get_formsets_with_inlines(self, request, obj=None):
        for inline in self.get_inline_instances(request, obj):
            # hide MyInline in the add view
            if isinstance(inline, MyInline) and obj is 沒有:
                continue
            yield inline.get_formset(request, obj), inline

ModelAdmin.formfield_for_foreignkey(db_fieldrequest**kwargs)

formfield_for_foreignkey上的ModelAdmin方法允許覆蓋外鍵字段的默認窗體字段。 例如,要根據用戶返回此外鍵字段的對象子集:

class MyModelAdmin(admin.ModelAdmin):
    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        if db_field.name == "car":
            kwargs["queryset"] = Car.objects.filter(owner=request.user)
        return super(MyModelAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)

這使用User實例過濾Car外鍵字段,只顯示由HttpRequest實例擁有的汽車。

ModelAdmin. formfield_for_manytomany(db_fieldrequest**kwargs)

formfield_for_foreignkey方法類似,可以覆蓋formfield_for_manytomany方法來更改多對多字段的默認窗體字段。 例如,如果所有者可以擁有多個汽車,並且汽車可以屬於多個所有者 - 多對多關系,則您可以過濾Car外鍵字段,僅顯示由User

class MyModelAdmin(admin.ModelAdmin):
    def formfield_for_manytomany(self, db_field, request, **kwargs):
        if db_field.name == "cars":
            kwargs["queryset"] = Car.objects.filter(owner=request.user)
        return super(MyModelAdmin, self).formfield_for_manytomany(db_field, request, **kwargs)

ModelAdmin.formfield_for_choice_field(db_fieldrequest**kwargs)

formfield_for_choice_fieldformfield_for_manytomany方法類似,可以覆蓋formfield_for_foreignkey方法更改已聲明選擇的字段的默認窗體字段。 例如,如果超級用戶可用的選擇應與正式工作人員可用的選項不同,則可按以下步驟操作:

class MyModelAdmin(admin.ModelAdmin):
    def formfield_for_choice_field(self, db_field, request, **kwargs):
        if db_field.name == "status":
            kwargs['choices'] = (
                ('accepted', 'Accepted'),
                ('denied', 'Denied'),
            )
            if request.user.is_superuser:
                kwargs['choices'] += (('ready', 'Ready for deployment'),)
        return super(MyModelAdmin, self).formfield_for_choice_field(db_field, request, **kwargs)

在表單域中設置的任何choices屬性將僅限於表單字段。 如果模型上的相應字段有選擇集,則提供給表單的選項必須是這些選擇的有效子集,否則,在保存模型本身之前驗證模型本身時,表單提交將失敗並顯示ValidationError 。

ModelAdmin. get_changelist(request**kwargs)[source]

返回要用於列表的Changelist類。 默認情況下,使用django.contrib.admin.views.main.ChangeList。 通過繼承此類,您可以更改列表的行為。

ModelAdmin. get_changelist_form(request**kwargs)[source]

返回ModelForm類以用於更改列表頁面上的Formset。 要使用自定義窗體,例如:

from django import forms

class MyForm(forms.ModelForm):
    pass

class MyModelAdmin(admin.ModelAdmin):
    def get_changelist_form(self, request, **kwargs):
        return MyForm

ModelAdmin.get_changelist_formset(request**kwargs)[source]

如果使用list_editable,則返回ModelFormSet類以在更改列表頁上使用。 要使用自定義表單集,例如:

from django.forms import BaseModelFormSet

class MyAdminFormSet(BaseModelFormSet):
    pass

class MyModelAdmin(admin.ModelAdmin):
    def get_changelist_formset(self, request, **kwargs):
        kwargs['formset'] = MyAdminFormSet
        return super(MyModelAdmin, self).get_changelist_formset(request, **kwargs)
ModelAdmin. lookup_allowed(lookupvalue)

可以從URL查詢字符串中的查找過濾更改列表頁面中的對象。 例如,這是list_filter的工作原理。 查詢與QuerySet.filter()(例如user__email=user@example.com)中使用的查找類似。 由於查詢字符串中的查詢可以由用戶操縱,因此必須對其進行清理,以防止未經授權的數據暴露。

給定了lookup_allowed()方法,從查詢字符串(例如'user__email')和相應的值(例如'user@example.com'),並返回一個布爾值,表示是否允許使用參數過濾changelist的QuerySet。 如果lookup_allowed()返回False,則會引發DisallowedModelAdminLookupSuspiciousOperation的子類)。

默認情況下,lookup_allowed()允許訪問模型的本地字段,list_filter中使用的字段路徑(但不是來自get_list_filter()的路徑)並且limit_choices_to所需的查找在raw_id_fields中正常運行。

覆蓋此方法可自定義ModelAdmin子類允許的查找。

ModelAdmin. has_add_permission(request)

如果允許添加對象,則應返回True,否則返回False

ModelAdmin. has_change_permission(requestobj=None)

如果允許編輯obj,則應返回True,否則返回False。 如果obj為False,則應返回TrueNone以指示是否允許對此類對象進行編輯(例如,False將被解釋為意味着當前用戶不允許編輯此類型的任何對象)。

ModelAdmin. has_delete_permission(requestobj=None)

如果允許刪除obj,則應返回True,否則返回False。 如果obj是None,應該返回TrueFalse以指示是否允許刪除此類型的對象(例如,False將被解釋為意味着當前用戶不允許刪除此類型的任何對象)。

ModelAdmin. has_module_permission(request)

如果在管理索引頁上顯示模塊並允許訪問模塊的索引頁,則應返回True,否則False。 默認情況下使用User.has_module_perms()。 覆蓋它不會限制對添加,更改或刪除視圖的訪問,has_add_permission()has_change_permission()has_delete_permission()用於那。

ModelAdmin. get_queryset(request)

ModelAdmin上的get_queryset方法會返回管理網站可以編輯的所有模型實例的QuerySet。 覆蓋此方法的一個用例是顯示由登錄用戶擁有的對象:

class MyModelAdmin(admin.ModelAdmin):
    def get_queryset(self, request):
        qs = super(MyModelAdmin, self).get_queryset(request)
        if request.user.is_superuser:
            return qs
        return qs.filter(author=request.user)
ModelAdmin. message_user(requestmessagelevel=messages.INFOextra_tags=''fail_silently=False)[source]

使用django.contrib.messages 向用戶發送消息。 參見custom ModelAdmin example

關鍵字參數運行你修改消息的級別、添加CSS 標簽,如果contrib.messages 框架沒有安裝則默默的失敗。 關鍵字參數與django.contrib.messages.add_message() 的參數相匹配,更多細節請參見這個函數的文檔。 有一個不同點是級別除了使用整數/常數傳遞之外還以使用字符串。

ModelAdmin. get_paginator(requestquerysetper_pageorphans=0allow_empty_first_page=True)[source]

返回要用於此視圖的分頁器的實例。 默認情況下,實例化paginator的實例。

ModelAdmin. response_add(requestobjpost_url_continue=None)[source]

add_view()階段確定HttpResponse

response_add在管理表單提交后,在對象和所有相關實例已創建並保存之后調用。 您可以覆蓋它以在對象創建后更改默認行為。

ModelAdmin. response_change(requestobj)[source]

確定change_view() 階段的HttpResponse

response_change 在Admin 表單提交並保存該對象和所有相關的實例之后調用。 您可以重寫它來更改對象修改之后的默認行為。

ModelAdmin. response_delete(requestobj_displayobj_id)[source]

delete_view()階段確定HttpResponse

在對象已刪除后調用response_delete。 您可以覆蓋它以在對象被刪除后更改默認行為。

obj_display是具有已刪除對象名稱的字符串。

obj_id是用於檢索要刪除的對象的序列化標識符。

ModelAdmin. get_changeform_initial_data(request)[source]

用於管理員更改表單上的初始數據的掛鈎。 默認情況下,字段從GET參數給出初始值。 例如,initial_value會將name字段的初始值設置為?name=initial_value

該方法應該返回表單中的字典 { '字段名': 'fieldval'}:

def get_changeform_initial_data(self, request):
    return {'name': 'custom_initial_value'}

其他方法

ModelAdmin. add_view(requestform_url=''extra_context=None)[source]

Django視圖為模型實例添加頁面。 見下面的注釋。

ModelAdmin. change_view(requestobject_idform_url=''extra_context=None)[source]

模型實例編輯頁面的Django視圖。 見下面的注釋。

ModelAdmin. changelist_view(requestextra_context=None)[source]

Django視圖為模型實例更改列表/操作頁面。 見下面的注釋。

ModelAdmin. delete_view(requestobject_idextra_context=None)[source]

模型實例刪除確認頁面的Django 視圖。 見下面的注釋。

ModelAdmin. history_view(requestobject_idextra_context=None)[source]

顯示給定模型實例的修改歷史的頁面的Django視圖。

與上一節中詳述的鈎型ModelAdmin方法不同,這五個方法實際上被設計為從管理應用程序URL調度處理程序調用為Django視圖,以呈現處理模型實例的頁面CRUD操作。 因此,完全覆蓋這些方法將顯着改變管理應用程序的行為。

覆蓋這些方法的一個常見原因是增加提供給呈現視圖的模板的上下文數據。 在以下示例中,覆蓋更改視圖,以便為渲染的模板提供一些額外的映射數據,否則這些數據將不可用:

class MyModelAdmin(admin.ModelAdmin):

    # A template for a very customized change view:
    change_form_template = 'admin/myapp/extras/openstreetmap_change_form.html'

    def get_osm_info(self):
        # ...
        pass

    def change_view(self, request, object_id, form_url='', extra_context=None):
        extra_context = extra_context or {}
        extra_context['osm_data'] = self.get_osm_info()
        return super(MyModelAdmin, self).change_view(
            request, object_id, form_url, extra_context=extra_context,
        )

這些視圖返回TemplateResponse實例,允許您在渲染之前輕松自定義響應數據。

ModelAdmin資產定義

有時候你想添加一些CSS和/或JavaScript到添加/更改視圖。 這可以通過在Media上使用ModelAdmin內部類來實現:

class ArticleAdmin(admin.ModelAdmin):
    class Media:
        css = {
            "all": ("my_styles.css",)
        }
        js = ("my_code.js",)

staticfiles appSTATIC_URL(或MEDIA_URL如果STATIC_URLNone資產路徑。 相同的規則適用於表單上的regular asset definitions on forms

jQuery

Django管理JavaScript使用jQuery庫。

為了避免與用戶提供的腳本或庫沖突,Django的jQuery(版本2.2.3)命名為django.jQuery。 如果您想在自己的管理JavaScript中使用jQuery而不包含第二個副本,則可以使用更改列表上的django.jQuery對象和添加/編輯視圖。

在Django更改1.10:

嵌入式jQuery從2.1.4升級到2.2.3。

默認情況下,ModelAdmin類需要jQuery,因此除非有特定需要,否則不需要向您的ModelAdmin的媒體資源列表添加jQuery。 例如,如果您需要將jQuery庫放在全局命名空間中(例如使用第三方jQuery插件時)或者如果您需要更新的jQuery版本,則必須包含自己的副本。

Django提供了jQuery的未壓縮和“縮小”版本,分別是jquery.jsjquery.min.js

ModelAdminInlineModelAdmin具有media屬性,可返回存儲到JavaScript文件的路徑的Media對象列表形式和/或格式。 如果DEBUGTrue,它將返回各種JavaScript文件的未壓縮版本,包括jquery.js;如果沒有,它將返回“最小化”版本。

向admin 添加自定義驗證

在管理員中添加數據的自定義驗證是很容易的。 自動管理界面重用django.forms,並且ModelAdmin類可以定義您自己的形式:

class ArticleAdmin(admin.ModelAdmin):
    form = MyArticleAdminForm

MyArticleAdminForm可以在任何位置定義,只要在需要的地方導入即可。 現在,您可以在表單中為任何字段添加自己的自定義驗證:

class MyArticleAdminForm(forms.ModelForm):
    def clean_name(self):
        # do something that validates your data
        return self.cleaned_data["name"]

重要的是你在這里使用ModelForm否則會破壞。

3.InlineModelAdmin對象

class  InlineModelAdmin
class  TabularInline[source]
class  StackedInline[source]

此管理界面能夠在一個界面編輯多個Model。 這些稱為內聯。 假設你有這兩個模型:

from django.db import models

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

class Book(models.Model):
   author = models.ForeignKey(Author, on_delete=models.CASCADE)
   title = models.CharField(max_length=100)

The first step in displaying this intermediate model in the admin is to define an inline class for the Membership model: 您可以通過在ModelAdmin.inlines中指定模型來為模型添加內聯:

from django.contrib import admin

class BookInline(admin.TabularInline):
    model = Book

class AuthorAdmin(admin.ModelAdmin):
    inlines = [
        BookInline,
    ]

Django提供了兩個InlineModelAdmin的子類如下:

這兩者之間僅僅是在用於呈現他們的模板上有區別。

InlineModelAdmin options 

BaseModelAdminModelAdmin具有許多相同的功能,並添加了一些自己的功能(共享功能實際上是在InlineModelAdmin超類中定義的)。 共享功能包括:

InlineModelAdmin類添加:

InlineModelAdmin. model

內聯正在使用的模型。 這是必需的。

InlineModelAdmin. fk_name

模型上的外鍵的名稱。 在大多數情況下,這將自動處理,但如果同一父模型有多個外鍵,則必須顯式指定fk_name

InlineModelAdmin. formset

默認為BaseInlineFormSet。 使用自己的表單可以給你很多自定義的可能性。 內聯圍繞model formsets構建。

InlineModelAdmin. form

form的值默認為ModelForm。 這是在為此內聯創建表單集時傳遞到inlineformset_factory()的內容。

InlineModelAdmin. classes
Django中的新功能1.10。

包含額外CSS類的列表或元組,以應用於為內聯呈現的字段集。 默認為None。 fieldsets中配置的類一樣,帶有collapse類的內聯將最初折疊,並且它們的標題將具有一個小的“show”鏈接。

InlineModelAdmin. extra

這控制除初始形式外,表單集將顯示的額外表單的數量。 有關詳細信息,請參閱formsets documentation

對於具有啟用JavaScript的瀏覽器的用戶,提供了“添加另一個”鏈接,以允許除了由於extra參數提供的內容之外添加任意數量的其他內聯。

如果當前顯示的表單數量超過max_num,或者用戶未啟用JavaScript,則不會顯示動態鏈接。

InlineModelAdmin.get_extra()還允許您自定義額外表單的數量。

InlineModelAdmin. max_num

這控制在內聯中顯示的表單的最大數量。 這不直接與對象的數量相關,但如果值足夠小,可以。 有關詳細信息,請參閱Limiting the number of editable objects

InlineModelAdmin.get_max_num()還允許您自定義最大數量的額外表單。

InlineModelAdmin。  MIN_NUM T0> 

這控制在內聯中顯示的表單的最小數量。 有關詳細信息,請參閱modelformset_factory()

InlineModelAdmin.get_min_num()還允許您自定義顯示的表單的最小數量。

InlineModelAdmin。  raw_id_fields T0> 

By default, Django’s admin uses a select-box interface (<select>) for fields that are ForeignKey有時候你不想在下拉菜單中顯示所有相關實例產生的開銷。

ForeignKey 是一個字段列表,你希望將Input 或raw_id_fields 轉換成ManyToManyField Widget:

class BookInline(admin.TabularInline):
    model = Book
    raw_id_fields = ("pages",)
InlineModelAdmin。 模板 T0> 

用於在頁面上呈現內聯的模板。

InlineModelAdmin。  verbose_name T0> 

覆蓋模型的內部verbose_name類中找到的Meta

InlineModelAdmin。  verbose_name_plural T0> 

覆蓋模型的內部verbose_name_plural類中的Meta

InlineModelAdmin。  can_delete T0> 

指定是否可以在內聯中刪除內聯對象。 默認為True

指定是否可以在admin中更改的內聯對象具有指向更改表單的鏈接。 默認為False

InlineModelAdmin。 get_formset請求obj =無** kwargs

返回BaseInlineFormSet類,以在管理員添加/更改視圖中使用。 請參閱ModelAdmin.get_formsets_with_inlines的示例。

InlineModelAdmin。 get_extra請求obj =無** kwargs

返回要使用的其他內聯表單的數量。 默認情況下,返回InlineModelAdmin.extra屬性。

覆蓋此方法以編程方式確定額外的內聯表單的數量。 例如,這可以基於模型實例(作為關鍵字參數obj傳遞):

class BinaryTreeAdmin(admin.TabularInline):
    model = BinaryTree

    def get_extra(self, request, obj=None, **kwargs):
        extra = 2
        if obj:
            return extra - obj.binarytree_set.count()
        return extra

InlineModelAdmin。get_max_num請求obj =無** kwargs

返回要使用的額外內聯表單的最大數量。 默認情況下,返回InlineModelAdmin.max_num屬性。

覆蓋此方法以編程方式確定內聯表單的最大數量。 例如,這可以基於模型實例(作為關鍵字參數obj傳遞):

class BinaryTreeAdmin(admin.TabularInline):
    model = BinaryTree

    def get_max_num(self, request, obj=None, **kwargs):
        max_num = 10
        if obj and obj.parent:
            return max_num - 5
        return max_num
InlineModelAdmin。 get_min_num請求obj =無** kwargs

返回要使用的內聯表單的最小數量。 默認情況下,返回InlineModelAdmin.min_num屬性。

覆蓋此方法以編程方式確定最小內聯表單數。 例如,這可以基於模型實例(作為關鍵字參數obj傳遞)。

使用具有兩個或多個外鍵的模型與同一個父模型

有時可能有多個外鍵到同一個模型。 以這個模型為例:

from django.db import models

class Friendship(models.Model):
    to_person = models.ForeignKey(Person, on_delete=models.CASCADE, related_name="friends")
    from_person = models.ForeignKey(Person, on_delete=models.CASCADE, related_name="from_friends")

如果您想在Person管理員添加/更改頁面上顯示內聯,則需要明確定義外鍵,因為它無法自動執行:

from django.contrib import admin
from myapp.models import Friendship

class FriendshipInline(admin.TabularInline):
    model = Friendship
    fk_name = "to_person"

class PersonAdmin(admin.ModelAdmin):
    inlines = [
        FriendshipInline,
    ]

使用多對多模型

默認情況下,多對多關系的管理窗口小部件將顯示在包含ManyToManyField的實際引用的任何模型上。 根據您的ModelAdmin定義,模型中的每個多對多字段將由標准HTML &lt; select multiple> t4>,水平或垂直過濾器或raw_id_admin小部件。 但是,也可以用內聯替換這些小部件。

假設我們有以下模型:

from django.db import models

class Person(models.Model):
    name = models.CharField(max_length=128)

class Group(models.Model):
    name = models.CharField(max_length=128)
    members = models.ManyToManyField(Person, related_name='groups')

如果要使用內聯顯示多對多關系,可以通過為關系定義InlineModelAdmin對象來實現:

from django.contrib import admin

class MembershipInline(admin.TabularInline):
    model = Group.members.through

class PersonAdmin(admin.ModelAdmin):
    inlines = [
        MembershipInline,
    ]

class GroupAdmin(admin.ModelAdmin):
    inlines = [
        MembershipInline,
    ]
    exclude = ('members',)

在這個例子中有兩個值得注意的特征。

首先 - MembershipInline類引用Group.members.through。 through屬性是對管理多對多關系的模型的引用。 在定義多對多字段時,此模型由Django自動創建。

其次,GroupAdmin必須手動排除members字段。 Django在定義關系(在這種情況下,Group)的模型上顯示多對多字段的管理窗口小部件。 如果要使用內聯模型來表示多對多關系,則必須告知Django的管理員而不是顯示此窗口小部件 - 否則您最終會在管理頁面上看到兩個窗口小部件,用於管理關系。

請注意,使用此技術時,不會觸發m2m_changed信號。 這是因為,就管理而言,through只是一個具有兩個外鍵字段而不是多對多關系的模型。

在所有其他方面,InlineModelAdmin與任何其他方面完全相同。 您可以使用任何正常的ModelAdmin屬性自定義外觀。

使用多對多中介模型

當您使用ManyToManyFieldthrough參數指定中介模型時,admin將不會默認顯示窗口小部件。 這是因為該中間模型的每個實例需要比可以在單個小部件中顯示的更多的信息,並且多個小部件所需的布局將根據中間模型而變化。

但是,我們仍然希望能夠在內聯里編輯該信息。 幸運的是,這用內聯管理模型很容易做到 假設我們有以下模型:

from django.db import models

class Person(models.Model):
    name = models.CharField(max_length=128)

class Group(models.Model):
    name = models.CharField(max_length=128)
    members = models.ManyToManyField(Person, through='Membership')

class Membership(models.Model):
    person = models.ForeignKey(Person, on_delete=models.CASCADE)
    group = models.ForeignKey(Group, on_delete=models.CASCADE)
    date_joined = models.DateField()
    invite_reason = models.CharField(max_length=64)

在admin中顯示此中間模型的第一步是為Membership模型定義一個內聯類:

class MembershipInline(admin.TabularInline):
    model = Membership
    extra = 1

此簡單示例使用InlineModelAdmin模型的默認Membership值,並將額外添加表單限制為一個。 這可以使用InlineModelAdmin類可用的任何選項進行自定義。

現在為PersonGroup模型創建管理視圖:

class PersonAdmin(admin.ModelAdmin):
    inlines = (MembershipInline,)

class GroupAdmin(admin.ModelAdmin):
    inlines = (MembershipInline,)

最后,向管理網站注冊您的PersonGroup模型:

admin.site.register(Person, PersonAdmin)
admin.site.register(Group, GroupAdmin)

現在,您的管理網站已設置為從GroupPerson詳細信息頁面內聯編輯Membership對象。

使用通用關系作為內聯

可以使用內聯與一般相關的對象。 假設您有以下模型:

from django.db import models
from django.contrib.contenttypes.fields import GenericForeignKey

class Image(models.Model):
    image = models.ImageField(upload_to="images")
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey("content_type", "object_id")

class Product(models.Model):
    name = models.CharField(max_length=100)

If you want to allow editing and creating an Image instance on the Product, add/change views you can useGenericTabularInline or GenericStackedInline (both subclasses of GenericInlineModelAdmin) provided by admin它們分別為表示內聯對象的表單分別執行表格和堆疊的視覺布局,就像它們的非通用對象一樣。 他們的行為就像任何其他內聯一樣。 在此示例應用的admin.py中:

from django.contrib import admin
from django.contrib.contenttypes.admin import GenericTabularInline

from myproject.myapp.models import Image, Product

class ImageInline(GenericTabularInline):
    model = Image

class ProductAdmin(admin.ModelAdmin):
    inlines = [
        ImageInline,
    ]

admin.site.register(Product, ProductAdmin)

3.重寫Admin模板

覆蓋管理模板

相對重寫一個admin站點的各類頁面,直接在admin站點默認templates上直接進行修改是件相對簡單的事。 你甚至可以為特定的應用或一個特定的模型覆蓋少量的這些模板。

設置你的項目管理模板目錄

Admin模板文件位於contrib/admin/templates/admin 目錄中。

如要覆蓋一個或多個模板,首先在你的項目的admin 目錄中創建一個templates 目錄。 它可以是你在TEMPLATES 設置的DjangoTemplates 后端的DIRS 選項中指定的任何目錄。 If you have customized the 'loaders' option, be sure'django.template.loaders.filesystem.Loader' appears before 'django.template.loaders.app_directories.Loader' so that your custom templates will be found by the template loading system before those that are included withdjango.contrib.admin.

在 admin 目錄下, 以你的應用名創建子目錄. 在應用名的目錄下,以你模型層的名字創建子目錄. 注意:admin應用會以小寫名的形式在目錄下查找模型, 如果你想在大小寫敏感的文件系統上運行app,請確保以小寫形式命名目錄.

為一個特定的app重寫admin模板, 需要拷貝django/contrib/admin/templates/admin 目錄到你剛才創建的目錄下, 並且修改它們.

For example, if we wanted to add a tool to the change list view for all the models in an app named templates/admin/my_app/, we would copy contrib/admin/templates/admin/change_list.html to the my_app directory of our project, and make any necessary changes.

如果我們只想為名為“Page”的特定模型添加一個工具到更改列表視圖,我們將把同一個文件復制到我們項目的templates/admin/my_app/page目錄。

覆蓋與更換管理模板

由於管理模板的模塊化設計,通常既不必要也不建議替換整個模板。 最好只覆蓋模板中需要更改的部分。

要繼續上述示例,我們要為History模型的Page工具旁邊添加一個新鏈接。 查看change_form.html后,我們確定我們只需要覆蓋object-tools-items塊。 因此,這里是我們的新change_form.html

{% extends "admin/change_form.html" %}
{% load i18n admin_urls %}
{% block object-tools-items %}
    <li>
        <a href="{% url opts|admin_urlname:'history' original.pk|admin_urlquote %}" class="historylink">{% trans "History" %}</a>
    </li>
    <li>
        <a href="mylink/" class="historylink">My Link</a>
    </li>
    {% if has_absolute_url %}
        <li>
            <a href="{% url 'admin:view_on_site' content_type_id original.pk %}" class="viewsitelink">{% trans "View on site" %}</a>
        </li>
    {% endif %}
{% endblock %}

就是這樣! 如果我們將此文件放在templates/admin/my_app目錄中,我們的鏈接將出現在my_app中所有模型的更改表單上。

Templates which may be overridden per app or model

不是contrib/admin/templates/admin 中的每個模板都可以在每個應用或每個模型中覆蓋。 以下可以 ︰

  • app_index.html
  • change_form.html
  • change_list.html
  • delete_confirmation.html
  • object_history.html
  • popup_response.html
在Django更改1.11:

覆蓋popup_response.html模板的功能已添加。

對於那些不能以這種方式重寫的模板,你可能仍然為您的整個項目重寫它們。 只需要將新版本放在你的templates/admin 目錄下。這對於要創建自定義的404 和500 頁面特別有用。

根和登錄模板

如果你想要更改主頁、 登錄或登出頁面的模板,你最后創建你自己的AdminSite 實例(見下文),並更改AdminSite.index_templateAdminSite.login_template 和AdminSite.logout_template 屬性。

4.AdminSite對象

class  AdminSite(name='admin')[source]

Django管理站點由django.contrib.admin.sites.AdminSite的實例表示;默認情況下,此類的實例將創建為django.contrib.admin.site,您可以使用它注冊模型和ModelAdmin實例。

當構造AdminSite 的實例時,你可以使用name 參數給構造函數提供一個唯一的實例名稱。 這個實例名稱用於標識實例,尤其是reversing admin URLs 的時候。 如果沒有提供實例的名稱,將使用默認的實例名稱admin。 有關自定義AdminSite 類的示例,請參見Customizing the AdminSite class

AdminSite屬性

Overriding admin templates中所述,模板可以覆蓋或擴展基礎的Admin 模板。

AdminSite. site_header

每個Admin 頁面頂部的文本,形式為<h1>(字符串)。 默認為 “Django administration”。

AdminSite. site_title

每個Admin 頁面底部的文本,形式為<title>(字符串)。 默認為“Django site admin”。

AdminSite. site_url

每個Admin 頁面頂部"View site" 鏈接的URL。 默認情況下,site_url 為/。 設置為None 可以刪除這個鏈接。

對於在子路徑上運行的站點,each_context()方法會檢查當前請求是否具有request.META['SCRIPT_NAME']設置並使用該值,如果site_url未設置為/以外的其他內容。

在Django更改1.10:

上一段描述的SCRIPT_NAME支持已添加。

AdminSite. index_title

Admin 主頁頂部的文本(一個字符串)。 默認為 “Site administration”。

AdminSite. index_template

Admin 站點主頁的視圖使用的自定義模板的路徑。

AdminSite. app_index_template

Admin 站點app index 的視圖使用的自定義模板的路徑。

AdminSite. empty_value_display

用於在管理站點更改列表中顯示空值的字符串。 默認為破折號。 通過在字段上設置empty_value_display屬性,也可以在每個ModelAdmin以及ModelAdmin中的自定義字段上覆蓋該值。 有關示例,請參見ModelAdmin.empty_value_display

AdminSite. login_template

Admin 站點登錄視圖使用的自定義模板的路徑。

AdminSite. login_form

Admin 站點登錄視圖使用的AuthenticationForm 的子類。

AdminSite. logout_template

Admin 站點登出視圖使用的自定義模板的路徑。

AdminSite. password_change_template

Admin 站點密碼修改視圖使用的自定義模板的路徑。

AdminSite. password_change_done_template

Admin 站點密碼修改完成視圖使用的自定義模板的路徑。

AdminSite方法

AdminSite. each_context(request)[source]

返回一個字典,包含將放置在Admin 站點每個頁面的模板上下文中的變量。

包含以下變量和默認值:

  • site_headerAdminSite.site_header

  • site_titleAdminSite.site_title

  • site_urlAdminSite.site_url

  • has_permissionAdminSite.has_permission()

  • available_apps:從當前用戶可用的application registry中的應用程序列表。 列表中的每個條目都是表示具有以下密鑰的應用程序的dict:

    • app_label:應用程序標簽
    • app_url:管理員中的應用程序索引的URL
    • has_module_perms:一個布爾值,表示當前用戶是否允許顯示和訪問模塊的索引頁面
    • models:應用程序中可用的模型列表

    每個模型都是具有以下鍵的dict:

    • object_name:模型的類名
    • name:復數名稱的模型
    • perms:a dict tracking addchangedelete permissions
    • admin_url:admin changelist模型的URL
    • add_url:添加新模型實例的admin URL
AdminSite. has_permission(request)[source]

對於給定的True,如果用戶有權查看Admin 網站中的至少一個頁面,則返回 HttpRequest。 默認要求User.is_activeUser.is_staff 都為True

AdminSite. register(model_or_iterableadmin_class=None**options)[source]

使用給定的admin_class注冊給定的模型類(或模型類組成的可迭代對象)。 admin_class默認為ModelAdmin(默認的管理后台選項)。 如果給出了關鍵字參數 — 例如list_display — 它們將作為選項應用於admin_class。

如果模型是抽象的,則引發ImproperlyConfigured。 如果模型已經注冊則引發django.contrib.admin.sites.AlreadyRegistered

AdminSite的實例掛接到URLconf中

設置Django管理后台的最后一步是放置你的AdminSite到你的URLconf中。 將一個給定的URL指向AdminSite.urls方法就可以做到。 沒有必要使用include()

在下面的示例中,我們注冊默認的AdminSite實例django.contrib.admin.site到URL /admin/

# urls.py
from django.conf.urls import url
from django.contrib import admin

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

定制AdminSite

如果你想要建立你自己的具有自定義行為Admin 站點,你可以自由地子類化AdminSite 並重寫或添加任何你喜歡的東西。 你只需創建AdminSite 子類的實例(方式與你會實例化任何其它Python 類相同) 並注冊你的模型和ModelAdmin 子類與它而不是默認的站點。 最后,更新myproject/urls.py 來引用你的AdminSite 子類。

Admin.py

from django.contrib.admin import AdminSite

from .models import MyModel

class MyAdminSite(AdminSite):
    site_header = 'Monty Python administration'

admin_site = MyAdminSite(name='myadmin')
admin_site.register(MyModel)

url.py

from django.conf.urls import url

from myapp.admin import admin_site

urlpatterns = [
    url(r'^myadmin/', admin_site.urls),
]

注意,當使用你自己的admin 實例時,你可能不希望自動發現AdminSite 模塊,因為這將導入admin 模塊到你的每個myproject.admin模塊中 。 這時,你需要將'django.contrib.admin' 而不是'django.contrib.admin.apps.SimpleAdminConfig' 放置在你的INSTALLED_APPS 設置中。

相同的URLconf 中的多個管理站點

在同一個Django供電的網站上創建管理站點的多個實例很容易。 只需要創建AdminSite 的多個實例並將每個實例放置在不同的URL 下。

在下面的示例中,AdminSite 和/advanced-admin/ 分別使用/basic-admin/ 的myproject.admin.basic_site 實例和myproject.admin.advanced_site 實例表示不同版本的Admin 站點:

# urls.py
from django.conf.urls import url
from myproject.admin import basic_site, advanced_site

urlpatterns = [
    url(r'^basic-admin/', basic_site.urls),
    url(r'^advanced-admin/', advanced_site.urls),
]

AdminSite 實例的構造函數中接受一個單一參數用做它們的名字,可以是任何你喜歡的東西。 此參數將成為reversing them 時URL 名稱的前綴。 只有在你使用多個AdminSite 時它才是必要的。

將視圖添加到管理站點

ModelAdmin一樣,AdminSite提供了一個get_urls()方法,可以重寫該方法以定義網站的其他視圖。 要向您的管理網站添加新視圖,請擴展基本get_urls()方法,為新視圖添加模式。

添加密碼重置功能

您可以通過在URLconf中添加幾行來將密碼重置功能添加到管理站點。 具體操作就是加入下面四個正則規則。

from django.contrib.auth import views as auth_views

url(
    r'^admin/password_reset/$',
    auth_views.PasswordResetView.as_view(),
    name='admin_password_reset',
),
url(
    r'^admin/password_reset/done/$',
    auth_views.PasswordResetDoneView.as_view(),
    name='password_reset_done',
),
url(
    r'^reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>.+)/$',
    auth_views.PasswordResetConfirmView.as_view(),
    name='password_reset_confirm',
),
url(
    r'^reset/done/$',
    auth_views.PasswordResetCompleteView.as_view(),
    name='password_reset_complete',
),

(假設您已在admin/添加了管理員,並要求您在包含管理應用程序的行之前將^admin/開頭的網址)。

如果存在admin_password_reset命名的URL,則會在密碼框下的默認管理登錄頁面上顯示“忘記了您的密碼?”鏈接。

LogEntry對象

類  楷模。  LogEntry T0> 

LogEntry類跟蹤通過管理界面完成的對象的添加,更改和刪除。

LogEntry屬性

LogEntry. action_time

行動的日期和時間。

LogEntry. user

執行該操作的用戶(一個AUTH_USER_MODEL實例)。

LogEntry. content_type

修改對象的ContentType

LogEntry. object_id

修改對象的主鍵的文本表示。

LogEntry. object_repr

修改后的對象'repr()

LogEntry. action_flag

記錄的動作類型:ADDITIONCHANGEDELETION

例如,要獲取通過管理員完成的所有添加的列表:

from django.contrib.admin.models import LogEntry, ADDITION

LogEntry.objects.filter(action_flag=ADDITION)
LogEntry. change_message

修改的詳細說明。 例如,在編輯的情況下,消息包含編輯字段的列表。 Django管理網站將此內容格式化為JSON結構,因此get_change_message()可以重構以當前用戶語言翻譯的消息。 自定義代碼可能將此設置為純字符串。 建議您使用get_change_message()方法檢索該值,而不是直接訪問該值。

在Django更改1.10:

以前,此屬性始終是一個簡單的字符串。 它現在是JSON結構,以便可以使用當前用戶語言翻譯該消息。 老消息不變。

LogEntry方法

LogEntry. get_edited_object()

返回引用對象的快捷方式。

LogEntry. get_change_message()
Django中的新功能1.10。

change_message格式化並轉換為當前用戶語言。 在Django 1.10之前創建的消息將始終以其記錄的語言顯示。

5.反向解析管理后台的URL

 

AdminSite部署后,該站點所提供的視圖都可以使用Django的URL反向解析系統訪問。

 

AdminSite提供以下命名URL:

 

頁面 網址名稱 參數
指數 index  
登錄 login  
登出 logout  
密碼更改 password_change  
完成密碼更改 password_change_done  
i18n JavaScript jsi18n  
應用的主頁 app_list app_label
重定向到對象的頁面 view_on_site content_type_idobject_id

 

每個ModelAdmin 實例還將提供額外的命名URL:

 

頁面 網址名稱 參數
更改列表 {{ app_label }}_{{ model_name }}_changelist  
添加 {{ app_label }}_{{ model_name }}_add  
歷史 {{ app_label }}_{{ model_name }}_history OBJECT_ID
刪除 {{ app_label }}_{{ model_name }}_delete OBJECT_ID
更改 {{ app_label }}_{{ model_name }}_change OBJECT_ID

 

UserAdmin提供了一個命名的URL:

 

頁面 網址名稱 參數
密碼更改 auth_user_password_change 用戶名

 

這些命名URL 注冊的應用命名空間為admin,實例命名空間為對應的AdminSite 實例的名稱。

 

所以,如果你想要獲取默認Admin 中,(polls 應用的) 一個特定的Choice 對象的更改視圖的引用,你可以調用︰

 

>>> from django.urls import reverse
>>> c = Choice.objects.get(...)
>>> change_url = reverse('admin:polls_choice_change', args=(c.id,))

 

這將查找Admin 應用中第一個注冊的實例(無論實例名稱是什么),並解析到poll.Choice 實例的更改視圖。

如果你想要查找一個特定的Admin 實例中URL,請提供實例的名稱作為current_app 給反向解析的調用 。 例如,如果你希望得到名為custom 的Admin 實例中的視圖,你將需要調用

>>> change_url = reverse('admin:polls_choice_change', args=(c.id,), current_app='custom')

為了讓模板中反向解析Admin URL 更加容易,Django 提供一個admin_urlname 過濾器,它以Action 作為參數︰

 

{% load admin_urls %}
<a href="{% url opts|admin_urlname:'add' %}">Add user</a>
<a href="{% url opts|admin_urlname:'delete' user.pk %}">Delete this user</a>

 

在上面的例子中Action 將匹配上文所述的ModelAdmin 實例的URL 名稱的最后部分。 model_name 變量可以是任何具有app_labelopts 屬性的對象,通常由Admin 視圖為當前的模型提供。

 

staff_member_required裝飾器

 

staff_member_required(redirect_field_name='next'login_url='admin:login')[source]

該裝飾器用於需要授權的管理員視圖。 使用此功能裝飾的視圖將具有以下行為:

  • 如果用戶登錄,是工作人員(User.is_staff=True),並且處於活動狀態(User.is_active=True),請正常執行該視圖。
  • 否則,該請求將被重定向到由login_url參數指定的URL,由redirect_field_name指定的查詢字符串變量中的原始請求路徑。 例如:/admin/login/?next=/admin/polls/question/3/

使用示例

from django.co

 

from django.contrib.admin.views.decorators import staff_member_required

@staff_member_required
def my_view(request):
    ...

 6.管理員動作

簡單來說,Django管理員的基本工作流程是“選擇一個對象,然后進行更改”。這對大多數用例都很有效。 然而當你一次性要對多個對象做相同的改變,這個流程是非常的單調乏味的。

在這些情況下,Django Admin 可以讓你編寫並注冊“Action” —— 僅僅只是一個以更改列表頁面上選中對象的列表為參數的回調函數。

如果您查看管理員中的任何更改列表,您將看到此功能在操作中; Django附帶所有型號的“刪除所選對象”操作。 例如,下面是從Django 內建的django.contrib.auth 應用創建的用戶模型:

寫入動作

通過示例來解釋Action 最為簡單,讓我們開始吧。

Action 的一個最為普遍的用例是模型的整體更新。 考慮帶有Article模型的簡單新聞應用:

from django.db import models

STATUS_CHOICES = (
    ('d', 'Draft'),
    ('p', 'Published'),
    ('w', 'Withdrawn'),
)

class Article(models.Model):
    title = models.CharField(max_length=100)
    body = models.TextField()
    status = models.CharField(max_length=1, choices=STATUS_CHOICES)

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

我們可能在模型上執行的一個普遍任務是,將文章狀態從“草稿”更新為“已發布”。 在Admin 界面上一次處理一篇文章非常輕松,但是如果我們想要批量發布一些文章,則會非常單調乏味。 所以讓我們編寫一個Action,可以讓我們將一篇文章的狀態修改為“已發布”。

寫入動作函數

首先,我們需要定義一個函數,當在Admin 界面上觸發該Action 的時候調用。 Action 函數,跟普通的函數一樣,需要接收三個參數:

我們用於發布這些文章的函數並不需要ModelAdmin 和請求對象,但是我們會用到查詢集:

def make_published(modeladmin, request, queryset):
    queryset.update(status='p')

編寫Action 的全部內容實際上就這么多了。 但是,我們要進行一個可選但是有用的步驟,在Admin 中給Action 起一個“友好”的標題。 默認情況下,Action 以“Make published” 的形式出現在Action 列表中 —— 將函數名稱中的所有下划線用空格替換。 這樣就很好了,但是我們可以提供一個更好、更人性化的名稱,通過向make_published函數添加short_description 屬性:

def make_published(modeladmin, request, queryset):
    queryset.update(status='p')
make_published.short_description = "Mark selected stories as published"

ModelAdmin 添加動作

接下來,我們需要把Action 告訴ModelAdmin。 它和其他配置項的工作方式相同。 所以,帶有Action 及其注冊的完整的admin.py 看起來像這樣:

from django.contrib import admin
from myapp.models import Article

def make_published(modeladmin, request, queryset):
    queryset.update(status='p')
make_published.short_description = "Mark selected stories as published"

class ArticleAdmin(admin.ModelAdmin):
    list_display = ['title', 'status']
    ordering = ['title']
    actions = [make_published]

admin.site.register(Article, ArticleAdmin)

這段代碼向我們提供的Admin 更改列表看起來像這樣:

這就是全部內容了。 如果你想編寫自己的Action ,你現在應該知道怎么開始了。 這篇文檔的剩余部分會介紹更多高級技巧。

處理動作中的錯誤

如果你的Action 運行時發生可預見的些錯誤,你應該以優雅的方式向用戶通知這些錯誤。 這意味着處理異常並使用django.contrib.admin.ModelAdmin.message_user()在響應中向用戶展示友好的問題描述。

高級動作技術

對於進一步的選擇,你可以使用一些額外的選項。

作為ModelAdmin方法的操作

上面的例子展示了定義為一個簡單函數的make_published操作。 這真是極好的,但是以視圖的代碼設計角度來看,它並不完美:由於操作與Article緊密耦合,不如將操作直接綁定到ArticleAdmin對象上更有意義。

這樣做十分簡單:

class ArticleAdmin(admin.ModelAdmin):
    ...

    actions = ['make_published']

    def make_published(self, request, queryset):
        queryset.update(status='p')
    make_published.short_description = "Mark selected stories as published"

首先注意,我們將self放到一個方法中,並重命名 modeladmin 為make_published,其次,我們現在將'make_published'字符串放進了actions,而不是一個直接的函數引用。 這樣會讓 ModelAdmin將這個操作視為方法。

將Action 定義為方法,可以使操作以更加直接、符合語言習慣的方式來訪問ModelAdmin,並可以調用Admin 提供的任何方法。

例如,我們可以使用self 向用戶發送消息來告訴她Action 成功了:

class ArticleAdmin(admin.ModelAdmin):
    ...

    def make_published(self, request, queryset):
        rows_updated = queryset.update(status='p')
        if rows_updated == 1:
            message_bit = "1 story was"
        else:
            message_bit = "%s stories were" % rows_updated
        self.message_user(request, "%s successfully marked as published." % message_bit)

這會使動作與后台在成功執行動作后做的事情相匹配:

 

提供中間頁面的操作

默認情況下,在執行Actions 之后,用戶會簡單地通過重定向返回到之前的更改列表頁面中。 然而,一些Action,尤其是更加復雜的操作,需要返回一個中間頁面。 例如,內建的刪除操作,在刪除選中對象之前需要向用戶詢問來確認。

要提供中間頁面,只要從你的操作返回HttpResponse(或其子類)就可以了。 例如,你可以編寫一個簡單的導出函數,它使用Django 的serialization functions來將一些選中的對象轉換為JSON:

from django.http import HttpResponse
from django.core import serializers

def export_as_json(modeladmin, request, queryset):
    response = HttpResponse(content_type="application/json")
    serializers.serialize("json", queryset, stream=response)
    return response

一般情況下,上面的代碼的實現方式並不是很好。 大多數情況下,最佳實踐是返回 HttpResponseRedirect,並且使用戶重定向到你編寫的視圖中,向GET查詢字符串傳遞選中對象的列表。 這需要你在中間界面上提供復雜的交互邏輯。 例如,如果你打算提供一個更加復雜的導出函數,你會希望讓用戶選擇一種格式,以及可能在導出中包含一個含有字段的列表。 最佳方式是編寫一個小型的操作,簡單重定向到你的自定義導出視圖中:

from django.contrib import admin
from django.contrib.contenttypes.models import ContentType
from django.http import HttpResponseRedirect

def export_selected_objects(modeladmin, request, queryset):
    selected = request.POST.getlist(admin.ACTION_CHECKBOX_NAME)
    ct = ContentType.objects.get_for_model(queryset.model)
    return HttpResponseRedirect("/export/?ct=%s&ids=%s" % (ct.pk, ",".join(selected)))

正如你所看到的,行動是簡單的部分;所有復雜的邏輯將屬於您的導出視圖。 這需要處理任何類型的對象,所以需要處理ContentType

這個視圖的編寫作為一個練習留給讀者。

可用於整個admin站點的actions

AdminSite。 add_actionactionname = None[source] 

如果一些操作對管理站點的任何對象都可用的話,是非常不錯的 -- 上面所定義的導出操作是個不錯的備選方案。 你可以使用AdminSite.add_action()讓一個操作在全局都可以使用。 像這樣:

from django.contrib import admin

admin.site.add_action(export_selected_objects)

這樣,export_selected_objects 操作可以在全局使用,名稱為“export_selected_objects”。 你可以顯式指定Action的名稱 —— 如果你想以編程的方式remove the action 是非常有用的 —— 通過向AdminSite.add_action()傳遞第二個參數:

admin.site.add_action(export_selected_objects, 'export_selected')

禁用動作

有時你需要禁用特定的操作 -- 尤其是registered site-wide -- 對於特定的對象。 你可以使用一些方法來禁用操作:

禁用站點范圍的動作

AdminSite。 disable_action(name)[source]

如果你需要禁用site-wide action ,你可以調用 AdminSite.disable_action()

例如,你可以使用這個方法來移除內建的“刪除選中的對象”操作:

admin.site.disable_action('delete_selected')

一旦你執行了上面的代碼,這個操作不再對整個站點中可用。

然而,如果你需要為特定的模型重新啟動在全局禁用的對象,把它顯式放在ModelAdmin.actions 列表中就可以了:

# 全站禁用刪除功能
admin.site.disable_action('delete_selected')

# 這個ModelAdmin不會有刪除功能
class SomeModelAdmin(admin.ModelAdmin):
    actions = ['some_other_action']
    ...

# 這個聲明還要用
class AnotherModelAdmin(admin.ModelAdmin):
    actions = ['delete_selected', 'a_third_action']
    ...

禁用特定ModelAdmin 的所有操作

如果你想批量移除所提供 ModelAdmin上的所有操作,可以把ModelAdmin.actions設置為None

class MyModelAdmin(admin.ModelAdmin):
    actions = 沒有

這樣會告訴ModelAdmin,不要展示或者允許任何操作,包括site-wide actions

有條件地啟用或禁用動作

的ModelAdmin。 get_actions(request)[source]

最后,你可以通過覆寫ModelAdmin.get_actions(),對每個請求(每個用戶)按需開啟或禁用操作。

這個函數返回包含允許操作的字典。 字典的鍵是操作的名稱,值是 (function, name, short_description)元組。

多數情況下,你會按需使用這一方法,來從超類中的列表移除操作。 例如,如果我只希望名稱以'J'開頭的用戶可以批量刪除對象,我可以執行下面的代碼:

class MyModelAdmin(admin.ModelAdmin):
    ...

    def get_actions(self, request):
        actions = super(MyModelAdmin, self).get_actions(request)
        if request.user.username[0].upper() != 'J':
            if 'delete_selected' in actions:
                del actions['delete_selected']
        return actions

日志

logging的組成

Python的logging配置由四個部分組成:

Loggers

Logger為日志系統的入口。 每個logger是一個帶有名稱的bucket,你可以向這個bucket寫入需要處理的消息。

每個logger都有一個日志級別。 日志級別表示該logger將要處理的消息的嚴重性。 Python定義以下幾種日志級別:

  • DEBUG:用於調試目的的底層系統信息
  • INFO:普通的系統信息
  • WARNING:表示出現一個較小的問題。
  • ERROR:表示出現一個較大的問題。
  • CRITICAL:表示出現一個致命的問題。

寫入logger的每條消息都是一個日志記錄。 每個日志記錄也具有一個日志級別,它表示對應的消息的嚴重性。 每個日志記錄還可以包含描述正在打印的事件的有用元信息。 這些元信息可以包含很多細節,例如回溯棧或錯誤碼。

當一條消息傳遞給logger時,消息的日志級別將與logger的日志級別進行比較。 如果消息的日志級別大於等於logger 的日志級別,該消息將會往下繼續處理。 如果小於,該消息將被忽略。

Logger一旦決定消息需要處理,它將傳遞該消息給一個Handler

Handlers

Handler決定如何處理logger中的每條消息。 它描述一個特定的日志行為,例如將消息寫到屏幕上、寫到文件中或者寫到網絡socket。

與logger一樣,handler也有一個日志級別。 如果消息的日志級別小於handler的級別,handler將忽略該消息。

Logger 可以有多個handler,而每個handler 可以有不同的日志級別。 利用這種方式,可以根據消息的重要性提供不同形式的處理。 例如,你可以用一個handler將CRITICAL和 ERROR消息發送給一個頁面服務,而用另外一個hander將所有的消息(包括ERRORCRITICAL消息)記錄到一個文件中用於以后進行分析。

Filters

Filter用於對從logger傳遞給handler的日志記錄進行額外的控制。

默認情況下,滿足日志級別的任何消息都將被處理。 通過安裝一個filter,你可以對日志處理添加額外的條件。 例如,你可以安裝一個filter,只允許處理來自特定源的ERROR 消息。

Filters 還可以用於修改將要處理的日志記錄的優先級。 例如,如果日志記錄滿足特定的條件,你可以編寫一個filter 將日志記錄從ERROR 降為WARNING

Filters可以安裝在loggers或handlers上;可以將多個filters鏈接起來執行多個過濾操作。

Formatters

最后,日志記錄需要轉換成文本。 Formatter描述文本的准確格式。 Formatter通常由包含日志記錄的屬性的Python格式化字符串組成;但是,你也可以編寫自定義formatters來實現特定的格式化行為。

使用logging

配置好logger、handler、filter 和formatter 之后,你需要在代碼中放入logging 調用。 使用logging 框架非常簡單。 下面是個例子:

# 導入logging庫
import logging

# 獲取logger的一個實例
logger = logging.getLogger(__name__)

def my_view(request, arg1, arg):
    ...
    if bad_mojo:
        # 記錄一條錯誤信息
        logger.error('Something went wrong!')

就是這樣! 每次滿足bad_mojo 條件,將寫入一條錯誤日志記錄。

命名loggers

logging.getLogger() 調用獲取(如有必要則創建)一個logger 的實例。 Logger實例通過一個名稱標識。 Logger使用名稱標識的目的是用於配置。

Logger的名稱習慣上通常使用__name__,即包含該logger的Python模塊的名字。 這允許你基於模塊filter和handle日志調用。 如果你想使用其它方式組織日志消息,可以提供點號分隔的名稱來標識你的logger:

# 獲取一個特定名稱的logger
logger = logging.getLogger('project.interesting.stuff')

點號分隔的logger名稱定義一個層級。 project.interesting logger被認為是project.interesting.stuff logger 的父級;project logger是project.interestinglogger的父級。

層級為何如此重要? 因為可以設置logger傳播它們的logging調用給它們的上一級。 利用這種方式,你可以在根logger上定義一系列的handler,並捕獲子logger中的所有logging調用。 project這一級定義的handler將會捕獲在project.interestingproject.interesting.stuff這倆級logger上的日志消息。

這種傳播行為可以基於每個logger 進行控制。 如果你不想讓某個logger 傳播消息給它的上一級,你可以關閉這個行為。

調用logging

Logger 實例為每個默認的日志級別提供一個入口方法:

  • logger.debug()
  • logger.info()
  • logger.warning()
  • logger.error()
  • logger.critical()

還有另外兩個調用:

  • logger.log():打印消息時手工指定日志級別。
  • logger.exception():創建一個ERROR 級別日志消息,它封裝當前異常棧的幀。

配置logging

當然,只是將logging 調用放入你的代碼中還是不夠的。 你還需要配置logger、handler、filter 和formatter 來確保日志的輸出是有意義的。

Python的logging庫提供幾種配置logging的技術,從程序接口到配置文件。 默認情況下,Django使用dictConfig格式

為了配置logging,你需要使用LOGGING來定義字典形式的logging設置。 這些設置描述你的logging設置的logger、handler、filter和formatter,以及它們的日志等級和其它屬性。

默認情況下,LOGGING設置與Django默認的logging配置使用下面的模式進行合並。

如果LOGGING中的disable_existing_loggers鍵為True(默認值),那么默認配置中的所有logger都將禁用。 禁用loggers與刪除不一樣;loggers仍然存在,但會靜默地丟棄任何記錄到它的記錄,甚至不會將條目傳播到父logger。 所以你應該非常小心使用'disable_existing_loggers': True;這可能不是你想要的。 相反,你可以將disable_existing_loggers設置為False,並重新定義部分或全部默認loggers;或者你可以將LOGGING_CONFIG設置為None自己處理logging配置

Logging的配置屬於Django setup()函數的一部分。 所以,你可以肯定在你的項目代碼中logger是永遠可用的。

示例

dictConfig格式的完整文檔是logging字典配置最好的信息源。 但是為了讓你嘗嘗,下面是幾個例子。

首先,這是一個簡單的配置,將所有記錄從django logger寫入本地文件:

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'file': {
            'level': 'DEBUG',
            'class': 'logging.FileHandler',
            'filename': '/path/to/django/debug.log',
        },
    },
    'loggers': {
        'django': {
            'handlers': ['file'],
            'level': 'DEBUG',
            'propagate': True,
        },
    },
}

如果你使用這個示例,請確保修改'filename' 路徑為運行Django 應用的用戶有權限寫入的一個位置。

其次,下面這個示例演示如何讓日志系統將Django 的日志打印到控制台。 它在本地開發期間可能有用。

默認情況下,此配置只將級別INFO或更高版本的消息發送到控制台(與Django的默認日志記錄配置相同),但默認情況下僅在DEBUG=True時顯示日志記錄)。 Django 中這樣的日志信息不多。 然而,使用此配置,你還可以設置環境變量DJANGO_LOG_LEVEL=DEBUG,以查看Django的所有調試日志記錄,這非常詳細,因為它包括所有數據庫查詢:

import os

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
        },
    },
    'loggers': {
        'django': {
            'handlers': ['console'],
            'level': os.getenv('DJANGO_LOG_LEVEL', 'INFO'),
        },
    },
}

最后,下面是相當復雜的一個logging 設置:

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
        },
        'simple': {
            'format': '%(levelname)s %(message)s'
        },
    },
    'filters': {
        'special': {
            '()': 'project.logging.SpecialFilter',
            'foo': 'bar',
        },
        'require_debug_true': {
            '()': 'django.utils.log.RequireDebugTrue',
        },
    },
    'handlers': {
        'console': {
            'level': 'INFO',
            'filters': ['require_debug_true'],
            'class': 'logging.StreamHandler',
            'formatter': 'simple'
        },
        'mail_admins': {
            'level': 'ERROR',
            'class': 'django.utils.log.AdminEmailHandler',
            'filters': ['special']
        }
    },
    'loggers': {
        'django': {
            'handlers': ['console'],
            'propagate': True,
        },
        'django.request': {
            'handlers': ['mail_admins'],
            'level': 'ERROR',
            'propagate': False,
        },
        'myproject.custom': {
            'handlers': ['console', 'mail_admins'],
            'level': 'INFO',
            'filters': ['special']
        }
    }
}

這個logging 配置完成以下事情:

  • 以‘dictConfig version 1’格式解析配置。 目前為止,這是dictConfig 格式唯一的版本。

  • 定義兩個formatter:

    • simple,它只輸出日志的級別(例如,DEBUG)和日志消息。

      format 字符串是一個普通的Python 格式化字符串,描述每行日志的細節。 可以在Formatter Objects中找到可以輸出的詳細列表。

    • verbose,它輸出日志級別、日志消息,以及時間、進程、線程和生成日志消息的模塊。

  • 定義兩個過濾器:

    • project.logging.SpecialFilter,使用別名special。 如果此過濾器需要其他參數,則可以在過濾器配置字典中將其作為附加鍵提供。 在這種情況下,實例化SpecialFilter時,參數foo將被賦值為bar
    • django.utils.log.RequireDebugTrue, which passes on records when DEBUG is True.
  • 定義兩個處理程序:

    • console,一個StreamHandler,它將向stderr打印任何INFO(或更高版本)的消息。 這個handler 使用simple 輸出格式。
    • mail_admins,一個AdminEmailHandler,它將用郵件發送ERROR(和更高級)的消息到站點管理員。 這個handler 使用special filter。
  • 配置三個logger:

    • django,它將所有消息傳遞到console處理程序。
    • django.request,它將把所有的ERROR日志信息發送給mail_admins handler。 另外,標記這個logger  向它的父級傳遞日志消息(即propagate的值為False)。 這表示寫入django.request 的日志信息將不會被django logger 處理。
    • myproject.custom,將把INFO級別(更高級別)的日志經過special過濾器過濾后,發送給console和 mail_admins這倆個handler。 這意味着所有INFO級消息(或更高級別)將被打印到控制台; ERRORCRITICAL消息也將通過電子郵件輸出。

自定義logging配置

如果你不想使用Python 的dictConfig 格式配置logger,你可以指定你自己的配置模式。

LOGGING_CONFIG 設置定義一個可調用對象,將它用來配置Django 的logger。 默認情況下,它指向Python 的logging.config.dictConfig() 函數。 但是,如果你想使用不同的配置過程,你可以使用其它只接受一個參數的可調用對象。 配置logging 時,將使用LOGGING 的內容作為參數的值。

禁用logging配置

如果你完全不想配置logging(或者你想使用自己的方法手工配置logging),你可以設置LOGGING_CONFIG 為None。 這將禁用Django默認的logging配置過程。 下面的示例禁用Django 的logging 配置,然后手工配置logging:

LOGGING_CONFIG = None

import logging.config
logging.config.dictConfig(...)

設置LOGGING_CONFIG 為None 只表示禁用自動配置過程,而不是禁用logging 本身。 如果你禁用配置過程,Django 仍然執行logging 調用,只是調用的是默認定義的logging 行為。

分頁

 Django提供了一些類來幫助你管理分頁的數據 — 也就是說,數據被分在不同頁面中,並帶有“上一頁/下一頁”鏈接。 這些類位於django/core/paginator.py中。

示例

Paginator提供對象的一個列表,以及你想為每一頁分配的元素數量,它就會為你提供訪問每一頁上對象的方法:

 

>>> from django.core.paginator import Paginator
>>> objects = ['john', 'paul', 'george', 'ringo']
>>> p = Paginator(objects, 2)

>>> p.count
4
>>> p.num_pages
2
>>> type(p.page_range)  # `<type 'rangeiterator'>` in Python 2.
<class 'range_iterator'>
>>> p.page_range
range(1, 3)

>>> page1 = p.page(1)
>>> page1
<Page 1 of 2>
>>> page1.object_list
['john', 'paul']

>>> page2 = p.page(2)
>>> page2.object_list
['george', 'ringo']
>>> page2.has_next()
False
>>> page2.has_previous()
True
>>> page2.has_other_pages()
True
>>> page2.next_page_number()
Traceback (most recent call last):
...
EmptyPage: That page contains no results
>>> page2.previous_page_number()
1
>>> page2.start_index() # The 1-based index of the first item on this page
3
>>> page2.end_index() # The 1-based index of the last item on this page
4

>>> p.page(0)
Traceback (most recent call last):
...
EmptyPage: That page number is less than 1
>>> p.page(3)
Traceback (most recent call last):
...
EmptyPage: That page contains no results

在視圖中使用Paginator

下面是一個有點復雜的例子,它們在視圖中使用Paginator來為查詢集分頁。 我們提供視圖以及相關的模板來展示如何展示這些結果。 這個例子假設你擁有一個已經導入的Contacts模型。

視圖函數看起來像是這樣:

 

from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.shortcuts import render

def listing(request):
    contact_list = Contacts.objects.all()
    paginator = Paginator(contact_list, 25) # 每頁顯示25個聯系人

    page = request.GET.get('page')
    try:
        contacts = paginator.page(page)
    except PageNotAnInteger:
        # 如果page不是一個整數,則展示第一頁。
        contacts = paginator.page(1)
    except EmptyPage:
        # 如果page不在范圍內(例如,9999),則展示結果的最后一頁。
        contacts = paginator.page(paginator.num_pages)

    return render(request, 'list.html', {'contacts': contacts})

list.html模板中,你會想要包含頁面之間的導航,以及來自對象本身的有用信息:

 

{% for contact in contacts %}
    {# Each "contact" is a Contact model object. #}
    {{ contact.full_name|upper }}<br />
    ...
{% endfor %}

<div class="pagination">
    <span class="step-links">
        {% if contacts.has_previous %}
            <a href="?page={{ contacts.previous_page_number }}">previous</a>
        {% endif %}

        <span class="current">
            Page {{ contacts.number }} of {{ contacts.paginator.num_pages }}.
        </span>

        {% if contacts.has_next %}
            <a href="?page={{ contacts.next_page_number }}">next</a>
        {% endif %}
    </span>
</div>

Paginator對象

Paginator類擁有以下構造函數:

class  Paginator(object_listper_pageorphans=0allow_empty_first_page=True)[source]

必選參數

object_list

列表、元組、QuerySet或具有count()__len__()方法的其它可切片的對象。 為了得到一致的分頁,QuerySet應該排好序,例如使用order_by()子句或模型上的默認ordering

 

per_page
每個頁面上包含的元素的最大數目,不包含孤兒(參見下文的 orphans參數)。

可選參數

orphans
當你不想要最后一個頁面的記錄很少時,請使用此選項。 如果最后一頁通常會有一些小於或等於orphans的項目,那么這些項目將被添加到上一頁(成為最后一頁),而不是讓它們自己單獨留在一個頁面。 例如有23個記錄,per_page=10orphans=3時,將有兩個頁面;第一頁有10個記錄,第二頁(最后一頁)有13個記錄。 orphans默認為零,這意味着頁面從不組合,最后一頁可能有一個項目。
allow_empty_first_page
第一頁是否允許為空。 如果為Falseobject_list為空,則將引發一個EmptyPage錯誤。

方法

Paginator. page(number)[source]

返回在提供的下標處的Page對象,下標以1開始。 如果提供的頁碼不存在,拋出InvalidPage異常。

屬性

Paginator. count

所有頁面中包含的對象總數。

 

Paginator. num_pages

頁面總數。

Paginator. page_range

頁數的基於1的范圍迭代器,例如產生[1, 2, 3, 4]

InvalidPage異常

exception  InvalidPage[source]

異常的基類,當paginator傳入一個無效的頁碼時拋出。

Paginator.page()放回在所請求的頁面無效(比如不是一個整數)時,或者不包含任何對象時拋出異常。 通常,捕獲InvalidPage異常就夠了,但是如果你想更加精細一些,可以捕獲以下兩個異常之一:

exception  PageNotAnInteger[source]

當向page()提供一個不是整數的值時拋出。

exception  EmptyPage[source]

當向page()提供一個有效值,但是那個頁面上沒有任何對象時拋出。

這兩個異常都是InvalidPage的子類,所以你可以通過簡單的except InvalidPage來處理它們。

Page對象

你通常不需要手動構建 Page對象 -- 你可以從Paginator.page()來獲得它們。

class  Page(object_listnumberpaginator)[source]

當調用len()或者直接迭代一個頁面的時候,它的行為類似於 Page.object_list 的序列。

方法

Page. has_next()[source]

如果有下一頁,則返回True

Page. has_previous()[source]

如果有上一頁,返回 True

Page. has_other_pages()[source]

如果有上一頁下一頁,返回True

Page. next_page_number()[source]

返回下一頁的頁碼。 如果下一頁不存在,拋出InvalidPage異常。

Page. previous_page_number()[source]

返回上一頁的頁碼。 如果上一頁不存在,拋出InvalidPage異常。

Page. start_index()[source]

返回當前頁上的第一個對象,相對於分頁列表的所有對象的序號,從1開始。 比如,將五個對象的列表分為每頁兩個對象,第二頁的start_index()會返回3

Page. end_index()[source]

返回當前頁上的最后一個對象,相對於分頁列表的所有對象的序號,從1開始。 比如,將五個對象的列表分為每頁兩個對象,第二頁的end_index() 會返回 4

屬性

Page. object_list

當前頁上所有對象的列表。

Page. number

當前頁的序號,從1開始。

Page. paginator

相關的Paginator對象。

會話

 Django 提供對匿名會話的完全支持。 這個會話框架讓你可以存儲和取回每個站點訪客任意數據。 它在服務器端存儲數據, 並以cookies的形式進行發送和接受數據. Cookie 包含會話的ID —— 而不是數據本身(除非你使用cookie based backend)。

啟用會話

會話是通過一個middleware實現的。

為了啟用會話功能,需要這樣做:

  • 編輯MIDDLEWARE設置,並確保它包含'django.contrib.sessions.middleware.SessionMiddleware'。 使用django-admin startproject創建的默認的settings.py已經啟用SessionMiddleware

如果你不想使用會話,你也可以從MIDDLEWARE'django.contrib.sessions'SessionMiddleware您的INSTALLED_APPS。 它將節省一些性能消耗。

配置會話引擎

默認情況下,Django 存儲會話到你的數據庫中(使用django.contrib.sessions.models.Session模型)。 雖然這很方便,但是在某些架構中存儲會話在其它地方會更快,所以可以配置Django 來存儲會話到你的文件系統上或緩存中。

使用數據庫支持的會話

如果你想使用數據庫支持的會話,你需要添加'django.contrib.sessions' 到你的INSTALLED_APPS設置中。

在配置完成之后,請運行manage.py migrate來安裝保存會話數據的一張數據庫表。

使用緩存的會話

為了更好的性能,你可能想使用一個基於緩存的會話后端。

要使用Django的緩存系統存儲會話數據,您首先需要確保已配置緩存

 

如果你在CACHES中定義多個緩存,Django 將使用默認的緩存。 若要使用另外一種緩存,請設置SESSION_CACHE_ALIAS為該緩存的名字。

配置好緩存之后,對於如何在緩存中存儲數據你有兩個選擇:

  • 對於簡單的緩存會話存儲,可以設置SESSION_ENGINE 為"django.contrib.sessions.backends.cache" 。 此時會話數據將直接存儲在你的緩存中。 然而,緩存數據將可能不會持久:如果緩存填滿或者緩存服務器重啟,緩存數據可能會被清理掉。
  • 若要持久的緩存數據,可以設置SESSION_ENGINE"django.contrib.sessions.backends.cached_db"。 這使用直寫緩存 - 每次寫入高速緩存也將寫入數據庫。 會話讀取僅在數據不在緩存中時才使用數據庫。

兩種會話的存儲都非常快,但是簡單的緩存更快,因為它放棄了持久性。 大部分情況下,cached_db后端已經足夠快,但是如果你需要榨干最后一點的性能,並且接受會話數據丟失的風險,那么你可使用cache后端。

如果你使用cached_db 會話后端,你還需要遵循使用數據庫支持的會話中的配置說明。

使用基於文件的會話

要使用基於文件的會話,請設置SESSION_ENGINE"django.contrib.sessions.backends.file"

你可能還想設置SESSION_FILE_PATH(它的默認值來自tempfile.gettempdir()的輸出,大部分情況是/tmp)來控制Django在哪里存儲會話文件。 請保證你的Web 服務器具有讀取和寫入這個位置的權限。

在視圖中使用會話

SessionMiddleware 激活時,每個HttpRequest 對象 —— 傳遞給Django 視圖函數的第一個參數 —— 將具有一個session 屬性,它是一個類字典對象。

你可以在你的視圖中任何地方讀取並寫入 request.session。 你可以多次編輯它。

class  backends.base. SessionBase

這是所有會話對象的基類。 它具有以下標准的字典方法:

__getitem__(key)

例如:fav_color request.session['fav_color']

__setitem__(keyvalue)

例如:request.session['fav_color'] 'blue'

__delitem__(key)

例如:del request.session['fav_color']。 如果給出的KeyError 在會話中不存在,將拋出 key

__contains__(key)

例如:'fav_color' in request.session

get(keydefault=None)

例如:fav_color request.session.get('fav_color', 'red')

pop(keydefault=__not_given)

示例:fav_color request.session.pop('fav_color', 'blue') / T0>

keys()
items()
setdefault()
clear()

它還具有這些方法:

flush()

從會話中刪除當前會話數據,並刪除會話cookie。 這用於確保前面的會話數據不可以再次被用戶的瀏覽器訪問(例如,django.contrib.auth.logout() 函數中就會調用它)。

設置一個測試的Cookie 來驗證用戶的瀏覽器是否支持Cookie。 因為Cookie 的工作方式,只有到用戶的下一個頁面才能驗證。 更多信息參見下文的設置測試的Cookie

返回True 或False,取決於用戶的瀏覽器是否接受測試的Cookie。 因為Cookie的工作方式,你必須在前面一個單獨的頁面請求中調用set_test_cookie()。 更多信息參見下文的設置測試的Cookie

刪除測試的Cookie。 使用這個函數來自己清理。

set_expiry(value)

設置會話的超時時間。 你可以傳遞一系列不同的值:

  • 如果value是一個整數,會話將在這么多秒沒有活動后過期。 例如,調用request.session.set_expiry(300)將使得會話在5分鍾后過期。
  • 若果value是一個datetimetimedelta 對象,會話將在這個指定的日期/時間過期。 注意datetimetimedelta值只有在你使用PickleSerializer時才可序列化。
  • 如果value0,那么會話的Cookie將在用戶的瀏覽器關閉時過期。
  • 如果valueNone,那么會話轉向使用全局的會話過期策略。

過期的計算不考慮讀取會話的操作。 會話的過期從會話上次修改的時間開始計算。

get_expiry_age()

返回會話離過期的秒數。 對於沒有自定義過期的會話(或者設置為瀏覽器關閉時過期的會話),它將等於SESSION_COOKIE_AGE

該函數接收兩個可選的關鍵字參數:

  • modification:會話的最后一次修改時間,類型為一個datetime 對象。 默認為當前的時間。
  • None:會話的過期信息,類型為一個datetime 對象、一個int(以秒為單位)或expiry。 默認為通過set_expiry()保存在會話中的值,如果沒有則為None
get_expiry_date()

返回過期的日期。 對於沒有自定義過期的會話(或者設置為瀏覽器關閉時過期的會話),它將等於從現在開始SESSION_COOKIE_AGE秒后的日期。

這個函數接受與get_expiry_age()一樣的關鍵字參數。

get_expire_at_browser_close()

返回True 或False,取決於用戶的會話Cookie在用戶瀏覽器關閉時會不會過期。

clear_expired()

從會話的存儲中清除過期的會話。 這個類方法被clearsessions調用。

cycle_key()

創建一個新的會話,同時保留當前的會話數據。 django.contrib.auth.login() 調用這個方法來減緩會話的固定。

會話序列化

默認情況下,Django使用JSON序列化會話數據。 您可以使用SESSION_SERIALIZER設置自定義會話序列化格式。 即使使用Write your own serializer中描述的注意事項,我們強烈建議您使用JSON序列化,特別是在使用cookie后端時。

例如,如果您使用pickle序列化會話數據,則會出現攻擊情形。 如果你使用的是signed cookie session backend 並且SECRET_KEY被攻擊者知道(Django 本身沒有漏洞會導致它被泄漏),攻擊者就可以在會話中插入一個字符串,在unpickle 之后可以在服務器上執行任何代碼。 在因特網上這個攻擊技術很簡單並很容易查到。 盡管Cookie 會話的存儲對Cookie 保存的數據進行了簽名以防止篡改,SECRET_KEY 的泄漏會立即使得可以執行遠端的代碼。

捆綁序列化器

class  serializers. JSONSerializer

對 django.core.signing中的JSON 序列化方法的一個包裝。 只可以序列基本的數據類型。

另外,因為JSON 只支持字符串作為鍵,注意使用非字符串作為request.session 的鍵將不工作:

>>> # initial assignment
>>> request.session[0] = 'bar'
>>> # subsequent requests following serialization & deserialization
>>> # of session data
>>> request.session[0]  # KeyError
>>> request.session['0']
'bar'

類似地,無法在JSON中編碼的數據,如非UTF8字節,如'\xd9'(引發UnicodeDecodeError)不能被存儲。

有關JSON序列化的限制的更多詳細信息,請參閱Write your own serializer部分。

class  serializers. PickleSerializer

支持任意Python 對象,但是正如上面描述的,可能導致遠端執行代碼的漏洞,如果攻擊者知道了SECRET_KEY

編寫自己的串行器

注意,與PickleSerializer不同,JSONSerializer 不可以處理任意的Python 數據類型。 這是常見的情況,需要在便利性和安全性之間權衡。 如果你希望在JSON 格式的會話中存儲更高級的數據類型比如request.session 和 datetime,你需要編寫一個自定義的序列化器(或者在保存它們到Decimal中之前轉換這些值到一個可JSON 序列化的對象)。 雖然串行化這些值是相當簡單的(DjangoJSONEncoder可能是有幫助的),編寫可以可靠地獲取相同內容的解碼器更加脆弱。 例如,返回一個datetime 時,它可能實際上是與datetime 格式碰巧相同的一個字符串)。

你的序列化類必須實現兩個方法,dumps(self, obj) 和loads(self, data) 來分別序列化和去序列化會話數據的字典。

會話對象指南

  • request.session 上使用普通的Python 字符串作為字典的鍵。 這主要是為了方便而不是一條必須遵守的規則。
  • 以一個下划線開始的會話字典的鍵被Django保留作為內部使用。
  • 不要用新的對象覆蓋request.session,且不要訪問或設置它的屬性。 要像Python 字典一樣使用它。

實例

下面這個簡單的視圖在一個用戶提交一個評論后設置has_commented 變量為True。 它不允許一個用戶多次提交評論:

def post_comment(request, new_comment):
    if request.session.get('has_commented', False):
        return HttpResponse("You've already commented.")
    c = comments.Comment(comment=new_comment)
    c.save()
    request.session['has_commented'] = True
    return HttpResponse('Thanks for your comment!')

登錄站點一個“成員”的最簡單的視圖:

def login(request):
    m = Member.objects.get(username=request.POST['username'])
    if m.password == request.POST['password']:
        request.session['member_id'] = m.id
        return HttpResponse("You're logged in.")
    else:
        return HttpResponse("Your username and password didn't match.")

...根據login(),這個用戶登錄一個成員:

def logout(request):
    try:
        del request.session['member_id']
    except KeyError:
        pass
    return HttpResponse("You're logged out.")

標准的django.contrib.auth.logout() 函數實際上所做的內容比這個要多一點以防止意外的數據泄露。 它調用的request.sessionflush()方法。 我們使用這個例子來演示如何利用會話對象來工作,而不是一個完整的logout()實現。

設置測試cookie 

為了方便,Django 提供一個簡單的方法來測試用戶的瀏覽器是否接受Cookie。 只需在一個視圖中調用request.sessionset_test_cookie()方法,並在接下來的視圖中調用test_cookie_worked() —— 不是在同一個視圖中調用。

由於Cookie的工作方式,在set_test_cookie() 和test_cookie_worked() 之間這種笨拙的分離是必要的。 當你設置一個Cookie,直到瀏覽器的下一個請求你不可能真實知道一個瀏覽器是否接受了它。

使用delete_test_cookie() 來自己清除測試的Cookie是一個很好的實踐。 請在你已經驗證測試的Cookie 已經工作后做這件事。

下面是一個典型的使用示例:

from django.http import HttpResponse
from django.shortcuts import render

def login(request):
    if request.method == 'POST':
        if request.session.test_cookie_worked():
            request.session.delete_test_cookie()
            return HttpResponse("You're logged in.")
        else:
            return HttpResponse("Please enable cookies and try again.")
    request.session.set_test_cookie()
    return render(request, 'foo/login_form.html')

使用會議視圖

 在視圖的外面有一個API 可以使用來操作會話的數據:

>>> from django.contrib.sessions.backends.db import SessionStore
>>> s = SessionStore()
>>> # stored as seconds since epoch since datetimes are not serializable in JSON.
>>> s['last_login'] = 1376587691
>>> s.create()
>>> s.session_key
'2b1189a188b44ad18c35e113ac6ceead'
>>> s = SessionStore(session_key='2b1189a188b44ad18c35e113ac6ceead')
>>> s['last_login']
1376587691

SessionStore.create()旨在創建一個新的會話(即,一個沒有從會話存儲中加載,並且使用session_key=None)。 save()旨在保存現有會話(即從會話存儲中加載的會話)。 在新會話中調用save()也可以正常工作,但生成與現有事件相沖突的session_key的幾率很小。 create()調用save()循環,直到生成未使用的session_key

如果你使用的是django.contrib.sessions.backends.db 后端,每個會話只是一個普通的Django 模型。 Session 模型定義在django/contrib/sessions/models.py中。 因為它是一個普通的模型,你可以使用普通的Django 數據庫API 來訪問會話:

>>> from django.contrib.sessions.models import Session
>>> s = Session.objects.get(pk='2b1189a188b44ad18c35e113ac6ceead')
>>> s.expire_date
datetime.datetime(2005, 8, 20, 13, 35, 12)

注意,你需要調用get_decoded() 以獲得會話的字典。 這是必需的,因為字典是以編碼后的格式保存的:

>>> s.session_data
'KGRwMQpTJ19hdXRoX3VzZXJfaWQnCnAyCkkxCnMuMTExY2ZjODI2Yj...'
>>> s.get_decoded()
{'user_id': 42}

會話保存時

默認情況下,Django 只有在會話被修改時才會保存會話到數據庫中 —— 即它的字典中的任何值被賦值或刪除時:

# Session is modified.
request.session['foo'] = 'bar'

# Session is modified.
del request.session['foo']

# Session is modified.
request.session['foo'] = {}

# Gotcha: Session is NOT modified, because this alters
# request.session['foo'] instead of request.session.
request.session['foo']['bar'] = 'baz'

上面例子的最后一種情況,我們可以通過設置會話對象的modified屬性顯式地告訴會話對象它已經被修改過:

request.session.modified = True

若要修改這個默認的行為,可以設置 SESSION_SAVE_EVERY_REQUEST 為True。 當設置為True時,Django 將對每個請求保存會話到數據庫中。

注意會話的Cookie 只有在一個會話被創建或修改后才會發送。 如果SESSION_SAVE_EVERY_REQUEST 為True,會話的Cookie 將在每個請求中發送。

類似地,會話Cookie 的expires 部分在每次發送會話Cookie 時更新。

如果響應的狀態碼時500,則會話不會被保存。

瀏覽器長度會話與持久會話

你可以通過SESSION_EXPIRE_AT_BROWSER_CLOSE設置來控制會話框架使用瀏覽器時長的會話,還是持久的會話。

默認情況下,SESSION_EXPIRE_AT_BROWSER_CLOSE設置為False,表示會話的Cookie 保存在用戶的瀏覽器中的時間為SESSION_COOKIE_AGE。 如果你不想讓大家每次打開瀏覽器時都需要登錄時可以這樣使用。

如果SESSION_EXPIRE_AT_BROWSER_CLOSE 設置為True,Django 將使用瀏覽器時長的Cookie —— 用戶關閉他們的瀏覽器時立即過期。 如果你想讓大家在每次打開瀏覽器時都需要登錄時可以這樣使用。

這個設置是一個全局的默認值,可以通過顯式地調request.session 的set_expiry() 方法來覆蓋,在上面的在視圖中使用會話中有描述。

清除會話存儲

隨着用戶在你的網站上創建新的會話,會話數據可能會在你的會話存儲倉庫中積累。 如果你正在使用數據庫作為后端,django_session 數據庫表將持續增長。 如果你正在使用文件作為后端,你的臨時目錄包含的文件數量將持續增長。

要理解這個問題,考慮一下數據庫后端發生的情況。 當一個用戶登入時,Django 添加一行到django_session 數據庫表中。 每次會話數據更新時,Django 將更新這行。 如果用戶手工登出,Django 將刪除這行。 但是如果該用戶登出,該行將永遠不會刪除。以文件為后端的過程類似。

Django 提供自動清除過期會話的功能。 因此,定期地清除會話是你的任務。 Django 提供一個清除用的管理命令來滿足這個目的:clearsessions。 建議定期調用這個命令,例如作為一個每天運行的Cron 任務。

注意,以緩存為后端不存在這個問題,因為緩存會自動刪除過期的數據。 以cookie 為后端也不存在這個問題,因為會話數據通過用戶的瀏覽器保存。

設置

一些Django settings 讓你可以控制會話的行為:

會話安全性

一個站點下的子域名能夠在客戶端為整個域名設置Cookie。 如果子域名不受信任的用戶控制且允許來自子域名的Cookie,那么可能發生會話攻擊。

例如,一個攻擊者可以登錄good.example.com並為他的賬號獲取一個合法的會話。 如果該攻擊者具有bad.example.com的控制權,那么他可以使用這個域名來發送他的會話ID給你,因為子域名允許在*.example.com上設置Cookie。 當你訪問good.example.com時,你將以攻擊者身份登錄且不會察覺到並輸入你的敏感的個人信息(例如,信用卡信息)到攻擊者的賬號中。

另外一個可能的攻擊是,如果bad.example.com設置它的 SESSION_COOKIE_DOMAIN 為good.example.com ,這將導致來自該站點的會話Cookie 被發送到".example.com"

 信號

 Django 提供一個“信號分發器”,允許解耦的應用在框架的其它地方發生操作時會被通知到。 簡單來說,信號允許特定的sender通知一組receiver某些操作已經發生。 這在多處代碼和同一事件有關聯的情況下很有用。

Django提供一組內建的信號,允許用戶的代碼獲得Django特定操作的通知。 它們包含一些有用的通知:

監聽信號

要接收信號,請使用Signal.connect()方法注冊receiver函數。 receiver函數在信號發送時調用。

Signal. connect(receiversender=Noneweak=Truedispatch_uid=None)[source]

 參數:

receiver – 和這個信號連接的回調函數。 詳見Receiver函數。
sender – 指定一個特定的sender,來從它那里接受信號。 詳見連接到特定發送者發送的信號。
weak – Django通常以弱引用儲存信號處理器。 這就是說,如果你的receiver是個局部變量,可能會被垃圾回收。 
若要防止這個行為,當你調用信號的connect()方法時,請傳遞weak
=False。 dispatch_uid – 一個信號接收者的唯一標識符,以防信號多次發送。 詳見防止重復的信號。

通過注冊一個在每次HTTP請求結束時調用的信號,讓我們來看一看它是如何工作的。 我們將會連接到request_finished 信號。

Receiver函數

首先,我們需要定義receiver函數。 Receiver可以是任何Python函數或者方法:

def my_callback(sender, **kwargs):
    print("Request finished!")

請注意,該函數接受一個sender參數,以及通配符關鍵字參數(**kwargs);所有信號處理程序都必須接受這些參數。

我們過一會兒再關注sender,現在先看一看**kwargs參數。 所有信號都發送關鍵字參數,並且可以在任何時候修改這些關鍵字參數。 對於request_finished,它的文檔描述是不發送任何參數,這意味着我們似乎可以將我們自己的信號處理器編寫成my_callback(sender)

這是錯誤的 -- 實際上,如果你這么做了,Django 會拋出異常。 這是因為信號在任何時候都可能添加參數,你的receiver 必須能夠處理這些新的參數。

連接receiver函數

有兩種方法可以將一個receiver連接到信號。 你可以采用手動連接的方法:

from django.core.signals import request_finished

request_finished.connect(my_callback)

或者使用receiver() 裝飾器來自動連接:

receiver(signal)[source]
參數:    signal - 函數將要連接到的信號或信號列表。

下面是使用裝飾器連接的方法:

from django.core.signals import request_finished
from django.dispatch import receiver

@receiver(request_finished)
def my_callback(sender, **kwargs):
    print("Request finished!")

現在,我們的my_callback函數會在每次請求結束時調用。

這段代碼應該放在哪里?

嚴格來說,信號處理和注冊的代碼應該放在你想要的任何地方,但是推薦避免放在應用的根模塊和models模塊中,以盡量減少產生導入代碼的副作用。

實際上,信號處理函數通常定義在應用相關的signals子模塊中。 信號receiver在你應用配置類中的ready()方法中連接。 如果你使用receiver()裝飾器,只需要在ready()內部導入signals子模塊就可以

連接由特定sender發送的信號

一些信號會發送多次,但是你只想接收這些信號的一個確定的子集。 例如,考慮 django.db.models.signals.pre_save 信號,它在模型保存之前發送。 大多數情況下,你並不需要知道所有模型何時保存 -- 只需要知道一個特定的模型何時保存。

在這些情況下,你可以通過注冊來接收只由特定sender 發出的信號。 對於django.db.models.signals.pre_save的情況, sender 是被保存的模型類,所以你可以認為你只需要由某些模型發出的信號:

from django.db.models.signals import pre_save
from django.dispatch import receiver
from myapp.models import MyModel


@receiver(pre_save, sender=MyModel)
def my_handler(sender, **kwargs):
    ...

my_handler函數只在MyModel實例保存時被調用。

不同的信號使用不同的對象作為sender;有關每個特定信號的詳細信息,你需要參考內建信號的文檔

防止重復信號

在一些情況下,連接receiver 到信號的代碼可能會執行多次。 這可能會導致receiver函數被多次注冊,因此對於單個信號事件多次進行調用。

如果這樣的行為會導致問題(例如在任何時候模型保存時使用信號來發送郵件),可以傳遞一個唯一的標識符作為dispatch_uid 參數來標識你的receiver 函數。 標識符通常是一個字符串,雖然任何可計算哈希的對象都可以。 最后的結果是,對於每個唯一的dispatch_uid值,你的receiver 函數都只綁定到信號一次:

from django.core.signals import request_finished

request_finished.connect(my_callback, dispatch_uid="my_unique_identifier")

定義和發送信號

你的應用可以利用信號功能來提供自己的信號。

定義信號

class  Signal(providing_args=list)[source]

所有信號都是 django.dispatch.Signal 的實例。 providing_args是一個列表,由信號將提供給監聽者的參數名稱組成。 理論上是這樣,但是實際上並沒有任何檢查來保證向監聽者提供了這些參數。

像這樣:

import django.dispatch

pizza_done = django.dispatch.Signal(providing_args=["toppings", "size"])

這段代碼聲明了pizza_done信號,它向接受者提供size和 toppings 參數。

要記住你可以在任何時候修改參數的列表,所以首次嘗試的時候不需要完全確定API。

發送信號

Django中有兩種方法用於發送信號。

Signal. send(sender**kwargs)[source]
Signal. send_robustsender** kwargs[source] 

要發送信號,請調用Signal.send()(所有內置信號都使用它)或Signal.send_robust()。 你必須提供sender參數(大部分時候是一個類),並且可以提供任意數量的其他關鍵字參數。

例如,這樣來發送我們的pizza_done信號:

class PizzaStore(object):
    ...

    def send_pizza(self, toppings, size):
        pizza_done.send(sender=self.__class__, toppings=toppings, size=size)
        ...

send()send_robust()返回一個元組對的列表 [(receiver, response), ... ],表示被調用的receiver函數及其響應值的列表。

send_robust() 與 send()在處理receiver 函數時產生的異常有所不同。 send()不會捕獲receiver產生的異常;它只是讓錯誤向上傳播。 所以在錯誤產生的情況,不是所有receiver都會獲得通知。

send_robust捕獲所有繼承自Python ()Exception類的異常,並且確保所有receiver都能得到信號的通知。 如果發生錯誤,錯誤實例會在產生錯誤的receiver 的二元組中返回。

調用send_robust()的時候,所返回的錯誤的__traceback__屬性上會帶有 traceback。

斷開信號

Signal. disconnect(receiver=Nonesender=Nonedispatch_uid=None)[source]

調用Signal.disconnect()來斷開信號的接收器。 Signal.connect()中描述的參數相同。 如果接收器成功斷開,返回 True ,否則返回False

receiver 參數表示要斷開的已注冊receiver。 如果使用dispatch_uid 標識receiver,它可能為None

自1.9版以來已棄用: weak參數已被棄用,因為它不起作用。 它將在Django 2.0中被刪除。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM