第一步 入門
檢查版本
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 %}
{% 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.字段選項
如果為True
,Django將在數據庫中把空值存儲為NULL
。 默認為False
。
如果為
True
,該字段允許為空值, 默認為False
。
要注意,這與 null
不同。 null
純粹是數據庫范疇,指數據庫中字段內容是否允許為空,而 blank
是表單數據輸入驗證范疇的。 如果一個字段的blank=True
,表單的驗證將允 許輸入一個空值。 如果字段的blank=False
,該字段就是必填的。
由二項元組構成的一個可迭代對象(例如,列表或元組),用來給字段提供選擇項。 如果設置了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'
字段的默認值。 可以是一個值或者可調用對象。 如果可調用 ,每個新對象創建時它都會被調用。
表單部件額外顯示的幫助內容。 即使字段不在表單中使用,它對生成文檔也很有用。
如果為True
,那么這個字段就是模型的主鍵。
如果為True
, 則這個字段在整張表中必須是唯一的。
2.字段的自述名
除ForeignKey
、ManyToManyField
和 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)
ForeignKey
、ManyToManyField
和 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種風格的繼承。
- 通常,你只想使用父類來持有一些信息,你不想在每個子模型中都敲一遍。 這個類永遠不會單獨使用,所以你要使用抽象的基類。
- 如果你繼承一個已經存在的模型且想讓每個模型具有它自己的數據庫表,那么應該使用多表繼承。
- 最后,如果你只是想改變一個模塊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
模型將有三個字段:name
、age
和 home_group
。 CommonInfo
模型無法像一般的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
兩個最普遍的途徑是:
每次你篩選一個QuerySet
,得到的都是全新的另一個QuerySet
,它和之前的QuerySet
之間沒有任何綁定關系。 每次篩選都會創建一個獨立的QuerySet
,它可以被存儲及反復使用。
QuerySets
是惰性執行的 —— 創建QuerySet
不會帶來任何數據庫的訪問。 你可以將過濾器保持一整天,直到QuerySet
需要求值時,Django 才會真正運行這個查詢。
(2)使用get()檢索單個對象
filter()
始終給你一個QuerySet
,即使只有一個對象滿足查詢條件 —— 這種情況下,QuerySet
將只包含一個元素。
如果你知道只有一個對象滿足你的查詢,你可以使用Manager
的get()
方法,它直接返回該對象:
>>> 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 的OFFSET
和LIMIT
子句。
例如,下面的語句返回前面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')
-
startswith
,endswith
-
分別表示以XXX開頭和以XXX結尾。 當然還有大小寫不敏感的版本,叫做
istartswith
和iendswith
。
(4)跨關聯關系的查詢
Django 提供一種強大而又直觀的方式來“處理”查詢中的關聯關系,它在后台自動幫你處理JOIN
。 若要跨越關聯關系,只需使用關聯的模型字段的名稱,並使用雙下划線分隔,直至你想要的字段:
下面這個例子獲取所有Blog
的name
為'Beatles Blog'
的Entry
對象:
>>> Entry.objects.filter(blog__name='Beatles Blog')
這種跨越可以是任意的深度。
它還可以反向工作。 若要引用一個“反向”的關系,只需要使用該模型的小寫的名稱。
下面的示例獲取所有的Blog
對象,它們至少有一個Entry
的headline
包含'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
自帶的QuerySet
。 get_queryset()
應該返回一個帶有你需要的屬性的QuerySet
。
例如,下面的模型有兩個Manager
,一個返回所有的對象,另一個則只返回作者是Roald Dahl 的對象:
...語句Book.objects.all()
將返回數據庫中的所有書籍。
你可以通過重寫Manager.get_queryset()
方法來覆蓋Manager
自帶的QuerySet
。 get_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_name
或app_label.model_name
的模型,可以使用model._meta.label
或model._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
-
模型中某個可排序的字段的名稱,比如
DateField
、DateTimeField
或者IntegerField
。 它指定了Manager
的latest()
和earliest()
中使用的默認字段。例如:
get_latest_by = "order_date"
managed
-
Options.
managed
-
默認為
True
,表示Django會通過migrate
創建合適的數據表,並且可通過flush
管理命令移除這些數據庫表。 換句話說,Django會管理這些數據表的生命周期。如果是
False
,Django 就不會為當前模型創建和刪除數據表。 如果當前模型表示一個已經存在的且是通過其它方法創建的者數據表或數據庫視圖,這會相當有用。 這是設置為managed=False
時唯一的不同之處。 模型處理的其它任何方面都和平常一樣。 這包括:-
如果你不聲明它的話,會向你的模型中添加一個自增主鍵。 為了避免給后面的代碼讀者帶來混亂,當你在使用未被管理的模型時,強烈推薦你指定(specify)數據表中所有的列。
-
如果一個模型設置了
managed=False
且含有ManyToManyField
,且這個多對多字段指向其他同樣也是未被管理模型的,那么這兩個未被管理的模型的多對多中介表也不會被創建。 但是,一個被管理模型和一個未被管理模型之間的中介表就會被創建。如果你需要修改這一默認行為,創建中介表作為顯式的模型(也要設置
managed
),並且使用ManyToManyField.through
為你的自定義模型創建關聯。
如果你進行測試,測試中涉及非托管 model (
managed=False
),那么在測試之前,你應該要確保在 測試啟動時 已經創建了正確的數據表。如果你對在Python層面修改模型類的行為感興趣,你可以設置
managed=False
,並且為一個已經存在的模型創建一個副本。 不過在面對這種情況時還有個更好的辦法就是使 用Proxy models. -
order_with_respect_to
-
Options.
order_with_respect_to
-
使此對象相對於給定字段可以排序,通常為
ForeignKey
。 這可以用於使關聯的對象相對於父對象可排序。 比如,如果Answer
和Question
相關聯,一個問題有至少一個答案,並且答案的順序非常重要,你可以這樣做:
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
-
此型號特定於受支持的數據庫供應商的名稱。 當前內置的供應商名稱是:
sqlite
,postgresql
,mysql
,oracle
。 如果此屬性不為空,並且當前連接供應商不匹配,則該模型將不會同步。
select_on_save
-
Options.
select_on_save
-
該選項決定Django是否采用1.6之前的
django.db.models.Model.save()
算法。 舊的算法使用SELECT
來判斷是否存在需要更新的行。 而新的算法直接嘗試使用UPDATE
。 在某些少見的情況下,一個已存在行的UPDATE
操作對Django不可見。 一個例子是PostgreSQL的返回NULL
的ON 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 代碼遵循的算法:
- Django 決定要使用的根URLconf 模塊。 通常,這是
ROOT_URLCONF
設置的值,但是如果傳入的HttpRequest
對象具有urlconf
屬性(由中間件設置),則其值將被用於代替ROOT_URLCONF
設置。 - Django 加載該Python 模塊並尋找可用的
urlpatterns
。 它是django.conf.urls.url()
實例的一個Python 列表。 - Django 依次匹配每個URL 模式,在與請求的URL 匹配的第一個模式停下來。
- 一旦正則表達式匹配,Django將導入並調用給定的視圖,該視圖是一個簡單的Python函數(或基於類的class-based view)。 視圖將獲得如下參數:
- 一個
HttpRequest
實例。 - 如果匹配的正則表達式返回了沒有命名的組,那么正則表達式匹配的內容將作為位置參數提供給視圖。
- 關鍵字參數由正則表達式匹配的命名組組成,但是可以被
django.conf.urls.url()
的可選參數kwargs
覆蓋。
- 一個
- 如果沒有匹配到正則表達式,或者如果過程中拋出一個異常,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
異常。
實例
下面的示例從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方法相關的代碼(
GET
,POST
等) 可以通過單獨的方法而不是條件分支來解決。 - 面向對象的技術例如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 shortcuts和TemplateResponse
對象。
雖然基於類的視圖的最小實現不需要任何類屬性來完成它的功能,但是在許多基於類的設計中類屬性非常重要,有兩種方式來設置類屬性。
第一種方式是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_view
(request,view_func,view_args,view_kwargs)
request
是一個HttpRequest
對象。 view_func
是 Django會調用的一個Python的函數。 (它是一個真實的函數對象,不是函數的字符名稱。) view_args
是一個會被傳遞到視圖的位置參數列表,而view_kwargs
是一個會被傳遞到視圖的關鍵字參數字典。 view_args
和 view_kwargs
都不包括第一個視圖參數(request
)。
process_view()
會在Django 調用視圖之前被調用。
它應該返回一個None
或一個HttpResponse
對象。 如果返回None
,Django 將會繼續處理這個請求,執行其它的process_view()
中間件,然后調用對應的視圖。 如果它返回一個HttpResponse
對象,Django不會打擾調用相應的視圖;它將應用響應中間件到HttpResponse
並返回結果。
process_exception()
-
process_exception
(request,exception)
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
對象,通過修改 response
和response.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.DjangoTemplates
和 django.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
包中載入所有otherlibrary
和somelibrary
中已經注冊的標簽和過濾器:
{% 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 中的每個模板會自動轉義每個變量的輸出。 明確地說,下面五個字符被轉義:
<
會轉換為<
>
會轉換為>
'
(單引號)轉換為'
"
(雙引號)會轉換為"
&
會轉換為&
我們要再次強調這個行為是默認打開的。 如果你使用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.py
、models.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()
方法需要兩個參數:
- 過濾器的名稱(一個字符串對象)
- 編譯的函數 – 一個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
處理表單時候只會用到POST
和 GET
方法。
Django 的登錄表單使用POST
方法,在這個方法中瀏覽器組合表單數據、對它們進行編碼以用於傳輸、將它們發送到服務器然后接收它的響應。
相反,GET
組合提交的數據為一個字符串,然后使用它來生成一個URL。 這個URL 將包含數據發送的地址以及數據的鍵和值。 如果你在Django 文檔中做一次搜索,你會立即看到這點,此時將生成一個https://docs.djangoproject.com/search/?q=forms&release=1
形式的URL。
POST
和GET
用於不同的目的。
用於改變系統狀態的請求 —— 例如,給數據庫帶來變化的請求 —— 應該使用POST
。 GET
只應該用於不會影響系統狀態的請求。
GET
還不適合密碼表單,因為密碼將出現在URL 中,以及瀏覽器的歷史和服務器的日志中,而且都是以普通的文本格式。 它還不適合數據量大的表單和二進制數據,例如一張圖片。 使用GET
請求作為管理站點的表單具有安全隱患:攻擊者很容易模擬表單請求來取得系統的敏感數據。 POST
,如果與其它的保護措施結合將對訪問提供更多的控制,例如Django 的CSRF protection。
另一個方面,GET
適合網頁搜索這樣的表單,因為這種表示一個GET
請求的URL 可以很容易地作為書簽、分享和重新提交。
Django在表單中的角色
處理表單是一件很復雜的事情。 考慮一下Django 的Admin 站點,不同類型的大量數據項需要在一個表單中准備好、渲染成HTML、使用一個方便的界面編輯、返回給服務器、驗證並清除,然后保存或者向后繼續處理。
Django 的表單功能可以簡化並自動化大部分這些工作,而且還可以比大部分程序員自己所編寫的代碼更安全。
Django 會處理表單工作中的三個顯著不同的部分:
- 准備數據、重構數據,以便下一步提交。
- 為數據創建HTML 表單
- 接收並處理客戶端提交的表單和數據
可以手工編寫代碼來實現,但是Django 可以幫你完成所有這些工作。
Django的Form類
表單系統的核心部分是Django 的Form
類。 Django 的模型描述一個對象的邏輯結構、行為以及展現給我們的方式,與此類似,Form
類描述一個表單並決定它如何工作和展現。
就像模型類的屬性映射到數據庫的字段一樣,表單類的字段會映射到HTML 的<input>
表單的元素。 (ModelForm
通過一個Form
映射模型類的字段到HTML 表單的<input>
元素;Django 的Admin 站點就是基於這個)。
一個表單的字段本身就是類;他們管理表單數據,並在提交表單時執行驗證。 DateField
和FileField
處理的數據類型差別很大,必須完成不同的事情。
表單字段在瀏覽器中呈現給用戶的是一個HTML 的“widget” —— 用戶界面的一個片段。 每個字段類型都有一個合適的默認Widget class,需要時可以覆蓋。
實例化、處理和渲染表單
在Django 中渲染一個對象時,我們通常:
- 在視圖中獲得它(例如,從數據庫中獲取)
- 將它傳遞給模板的context
- 使用模板變量將它擴展為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 表單將用之前提交的數據填充,然后可以根據要求編輯並改正它。
如果True
為is_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
。 在這個例子中,我們的表單具有四個字段:message
、subject
、sender
和cc_myself
。 CharField
,EmailField
和BooleanField
只是三種可用的字段類型
窗口小部件
每個表單字段都有一個對應的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
將是一個布爾值。 類似地,IntegerField
和FloatField
字段分別將值轉換為Python 的int
和float
。
下面是在視圖中如何處理表單數據:
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'
若要表示一個字段不是必需的,請傳遞Field
給 required=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。 當Field
在Form
中顯示時將用到它。
正如在前面“輸出表單為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()
方法用於決定字段的值是否從初始值發生了改變。 返回True
或False
。
4.內置Field類
BooleanField
-
class
BooleanField
(**kwargs)[source] -
- 默認的Widget:
CheckboxInput
- 空值:
False
- 規范化為:Python 的
True
或False
。 - 如果字段帶有
True
,驗證值是否為required=True
(例如復選框被勾上)。 - 錯誤信息的鍵:
required
- 默認的Widget:
-
class
CharField
(**kwargs)[source] -
- 默認的Widget:
TextInput
- 空值:與
empty_value
給出的任何值。 - 規范化為:一個Unicode 對象。
- 如果提供,驗證
max_length
或min_length
。 否則,所有的輸入都是合法的。 - 錯誤信息的鍵:
min_length
,max_length
,required
有三個可選參數進行驗證:
-
max_length
-
min_length
如果提供,這兩個參數將確保字符串的最大和最小長度。
-
strip
-
如果
True
(默認),該值將被剝離前導和尾隨空格。
-
empty_value
-
Django中的新功能1.11。
用來表示“空”的值。 默認為空字符串。
- 默認的Widget:
ChoiceField
-
class
ChoiceField
(**kwargs)[source] -
- 默認的Widget:
Select
- 空值:
''
(一個空字符串) - 規范化為:一個Unicode 對象。
- 驗證給定的值在選項列表中存在。
- 錯誤信息的鍵:
required
,invalid_choice
invalid_choice
錯誤消息可能包含%(value)s
,它將被選擇的選項替換掉。還有一個參數:
-
choices
-
用來作為該字段選項的一個二元組組成的可迭代對象(例如,列表或元組)或者一個可調用對象。 參數的格式與模型字段的
choices
參數相同。
- 默認的Widget:
TypedChoiceField
-
class
TypedChoiceField
(**kwargs)[source] -
就像
ChoiceField
一樣,除了TypedChoiceField
還有兩個額外的參數:coerce
和empty_value
。- 默認的Widget:
Select
- 空值:與
empty_value
給出的任何值。 - 規范化為:
coerce
參數類型的值。 - 驗證給定的值在選項列表中存在並且可以被強制轉換。
- 錯誤信息的鍵:
required
,invalid_choice
接收的額外參數:
-
coerce
-
接收一個參數並返回強制轉換后的值的一個函數。 例如內建的
bool
、float
、int
和其它類型。 默認為id 函數。 注意強制轉換在輸入驗證結束后發生,所以它可能強制轉換不在choices
中的值。
-
empty_value
-
用於表示“空”的值。默認為空字符串;
None
是這里的另一個常見選擇。 注意這個值不會被coerce
參數中指定的函數強制轉換,所以請根據情況進行選擇。
- 默認的Widget:
DateField
-
class
DateField
(**kwargs)[source] -
- 默認的Widget:
DateInput
- 空值:
None
- 規范化為:一個Python
datetime.date
對象。 - 驗證給出的值是一個
datetime.date
、datetime.datetime
或指定日期格式的字符串。 - 錯誤信息的鍵:
required
,invalid
接收一個可選的參數:
-
input_formats
-
一個格式的列表,用於轉換一個字符串為
datetime.date
對象。
如果沒有提供
input_formats
,默認的輸入格式為: - 默認的Widget:
['%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.datetime
、datetime.date
或指定日期格式的字符串。 - 錯誤信息的鍵:
required
,invalid
接收一個可選的參數:
-
input_formats
-
一個格式的列表,用於轉換一個字符串為
datetime.datetime
對象。
如果沒有提供
input_formats
,默認的輸入格式為: - 默認的Widget:
['%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_digits
,max_digits
,max_decimal_places
,max_value
,invalid
,required
,min_value
%(limit_value)s
和min_value
錯誤信息可能包含max_value
,它們將被真正的限制值替換。 類似地,max_whole_digits
、max_decimal_places
和max_digits
錯誤消息可能包含%(max)s
。接收四個可選的參數:
-
max_value
-
min_value
-
它們控制字段中允許的值的范圍,應該以
decimal.Decimal
值給出。
-
max_digits
-
值允許的最大位數(小數點之前和之后的數字總共的位數,前導的零將被刪除)。
-
decimal_places
-
允許的最大小數位。
- 默認的Widget:當
DurationField
-
class
DurationField
(**kwargs)[source] -
- 默認的Widget:
TextInput
- 空值:
None
- 規范化為:一個Python
timedelta
。 - 驗證給出的值是一個字符串,而可以給轉換為
timedelta
。 - 錯誤信息的鍵:
required
,invalid
.
接收任何可以被
parse_duration()
理解的格式。 - 默認的Widget:
EmailField
-
class
EmailField
(**kwargs)[source] -
- 默認的Widget:
EmailInput
- 空值:
''
(一個空字符串) - 規范化為:一個Unicode 對象。
- 驗證給出的值是一個合法的郵件地址,使用一個適度復雜的正則表達式。
- 錯誤信息的鍵:
required
,invalid
具有兩個可選的參數用於驗證,
max_length
和min_length
。 如果提供,這兩個參數將確保字符串的最大和最小長度。 - 默認的Widget:
FileField
-
class
FileField
(**kwargs)[source] -
- 默認的Widget:
ClearableFileInput
- 空值:
None
- 規范化為:一個
UploadedFile
對象,它封裝文件內容和文件名為一個單獨的對象。 - 可以驗證非空的文件數據已經綁定到表單。
- 錯誤信息的鍵:
missing
,invalid
,required
,empty
,max_length
具有兩個可選的參數用於驗證,
max_length
和allow_empty_file
。 如果提供,這兩個參數確保文件名的最大長度,而且即使文件內容為空時驗證也會成功。 - 默認的Widget:
FloatField
-
class
FloatField
(**kwargs)[source] -
- 默認的Widget:當
Field.localize
是False
時為NumberInput
,否則為TextInput
。 - 空值:
None
- 規范化為:一個Float 對象。
- 驗證給定的值是一個浮點數。 和Python 的
float()
函數一樣,允許前導和尾隨的空白符。 - 錯誤信息的鍵:
max_value
,invalid
,required
,min_value
接收兩個可選的參數用於驗證,
max_value
和min_value
。 它們控制字段中允許的值的范圍。 - 默認的Widget:當
IntergerField
-
class
IntegerField
(**kwargs)[source] -
- 默認的Widget:當
Field.localize
是False
時為NumberInput
,否則為TextInput
。 - 空值:
None
- 規范化為:一個Python 整數或長整數。
- 驗證給定值是一個整數。 允許前導和尾隨空格,如Python的
int()
函數。 - 錯誤信息的鍵:
max_value
,invalid
,required
,min_value
%(limit_value)s
和min_value
錯誤信息可能包含max_value
,它們將被真正的限制值替換。采用兩個可選參數進行驗證:
-
max_value
-
min_value
它們控制字段中允許的值的范圍。
- 默認的Widget:當
GenericIPAddressField
-
class
GenericIPAddressField
(**kwargs)[source] -
包含IPv4或IPv6地址的字段。
- 默認的Widget:
TextInput
- 空值:
''
(一個空字符串) - 規范化為:一個Unicode 對象。 IPv6地址如下所述進行歸一化。
- 驗證給定值是有效的IP地址。
- 錯誤信息的鍵:
required
,invalid
IPv6地址規范化遵循 RFC 4291#section-2.2第2.2節,包括使用該段第3段中建議的IPv4格式,如
::ffff:192.0.2.0
例如,::ffff:0a0a:0a0a
將被標准化為2001::1
和2001:0::0:01
::ffff:10.10.10.10
。 所有字符都轉換為小寫。有兩個可選參數:
-
protocol
-
限制指定協議的有效輸入。 接受的值為
IPv6
(默認值),IPv4
或both
。 匹配不區分大小寫。
-
unpack_ipv4
-
解開IPv4映射地址,例如
::ffff:192.0.2.1
。 如果啟用此選項,則該地址將解包到192.0.2.1
。 默認為禁用。 只能在protocol
設置為'both'
時使用。
- 默認的Widget:
MultipleChoiceField
-
class
MultipleChoiceField
(**kwargs)[source] -
- 默認的Widget:
SelectMultiple
- 空值:
[]
(一個空列表) - 規范化為:一個Unicode 對象列表。
- 驗證給定值列表中的每個值都存在於選擇列表中。
- 錯誤信息的鍵:
invalid_list
,invalid_choice
,required
invalid_choice
錯誤消息可能包含%(value)s
,它將被選擇的選項替換掉。對於
choices
,需要一個額外的必需參數ChoiceField
。 - 默認的Widget:
TypedMultipleChoiceField
-
class
TypedMultipleChoiceField
(**kwargs)[source] -
就像
MultipleChoiceField
,除了TypedMultipleChoiceField
需要兩個額外的參數,coerce
和empty_value
。- 默認的Widget:
SelectMultiple
- 空值:
empty_value
- 規范化為:
coerce
參數提供的類型值列表。 - 驗證給定值存在於選項列表中並且可以強制。
- 錯誤信息的鍵:
required
,invalid_choice
invalid_choice
錯誤消息可能包含%(value)s
,它將被選擇的選項替換掉。對於
TypedChoiceField
,需要兩個額外的參數empty_value
和coerce
。 - 默認的Widget:
RegexField
-
class
RegexField
(**kwargs)[source] -
- 默認的Widget:
TextInput
- 空值:
''
(一個空字符串) - 規范化為:一個Unicode 對象。
- 驗證給定值與某個正則表達式匹配。
- 錯誤信息的鍵:
required
,invalid
需要一個必需的參數:
-
regex
-
指定為字符串或編譯的正則表達式對象的正則表達式。
還需要
max_length
,min_length
和strip
,它們與CharField
一樣工作。-
strip
-
默認為
False
。 如果啟用,則將在正則表達式驗證之前應用剝離
- 默認的Widget:
SlugField
-
class
SlugField
(**kwargs)[source] -
- 默認的Widget:
TextInput
- 空值:
''
(一個空字符串) - 規范化為:一個Unicode 對象。
- 驗證給定的字符串只包括字母、數字、下划線及連字符。
- 錯誤信息的鍵:
required
,invalid
此字段用於在表單中表示模型
SlugField
。使用可選參數:
-
allow_unicode
-
布爾型指令除了ASCII字母外,還可以接受Unicode字母。 默認為
False
。
- 默認的Widget:
TimeField
-
class
TimeField
(**kwargs)[source] -
- 默認的Widget:
TextInput
- 空值:
None
- 規范化為:一個Python 的
datetime.time
對象。 - 驗證給定值是
datetime.time
或以特定時間格式格式化的字符串。 - 錯誤信息的鍵:
required
,invalid
接收一個可選的參數:
-
input_formats
-
用於嘗試將字符串轉換為有效的
datetime.time
對象的格式列表。
如果沒有提供
input_formats
,默認的輸入格式為: - 默認的Widget:
URLField
-
class
URLField
(**kwargs)[source] -
- 默認的Widget:
URLInput
- 空值:
''
(一個空字符串) - 規范化為:一個Unicode 對象。
- 驗證給定值是有效的URL。
- 錯誤信息的鍵:
required
,invalid
采用以下可選參數:
-
max_length
-
min_length
這些與
CharField.max_length
和CharField.min_length
相同。 - 默認的Widget:
更多-->>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" />'
-
format_value
(value)[source] -
清除並返回一個用於小部件模板的值。
value
不能保證是有效的輸入,因此子類的實現應該防御性地編程。在Django更改1.10:在舊版本中,此方法是名為
_format_value()
的私有API。 舊的名稱將工作,直到Django 2.0。
-
get_context
(name,value,attrs)[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
(name, value, attrs=None, renderer=None)[source] -
使用給定的渲染器將小部件渲染為HTML。 如果
renderer
是None
,則使用FORM_RENDERER
設置中的渲染器。在Django更改1.11:添加了
renderer
參數。 支持不接受的子類將在Django 2.1中被刪除。
-
value_from_datadict
(data,files,name)[source] -
根據一個字典和該Widget 的名稱,返回該Widget 的值。
files
可能包含來自request.FILES
的數據。 如果沒有提供value,則返回None
。 在處理表單數據的過程中,value_from_datadict
可能調用多次,所以如果你自定義並添加額外的耗時處理時,你應該自己實現一些緩存機制。
-
value_omitted_from_data
(數據,文件,名稱)[source] -
Django中的新功能1.10.2。
給定
data
和files
字典和此小部件的名稱,返回是否有數據或文件的小部件。該方法的結果會影響模型窗體falls back to its default。
特殊情況是
CheckboxInput
,CheckboxSelectMultiple
和SelectMultiple
,它始終返回False
,因為未選中的復選框並未選擇&lt; select multiple&gt;
不會出現在HTML表單提交的數據中,因此用戶是否提交了值是未知的。
-
use_required_attribute
(initial)[source] -
Django中的新功能1.10.1。
給定一個表單域的
initial
值,返回是否可以使用required
表單使用此方法與Field.required
和Form.use_required_attribute
一起確定是否顯示每個字段的required
屬性。默認情況下,為隱藏的小部件返回
False
,否則返回True
。 特殊情況是ClearableFileInput
,當initial
未設置時返回False
,CheckboxSelectMultiple
,它始終返回False
,因為瀏覽器驗證將需要檢查所有復選框,而不是至少一個。在與瀏覽器驗證不兼容的自定義小部件中覆蓋此方法。 例如,由隱藏的
textarea
元素支持的WSYSIWG文本編輯器小部件可能希望始終返回False
,以避免在隱藏字段上進行瀏覽器驗證。
MultiWidget
class MultiWidget
(widgets,attrs = 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_context
(name,value,attrs)[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。 注意這個方法如何處理value
為None
的情況。
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 text、various checkboxes and selectors、uploading files和handling of multi-valued input。
TextInput
-
class
TextInput
[source] -
input_type
:'text'
template_name
:'django/forms/widgets/text.html'
- 呈現為:
<input type =“text” ...>
NumberInput
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
)。
DateInput
-
class
DateInput
[source] -
input_type
:'text'
template_name
:'django/forms/widgets/date.html'
- 呈現為:
<input type =“text” ...>
接收的參數與
TextInput
相同,但是帶有一些可選的參數:-
格式
-
字段的初始值應該顯示的格式。
如果沒有提供
format
參數,默認的格式為參考Format localization在DATE_INPUT_FORMATS
中找到的第一個格式。
DateTimeInput
-
class
DateTimeInput
[source] -
input_type
:'text'
template_name
:'django/forms/widgets/datetime.html'
- 呈現為:
<input type =“text” ...>
接收的參數與
TextInput
相同,但是帶有一些可選的參數:-
格式
-
字段的初始值應該顯示的格式。
如果沒有提供
format
參數,默認的格式為參考Format localization在DATETIME_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 localization在TIME_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 Select
widget, 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
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
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_label
、choice_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” ...>
清除字段的值,如果該字段不是必需的,並具有初始數據。
復合小部件
SplitDateTimeWidget
-
class
SplitDateTimeWidget
[source] -
template_name
:'django/forms/widgets/splitdatetime.html'
封裝(使用
MultiWidget
)兩個Widget:DateInput
用於日期,TimeInput
用於時間。 必須與SplitDateTimeField
而不是DateTimeField
一起使用。SplitDateTimeWidget
有兩個可選的屬性:-
date_format
-
time_format
SelectDateWidget
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
可以是一個string
、empty_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
主要有兩步:
與普通的表單驗證類型類似,模型表單的驗證在調用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 validated,form.errors
調用將通過檢查save()
來進行驗證。 如果表單中的數據不合法,將引發True
—— 例如,如果form.errors
為ValueError
。
如果表單數據中沒有可選字段,則生成的模型實例使用模型字段default
(如果有)。 This behavior doesn’t apply to fields that use CheckboxInput
, CheckboxSelectMultiple
, 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並且希望使用這些小部件之一的字段的缺省回退行為,請使用自定義表單字段或小部件。
較舊的版本沒有CheckboxInput
的例外,這意味着如果這是模型字段默認值,則未選中的復選框將接收到True
的值。
此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)。
然而,有兩種簡單的方法保證你不會出現這些安全問題:
-
設置
'__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個字段name
、birth_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_messages
、help_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.fields
或Meta.exclude
列表,您還可以將父類的Meta
子類子類化:
>>> class RestrictedArticleForm(EnhancedArticleForm): ... class Meta(ArticleForm.Meta): ... exclude = ('body',)
上例從父類ArticleForm.Meta
繼承后增加了額外的方法,並修改了 EnhancedArticleForm
排除了一個字段
當然,有一些注意事項
-
應用正常的Python名稱解析規則。 如果你有多個基類聲明一個
Meta
內部類,只會使用第一個。 這意味着孩子的Meta
(如果存在),否則第一個父母的Meta
等。 -
它可以同時繼承
Form
和ModelForm
,但是,必須確保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()})
表單包含的字段可以用 Meta
或ModelForm
關鍵字參數說明,或者用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_together
的unique_for_date|month|year
方法將驗證formset中沒有項目違反唯一約束(unique
,ModelFormSet
或clean()
)。 如果要覆蓋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})
請注意,我們在此示例中的GET
和POST
中傳遞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,你需要渲染主鍵字段。 例如,如果您要渲染模型的age
和name
字段:
<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
. 例如,如果initial
和extra=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_FORMS
, form-TOTAL_FORMS
and form-INITIAL_FORMS
) 他們是必要的,且必須位於表單集數據的最上方 這些必須傳遞給ManagementForm
. ManagementFormThis 用於管理表單集中的表單. 如果你不提供這些數據,將會觸發異常
>>> 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_count
和initial_form_count
initial_form_count
有一些與total_form_count
,BaseFormSet
和ManagementForm
密切相關的方法。
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_delete
和can_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
,並將相關信息傳遞給ValidationError
。See below中引發ValidationError
的最佳實踐。 如果沒有引發ValidationError
,這些方法應該返回驗證后的(規整化的)數據的Python 對象。
大部分應該可以使用validators 完成,它們可以很容易地重用。 Validators 是簡單的函數(或可調用對象),它們接收一個參數並對非法的輸入拋出ValidationError
。 Validators 在字段的to_python
和validate
方法調用之后運行。
表單的驗證分為幾個步驟,可以自定義或覆蓋:
-
Field
上的to_python()
方法是每次驗證的第一步。 它強制該值為正確的數據類型,並引發ValidationError
,如果這是不可能的。 這個方法從Widget 接收原始的值並返回轉換后的值。 例如,一個FloatField
將數據轉換成一個Pythonfloat
或者提起一個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
設置中的兩個選項:
'django.contrib.auth'
包含認證框架的核心和默認的模型。'django.contrib.contenttypes'
是Django內容類型系統,它允許權限與你創建的模型關聯。
和MIDDLEWARE
設置中的這些條目:
SessionMiddleware
跨請求管理sessions。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還提供views和forms用於允許user修改他們自己密碼。
更改用戶密碼將會注銷所有會話。 詳細信息請參閱Session invalidation on password change。
認證用戶
-
authenticate
(request=None, **credentials)[source] -
使用
authenticate()
來驗證一組憑據。 它以credentials為關鍵字參數,默認為username
和password
,根據每個認證的后端進行檢查,如果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
對象具有兩個多對多的字段:groups
和user_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, )
權限的緩存
在第一次獲取權限用於檢查后,模型的后端
將在該用戶對象上緩存這些權限。 這對於常見的請求-響應周期通常沒問題,因為通常在添加權限后不會立即檢查權限(例如在管理后台中)。 如果你要添加權限並立即檢查它們,例如在測試中或視圖中,最簡單的解決方案是從數據庫重新獲取用戶。 像這樣:
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請求中的認證
它們在每個請求上提供一個request.user
屬性,表示當前的用戶。 如果當前的用戶沒有登入,該屬性將設置成AnonymousUser
的一個實例,否則它將是User
的實例。
你可以使用is_authenticated
將它們區分開,如下所示:
if request.user.is_authenticated: # Do something for authenticated users. ... else: # Do something for anonymous users. ...
如何登錄用戶
如果你有一個認證了的用戶,你想把它附帶到當前的會話中 - 這可以通過login()
函數完成。
-
login
(request, user, backend=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和用於身份驗證的后端保存在用戶的會話中。 這允許相同的身份驗證后端在將來的請求中獲取用戶的詳細信息。 要保存在會話中的認證后端選擇如下:
- 使用可選的
backend
參數的值(如果提供)。 - 使用
user.backend
屬性的值(如果存在)。 這允許配對authenticate()
和login()
:authenticate()
設置user.backend
屬性用戶對象返回。 - 如果只有一個,請使用
AUTHENTICATION_BACKENDS
中的backend
。 - 否則,引發異常。
在情況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_func, login_url=None, redirect_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
(perm, login_url=None, raise_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認證系統進行檢查。
指定認證后端
在底層,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
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
可重用的應用不應實現自定義用戶模型。 一個項目可能使用多個應用,實現自定義用戶模型的兩個可重用應用不能一起使用。 如果需要在應用中存儲用戶的信息,請使用ForeignKey
或OneToOneField
到settings.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參考
字段
-
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
。 如果要允許非活動用戶登錄,您可以使用AllowAllUsersModelBackend
或AllowAllUsersRemoteUserBackend
。 在這種情況下,您還需要自定義LoginView
使用的AuthenticationForm
,因為它拒絕了非活動用戶。 請注意,諸如has_perm()
等權限檢查方法,Django管理員中的身份驗證全部返回為非活動用戶的False
。在Django更改1.10:在舊版本中,
ModelBackend
和RemoteUserBackend
允許非活動用戶進行身份驗證。
-
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
(perm, obj=None) -
如果用戶具有指定的權限,則返回
True
,其中perm的格式為"<app label>.<permission codename>"
。 (請參閱有關permissions)。 如果用戶沒有激活,這個方法將永遠返回False
。如果傳入
obj
,此方法將不會檢查模型的權限,而是檢查此特定對象。
-
has_perms
(perm_list, obj=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
(subject, message, from_email=None, **kwargs) -
發生郵件給這個用戶。 如果
None
為from_email
,Django 將使用DEFAULT_FROM_EMAIL
。 任何**kwargs
都將傳遞給底層的send_mail()
調用。
-
管理器方法
-
class
models.
UserManager
-
User
模型有一個自定義的管理器,它具有以下輔助方法(除了BaseUserManager
提供的方法之外):-
create_user
(username, email=None, password=None, **extra_fields) -
創建、保存並返回一個
User
。username
和password
設置為給出的值。email
的域名部分將自動轉換成小寫,返回的User
對象將設置is_active
為True
。如果沒有提供password,將調用
set_unusable_password()
。The
extra_fields
keyword arguments are passed through to theUser
’s__init__
method to allow setting arbitrary fields on a custom user model.參見Creating users 中的示例用法。
-
create_superuser
(username, email, password, **extra_fields) -
與
create_user()
相同,但是設置is_staff
和is_superuser
為True
。
-
AnonymousUser
對象
-
class
models.
AnonymousUser
-
django.contrib.auth.models.AnonymousUser
類實現了django.contrib.auth.models.User
接口,但具有下面幾個不同點:- id 永遠為
None
。 username
永遠為空字符串。get_username()
永遠返回空字符串。is_anonymous
是True
而不是False
。is_authenticated
是False
,而不是True
。is_staff
和is_superuser
永遠為False
。is_active
永遠為False
。groups
和user_permissions
永遠為空。set_password()
、check_password()
、save()
和delete()
引發NotImplementedError
。
- id 永遠為
在實際應用中,你自己可能不需要使用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()
允許一個對象作為特定權限參數來傳遞, 如果條件是 ifobj is not None
. 后端除了返回一個空的permissions 外,並不會去完成他們。-
authenticate
(request, username=None, password=None, **kwargs) -
通過調用
User.check_password
驗證password
和username
。 如果kwargs
沒有提供,它會使用CustomUser.USERNAME_FIELD
關鍵字從username
中獲取username。 返回一個認證過的User 或None
。request
is anHttpRequest
and may beNone
if it wasn’t provided toauthenticate()
(which passes it on to the backend).在Django更改1.11:添加了
request
參數。
-
get_user_permissions
(user_obj, obj=None) -
返回
user_obj
具有的自己用戶權限的權限字符串集合。 如果is_anonymous
或is_active
是False
,則返回空集。
-
get_group_permissions
(user_obj, obj=None) -
返回
user_obj
從其所屬組的權限中獲取的權限字符集。 如果is_anonymous
或is_active
是False
,則返回空集。
-
get_all_permissions
(user_obj, obj=None) -
返回
user_obj
的權限字符串集,包括用戶權限和組權限。 如果is_anonymous
或is_active
是False
,則返回空集。
-
has_perm
(user_obj, perm, obj=None) -
使用
get_all_permissions()
檢查user_obj
是否具有權限字符串perm
。 如果用戶不是is_active
,則返回False
。
-
has_module_perms
(user_obj, app_label) -
返回
user_obj
是否對應用app_label
有任何權限。
-
user_can_authenticate
() -
Django中的新功能1.10。
返回是否允許用戶進行身份驗證。 To match the behavior of
AuthenticationForm
whichprohibits inactive users from logging in
, this method returnsFalse
for users withis_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
-
True
或False
。 確定是否創建用戶對象(如果尚未在數據庫中)默認為True
。
-
RemoteUserBackend.
authenticate
(request, remote_user) -
作為
remote_user
傳遞的用戶名被認為是可信的。 此方法只需返回具有給定用戶名的用戶對象,如果create_unknown_user
為True
則創建新的用戶對象。如果
create_unknown_user
是User
,並且在數據庫中找不到具有給定用戶名的None
對象,則返回False
。request
is anHttpRequest
and may beNone
if it wasn’t provided toauthenticate()
(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
創建的默認項目模版中,管理后台已啟用。
下面的一些要求作為參考:
- 添加
'django.contrib.admin'
到INSTALLED_APPS
設置中. - admin有四個依賴 —
django.contrib.auth
、django.contrib.contenttypes
、django.contrib.messages
和django.contrib.sessions
。 如果這些應用沒有在INSTALLED_APPS
列表中, 那你要添加它們。 - 添加
django.contrib.auth.context_processors.auth
和django.contrib.messages.context_processors.messages
到TEMPLATES
中定義的DjangoTemplates
后端的'context_processors'
選項中,並添加django.contrib.auth.middleware.AuthenticationMiddleware
和django.contrib.messages.middleware.MessageMiddleware
到MIDDLEWARE
中。 默認情況下它們都已經添加,除非你手動調整過設置,否則不需要自己添加。 - 確定你的應用中的哪些模型在管理后台界面中應該可以編輯。
- 給上面的每個模型創建一個
ModelAdmin
類,封裝模型自定義的管理后台功能和選項。 - 實例化
AdminSite
並且告訴它你的每一個模型和ModelAdmin
類。 - 將
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
(*models, site=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_date
、title
和name
,上述聲明產生的表單將包含完全相同的字段。
-
ModelAdmin.
fields
-
使用
fields
選項可以在“添加”和“更改”頁面上的表單中進行簡單的布局更改,例如僅顯示可用字段的一個子集,修改其順序或將其分組為行。 例如,可以定義一個簡單的管理表單的版本使用django.contrib.flatpages.models.FlatPage
模塊像下面這樣:
class FlatPageAdmin(admin.ModelAdmin): fields = ('url', 'title', 'content')
在上面的例子中, 只有字段content
, title
和 url
將會在表單中順序的顯示. fields
能夠包含在 ModelAdmin.readonly_fields
中定義的作為只讀顯示的值
對於更復雜的布局需求,請參閱fieldsets
選項。
不同於 list_display
,fields
選項 只包含model中的字段名或者通過form
指定的表單。 只有當它們列在readonly_fields
中,它才能包含callables
要在同一行顯示多個字段, 就把那些字段打包在一個元組里。 在此示例中,url
和title
字段將顯示在同一行上,content
字段將在其自己的行下顯示:
class FlatPageAdmin(admin.ModelAdmin): fields = (('url', 'title'), 'content')
-
如果
editable=True
和fieldsets
選項都不存在, 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=True
和fields
選項都不存在, 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
和wide
. Fieldsets 使用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_name
和last_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
應該是一個列表或元組,其每個元素應該是下面類型中的一種:
-
字段名稱,其指定的字段應該是
ManyToManyField
、IntegerField
、ForeignKey
、DateField
、CharField
、BooleanField
或DateTimeField
,例如︰
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
屬性並重寫lookups
和queryset
方法,例如︰
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
不能接受DateTimeField
,ForeignKey
,OneToOneField
, 和ManyToManyField
字段.
-
ModelAdmin.
preserve_filters T0>
-
管理員現在在創建,編輯或刪除對象后保留列表視圖中的過濾器。 您可以將此屬性設置為
False
,以恢復之前清除過濾器的行為。
-
ModelAdmin.
radio_fields
-
默認情況下,Django的管理員為
ForeignKey
或者有choices
集合的字段使用一個下拉菜單(<select>). 如果radio_fields
中存在字段,Django將使用單選按鈕接口。 假設group
是Person
模型上的ForeignKey
class PersonAdmin(admin.ModelAdmin): radio_fields = {"group": admin.VERTICAL}
-
您可以選擇使用
django.contrib.admin
模塊中的VERTICAL
或HORIZONTAL
。除非是
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",)
如果該字段是一個ForeignKey
,Input
raw_id_fields
Widget 應該包含一個外鍵,或者如果字段是一個ManyToManyField
則應該是一個逗號分隔的值的列表。 raw_id_fields
Widget 在字段旁邊顯示一個放大鏡按鈕,允許用戶搜索並選擇一個值︰
ModelAdmin.
readonly_fields
默認情況下,管理后台將所有字段顯示為可編輯。 此選項中的任何字段(應為list
或tuple
)將按原樣顯示其數據,不可編輯;它們也被排除在用於創建和編輯的ModelForm
之外。 請注意,指定ModelAdmin.fields
或ModelAdmin.fieldsets
時,只讀字段必須包含進去才能顯示(否則將被忽略)。
如果在未通過ModelAdmin.fields
或ModelAdmin.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_as
是True
,“保存並添加另一個”將被替換為創建新對象(使用新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 的"跟隨"符號進行ForeignKey
或ManyToManyField
上的關聯查找:
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
(request, obj, form, change)[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
(request, obj)[source] -
delete_model
方法給出了HttpRequest
和模型實例。 覆蓋此方法允許進行前或后刪除操作。 使用Model.delete()
調用super().delete_model()
來刪除對象。
-
ModelAdmin.
save_formset
(request, form, formset, change)[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
作為參數,並且預期返回list
或tuple
,以便類似於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
(request, queryset, search_term)[source]
get_search_results
方法將顯示的對象列表修改為與提供的搜索項匹配的對象列表。 它接受請求,應用當前過濾器的查詢集以及用戶提供的搜索項。 它返回一個包含被修改以實現搜索的查詢集的元組,以及一個指示結果是否可能包含重復項的布爾值。
默認實現搜索在ModelAdmin.search_fields
中命名的字段。
此方法可以用您自己的自定義搜索方法覆蓋。 例如,您可能希望通過整數字段搜索,或使用外部工具(如Solr或Haystack)。您必須確定通過搜索方法實現的查詢集更改是否可能在結果中引入重復項,並在返回值的第二個元素中返回True
。
例如,要通過name
和age
搜索,您可以使用:
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
(request, obj=None) -
list
方法在添加表單上給予tuple
和obj
(或HttpRequest
),希望返回將以只讀形式顯示的字段名稱的get_readonly_fields
或None
,如上面在ModelAdmin.readonly_fields
部分中所述。
-
ModelAdmin.
get_prepopulated_fields
(request, obj=None) -
dictionary
方法在添加表單上給予obj
和HttpRequest
(或get_prepopulated_fields
),預期返回None
,如上面在ModelAdmin.prepopulated_fields
部分中所述。
-
ModelAdmin.
get_list_display
(request)[source] -
list
方法被賦予HttpRequest
,並且希望返回字段名稱的get_list_display
或tuple
顯示在如上所述的ModelAdmin.list_display
部分中的changelist視圖上。
-
ModelAdmin.
get_list_display_links
(request, list_display)[source] -
The
get_list_display_links
method is given theHttpRequest
and thelist
ortuple
returned byModelAdmin.get_list_display()
. 預期將返回更改列表上將鏈接到更改視圖的字段名稱的tuple
或list
或None
,如上所述在ModelAdmin.list_display_links
部分中。
-
ModelAdmin.
get_exclude
(request, obj=None) -
Django中的新功能1.11。
The
get_exclude
method is given theHttpRequest
and theobj
being edited (orNone
on an add form) and is expected to return a list of fields, as described inModelAdmin.exclude
.
-
ModelAdmin.
get_fields
(request, obj=None)[source] -
obj
方法被賦予HttpRequest
和get_fields
被編輯(或在添加表單上None
),希望返回字段列表,如上面在ModelAdmin.fields
部分中所述。
-
ModelAdmin.
get_fieldsets
(request, obj=None) -
<fieldset>
方法是在添加表單上給予obj
和HttpRequest
(或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 theHttpRequest
and should return a boolean or list asModelAdmin.list_select_related
does.
-
ModelAdmin.
get_search_fields
(request)[source] -
HttpRequest
方法被賦予get_search_fields
,並且期望返回與search_fields
屬性相同類型的序列類型。
-
ModelAdmin.
get_inline_instances
(request, obj=None)[source] -
list
方法在添加表單上給予tuple
和obj
(或HttpRequest
),預期會返回get_inline_instances
或None
的InlineModelAdmin
對象,如下面的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
(request, obj=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
(request, obj=None)[source] -
產量(
FormSet
,InlineModelAdmin
)對用於管理添加和更改視圖。例如,如果您只想在更改視圖中顯示特定的內聯,則可以覆蓋
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_field, request, **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_field, request, **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_field, request, **kwargs)
與formfield_for_choice_field
和formfield_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
(lookup, value) -
可以從URL查詢字符串中的查找過濾更改列表頁面中的對象。 例如,這是
list_filter
的工作原理。 查詢與QuerySet.filter()
(例如user__email=user@example.com
)中使用的查找類似。 由於查詢字符串中的查詢可以由用戶操縱,因此必須對其進行清理,以防止未經授權的數據暴露。給定了
lookup_allowed()
方法,從查詢字符串(例如'user__email'
)和相應的值(例如'user@example.com'
),並返回一個布爾值,表示是否允許使用參數過濾changelist的QuerySet
。 如果lookup_allowed()
返回False
,則會引發DisallowedModelAdminLookup
(SuspiciousOperation
的子類)。默認情況下,
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
(request, obj=None) -
如果允許編輯obj,則應返回
True
,否則返回False
。 如果obj為False
,則應返回True
或None
以指示是否允許對此類對象進行編輯(例如,False
將被解釋為意味着當前用戶不允許編輯此類型的任何對象)。
-
ModelAdmin.
has_delete_permission
(request, obj=None) -
如果允許刪除obj,則應返回
True
,否則返回False
。 如果obj是None
,應該返回True
或False
以指示是否允許刪除此類型的對象(例如,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
(request, message, level=messages.INFO, extra_tags='', fail_silently=False)[source] -
使用
django.contrib.messages
向用戶發送消息。 參見custom ModelAdmin example。關鍵字參數運行你修改消息的級別、添加CSS 標簽,如果
contrib.messages
框架沒有安裝則默默的失敗。 關鍵字參數與django.contrib.messages.add_message()
的參數相匹配,更多細節請參見這個函數的文檔。 有一個不同點是級別除了使用整數/常數傳遞之外還以使用字符串。
-
ModelAdmin.
get_paginator
(request, queryset, per_page, orphans=0, allow_empty_first_page=True)[source] -
返回要用於此視圖的分頁器的實例。 默認情況下,實例化
paginator
的實例。
-
ModelAdmin.
response_add
(request, obj, post_url_continue=None)[source] -
為
add_view()
階段確定HttpResponse
。response_add
在管理表單提交后,在對象和所有相關實例已創建並保存之后調用。 您可以覆蓋它以在對象創建后更改默認行為。
-
ModelAdmin.
response_change
(request, obj)[source] -
確定
change_view()
階段的HttpResponse
。response_change
在Admin 表單提交並保存該對象和所有相關的實例之后調用。 您可以重寫它來更改對象修改之后的默認行為。
-
ModelAdmin.
response_delete
(request, obj_display, obj_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
(request, form_url='', extra_context=None)[source] -
Django視圖為模型實例添加頁面。 見下面的注釋。
-
ModelAdmin.
change_view
(request, object_id, form_url='', extra_context=None)[source] -
模型實例編輯頁面的Django視圖。 見下面的注釋。
-
ModelAdmin.
changelist_view
(request, extra_context=None)[source] -
Django視圖為模型實例更改列表/操作頁面。 見下面的注釋。
-
ModelAdmin.
delete_view
(request, object_id, extra_context=None)[source] -
模型實例刪除確認頁面的Django 視圖。 見下面的注釋。
-
ModelAdmin.
history_view
(request, object_id, extra_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 app將STATIC_URL
(或MEDIA_URL
如果STATIC_URL
為None
資產路徑。 相同的規則適用於表單上的regular asset definitions on forms。
jQuery
Django管理JavaScript使用jQuery庫。
為了避免與用戶提供的腳本或庫沖突,Django的jQuery(版本2.2.3)命名為django.jQuery
。 如果您想在自己的管理JavaScript中使用jQuery而不包含第二個副本,則可以使用更改列表上的django.jQuery
對象和添加/編輯視圖。
嵌入式jQuery從2.1.4升級到2.2.3。
默認情況下,ModelAdmin
類需要jQuery,因此除非有特定需要,否則不需要向您的ModelAdmin
的媒體資源列表添加jQuery。 例如,如果您需要將jQuery庫放在全局命名空間中(例如使用第三方jQuery插件時)或者如果您需要更新的jQuery版本,則必須包含自己的副本。
Django提供了jQuery的未壓縮和“縮小”版本,分別是jquery.js
和jquery.min.js
。
ModelAdmin
和InlineModelAdmin
具有media
屬性,可返回存儲到JavaScript文件的路徑的Media
對象列表形式和/或格式。 如果DEBUG
是True
,它將返回各種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
BaseModelAdmin
與ModelAdmin
具有許多相同的功能,並添加了一些自己的功能(共享功能實際上是在InlineModelAdmin
超類中定義的)。 共享功能包括:
form
fieldets
fields
formfield_overrides
exclude
filter_horizontal
filter_vertical
ordering
prepopulated_fields
get_queryset()
radio_fields
readonly_fields
raw_id_fields
formfield_for_choice_field()
formfield_for_foreignkey()
formfield_for_manytomany()
has_add_permission()
has_change_permission()
has_delete_permission()
has_module_permission()
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
。
-
InlineModelAdmin。
show_change_link T0>
-
指定是否可以在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
屬性自定義外觀。
使用多對多中介模型
當您使用ManyToManyField
的through
參數指定中介模型時,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
類可用的任何選項進行自定義。
現在為Person
和Group
模型創建管理視圖:
class PersonAdmin(admin.ModelAdmin): inlines = (MembershipInline,) class GroupAdmin(admin.ModelAdmin): inlines = (MembershipInline,)
最后,向管理網站注冊您的Person
和Group
模型:
admin.site.register(Person, PersonAdmin)
admin.site.register(Group, GroupAdmin)
現在,您的管理網站已設置為從Group
或Person
詳細信息頁面內聯編輯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
覆蓋popup_response.html
模板的功能已添加。
對於那些不能以這種方式重寫的模板,你可能仍然為您的整個項目重寫它們。 只需要將新版本放在你的templates/admin
目錄下。這對於要創建自定義的404 和500 頁面特別有用。
根和登錄模板
如果你想要更改主頁、 登錄或登出頁面的模板,你最后創建你自己的AdminSite
實例(見下文),並更改AdminSite.index_template
、AdminSite.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_header
:AdminSite.site_header
-
site_title
:AdminSite.site_title
-
site_url
:AdminSite.site_url
-
has_permission
:AdminSite.has_permission()
-
available_apps
:從當前用戶可用的application registry中的應用程序列表。 列表中的每個條目都是表示具有以下密鑰的應用程序的dict:app_label
:應用程序標簽app_url
:管理員中的應用程序索引的URLhas_module_perms
:一個布爾值,表示當前用戶是否允許顯示和訪問模塊的索引頁面models
:應用程序中可用的模型列表
每個模型都是具有以下鍵的dict:
object_name
:模型的類名name
:復數名稱的模型perms
:adict
trackingadd
,change
和delete
permissionsadmin_url
:admin changelist模型的URLadd_url
:添加新模型實例的admin URL
-
-
AdminSite.
has_permission
(request)[source] -
對於給定的
True
,如果用戶有權查看Admin 網站中的至少一個頁面,則返回HttpRequest
。 默認要求User.is_active
和User.is_staff
都為True
。
-
AdminSite.
register
(model_or_iterable, admin_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
-
記錄的動作類型:
ADDITION
,CHANGE
,DELETION
。例如,要獲取通過管理員完成的所有添加的列表:
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_id ,object_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_label
和opts
屬性的對象,通常由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
- 表示當前請求的
HttpRequest
- 含有用戶所選的對象集合的
QuerySet
我們用於發布這些文章的函數並不需要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_action
(action,name = 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將所有的消息(包括ERROR
和CRITICAL
消息)記錄到一個文件中用於以后進行分析。
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.interesting
logger的父級。
層級為何如此重要? 因為可以設置logger傳播它們的logging調用給它們的上一級。 利用這種方式,你可以在根logger上定義一系列的handler,並捕獲子logger中的所有logging調用。 在project
這一級定義的handler將會捕獲在project.interesting
和project.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是永遠可用的。
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 whenDEBUG
isTrue
.
-
定義兩個處理程序:
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
級消息(或更高級別)將被打印到控制台;ERROR
和CRITICAL
消息也將通過電子郵件輸出。
自定義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_list, per_page, orphans=0, allow_empty_first_page=True)[source]
必選參數
-
object_list
-
列表、元組、
QuerySet
或具有count()
或__len__()
方法的其它可切片的對象。 為了得到一致的分頁,QuerySet
應該排好序,例如使用order_by()
子句或模型上的默認ordering
。
-
per_page
-
每個頁面上包含的元素的最大數目,不包含孤兒(參見下文的
orphans
參數)。
可選參數
-
orphans
-
當你不想要最后一個頁面的記錄很少時,請使用此選項。 如果最后一頁通常會有一些小於或等於
orphans
的項目,那么這些項目將被添加到上一頁(成為最后一頁),而不是讓它們自己單獨留在一個頁面。 例如有23個記錄,per_page=10
且orphans=3
時,將有兩個頁面;第一頁有10個記錄,第二頁(最后一頁)有13個記錄。orphans
默認為零,這意味着頁面從不組合,最后一頁可能有一個項目。 -
allow_empty_first_page
-
第一頁是否允許為空。 如果為
False
且object_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_list, number, paginator)[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
。
會話
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__
(key, value) -
例如:
request.session['fav_color'] = 'blue'
-
__delitem__
(key) -
例如:
del request.session['fav_color']
。 如果給出的KeyError
在會話中不存在,將拋出key
。
-
__contains__
(key) -
例如:
'fav_color' in request.session
-
get
(key, default=None) -
例如:
fav_color = request.session.get('fav_color', 'red')
-
pop
(key, default=__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
是一個datetime
或timedelta
對象,會話將在這個指定的日期/時間過期。 注意datetime
和timedelta
值只有在你使用PickleSerializer
時才可序列化。 - 如果
value
為0
,那么會話的Cookie將在用戶的瀏覽器關閉時過期。 - 如果
value
為None
,那么會話轉向使用全局的會話過期策略。
過期的計算不考慮讀取會話的操作。 會話的過期從會話上次修改的時間開始計算。
- 如果
-
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.session
的flush()
方法。 我們使用這個例子來演示如何利用會話對象來工作,而不是一個完整的logout()
實現。
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 讓你可以控制會話的行為:
SESSION_CACHE_ALIAS
SESSION_COOKIE_AGE
SESSION_COOKIE_DOMAIN
SESSION_COOKIE_HTTPONLY
SESSION_COOKIE_NAME
SESSION_COOKIE_PATH
SESSION_COOKIE_SECURE
SESSION_ENGINE
SESSION_EXPIRE_AT_BROWSER_CLOSE
SESSION_FILE_PATH
SESSION_SAVE_EVERY_REQUEST
SESSION_SERIALIZER
會話安全性
一個站點下的子域名能夠在客戶端為整個域名設置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特定操作的通知。 它們包含一些有用的通知:
-
django.db.models.signals.pre_save
&django.db.models.signals.post_save
在模型
save()
方法調用之前或之后發送。 -
django.db.models.signals.pre_delete
&django.db.models.signals.post_delete
-
django.db.models.signals.m2m_changed
模型上的
ManyToManyField
修改時發送。 -
django.core.signals.request_started
&django.core.signals.request_finished
Django開始或完成HTTP請求時發送。
監聽信號
要接收信號,請使用Signal.connect()
方法注冊receiver函數。 receiver函數在信號發送時調用。
-
Signal.
connect
(receiver, sender=None, weak=True, dispatch_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_robust
(sender,** 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=None, sender=None, dispatch_uid=None)[source]
調用Signal.disconnect()
來斷開信號的接收器。 和Signal.connect()
中描述的參數相同。 如果接收器成功斷開,返回 True
,否則返回False
。
receiver
參數表示要斷開的已注冊receiver。 如果使用dispatch_uid
標識receiver,它可能為None
。
自1.9版以來已棄用: weak
參數已被棄用,因為它不起作用。 它將在Django 2.0中被刪除。