返回顶部
扩大
缩小

Zhang_derek

13.Django1.11.6文档

第一步 入门

检查版本

python -m django --version

创建第一个项目

django-admin startproject mysite

运行

 python manage.py runserver

更改端口

python manage.py runserver 8080

更改IP

python manage.py runserver 0:8000

1.创建app

创建投票应用

python manage.py startapp polls

polls/views.py

from django.shortcuts import render

from django.http import HttpResponse


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

在polls里面创建一个urls.py文件,代码如下

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

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

在mysite/urls.py中添加include

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

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

访问:http://127.0.0.1:8000/polls/,就可以看到信息“Hello, world. You're at the polls index.”

2.创建模型

设置为Mysql数据库

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'django',        #数据库名字
        'USER': 'root',          #账号
        'PASSWORD': '123456',      #密码
        'HOST': '127.0.0.1',    #IP
        'PORT': '3306',                   #端口
    }
}

polls/init.py

import pymysql
pymysql.install_as_MySQLdb()

polls/models.py

from django.db import models


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

    def __str__(self):
        return self.question_text


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

然后执行下面的命令

python manage.py makemigrations      创建迁移文件

python manage.py migrate             更新到数据库中。

 3.后台管理

(1)创建管理员用户,设置用户名,邮箱,密码

python manage.py createsuperuser

登录:http://127.0.0.1:8000/admin/

(2)把polls应用添加到后台管理站点

编辑polls/admin.py

from django.contrib import admin

from polls.models import Question

admin.site.register(Question)

刷新就可以看到polls

4.视图

(1)polls/urls.py

from django.conf.urls import url

from . import views

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

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

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

(3)polls/views.py

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


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

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

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

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

(4)抛出404异常

from django.shortcuts import render,HttpResponse,Http404

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

detail.html

{{ question }}

(5)快捷方式  get_object_or_404

 更改detail()视图如下

from django.shortcuts import get_object_or_404

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

get_object_or_404() 函数将一个Django模型作为它的第一个参数,任意数量的关键字参数作为它的第二个参数,它会将这些关键字参数传递给模型管理器中的get() 函数。 如果对象不存在,它就引发一个 Http404异常。

 还有一个get_list_or_404()函数,它的工作方式类似get_object_or_404() —— 差别在于它使用filter()而不是get()如果列表为空则引发Http404

 (6)使用模板系统

编辑detail.html

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

方法调用发生在{% for %}循环中:Choice被解释为Python的代码question.choice_set.all,它返回一个由question.choice_set.all()对象组成的可迭代对象,并将其用于{% for %}标签。

最终:

http://127.0.0.1:8000/polls/   访问index页面,显示question列表

点任意一个击question,然后跳转到detail页面

(7)移除模板中硬编码的URL

前面在index.html中跳转到detail页面的编码

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

这种硬编码、紧耦合的方法有一个问题,就是如果我们想在拥有许多模板文件的项目中修改URLs,那将会变得很有挑战性。 然而,因为你在polls.urls模块的url()函数中定义了name 参数,你可以通过使用{% url %}模板标签来移除对你的URL配置中定义的特定的URL的依赖:修改如下

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

如果你想把polls应用中detail视图的URL改成其它样子比如polls/specifics/12/,就可以不必在该模板(或者多个模板)中修改它,只需要修改polls/urls.py

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

(8)命名空间URL名称

在真实的Django项目中,可能会有五个、十个、二十个或者更多的应用。 Django如何区分它们URL的名字呢?

答案是添加命名空间到你的URLconf。 polls/urls.py文件中,继续添加app_name来设置应用程序命名空间:

添加 app_name='polls'

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

app_name = 'polls'

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

然后修改polls/index.html

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

改为

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

 5.表单

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

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

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

<form action="{% url 'polls:vote' question.id %}" method="post">
{% csrf_token %}
{% for choice in question.choice_set.all %}
    <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" />
    <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br />
{% endfor %}
<input type="submit" value="Vote" />
</form>

简要说明:

在detail网页模板中,我们为Question对应的每个Choice都添加了一个单选按钮用于选择。每个单选按钮的value属性是对应的各个Choice的ID。 每个单选按钮的name"choice"。 这意味着,当有人选择一个单选按钮并提交表单提交时,它将发送一个POST数据choice=#,其中# 为选择的Choice的ID

由于我们创建一个POST表单(它具有修改数据的作用),所以我们需要小心跨站点请求伪造。简而言之,所有针对内部URL的POST表单都应该使用{% csrf_token %}模板标签。

(2)vote.py处理投票

现在,我们来创建一个处理提交的数据的Django视图,并用它来处理

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

from .models import Choice, Question
# ...
def vote(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    try:
        selected_choice = question.choice_set.get(pk=request.POST['choice'])
    except (KeyError, Choice.DoesNotExist):
        # 重新显示该问题的表单
        return render(request, 'polls/detail.html', {
            'question': question,
            'error_message': "You didn't select a choice.",
        })
    else:
        selected_choice.votes += 1
        selected_choice.save()
        # 始终在成功处理 POST 数据后返回一个 HttpResponseRedirect ,
        # (合并上句) 这样可以防止用户点击“后退”按钮时数据被发送两次。
        # (合并至上一句)
        return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))

这个例子中,request.POST['choice']以字符串形式返回选择的Choice的ID。 request.POST 的值永远是字符串。

在增加Choice的得票数之后,代码返回一个 HttpResponseRedirect而不是常用的HttpResponse。 HttpResponseRedirect只接收一个参数:用户将要被重定向的URL

我们在HttpResponseRedirect的构造函数中使用reverse()函数。 这个函数避免了我们在视图函数中硬编码URL。 它需要我们给出我们想要跳转的视图的名字和该视图所对应的URL模式中需要给该视图提供的参数。重定向的URL将调用'results'视图来显示最终的页面。

(3)results()

当有人对Question进行投票后,vote()视图将请求重定向到Question的结果界面。

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

(4)results.html

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

<ul>
{% for choice in question.choice_set.all %}
    <li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>    #pluralize复数
{% endfor %}
</ul>

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

现在,在你的浏览器中访问/polls/1/然后为Question投票。 你应该看到一个投票结果页面,并且在你每次投票之后都会更新。 如果你提交时没有选择任何Choice,你应该看到错误信息。

 6.静态文件

(1)创建style.css   

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

添加下面代码到style.css

li a {
    color: green;
}

在index.html添加如下

{% load static %}

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

{% static %}模板标签生成静态文件的绝对URL。

再访问index界面,会发现question链接是绿色的

(2)添加背景图片

polls/static/polls/目录中创建一个 images 子目录。在这个目录中,放入一张图片background.gif-->>(polls/static/polls/images/background.gif)

添加样式

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

7.自定义管理后台的表单

(1)重新排序编辑表单上的字段

from django.contrib import admin

from polls.models import Question


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

admin.site.register(Question,QuestionAdmin)

任何时候你需要更改模型的管理选项,你将遵循此模式 — 创建一个模型管理类,然后将其作为第二个参数传递给admin.site.register()

(2)添加关联对象

在创建Question对象的同时可以直接添加一组Choice

from django.contrib import admin

from .models import Choice, Question


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


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

admin.site.register(Question, QuestionAdmin)

这告诉Django:“Choice对象在Question的管理界面中编辑。 默认提供足够3个Choice的空间。

打开“Add question”页面:

它这样工作:有三个所关联的Choice —— 由extra指定 —— 每次你回到已经存在对象的"Change"页面时,都会额外地获得三个空白Choice。

 在现有的三个Choice的底部,你会发现一个“Add another Choice”的链接。 如果你点击它,就会增加一个新的空白Choice。

 还有个小问题。 显示所有关联的Choice 对象的字段占用大量的屏幕空间。 为此,Django提供了一种显示内联相关对象的表格方式;你只需将ChoiceInline声明更改为:

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

使用 TabularInline(而不是StackedInline),这些相关联的对象显示成紧凑的、基于表格的形式:

(3)自定义管理变更清单

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

(4)使用list_filter来添加过滤器

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

(5)添加搜索功能

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

 

 8.定制管理后台的外观

很明显,每个管理页面的顶部都有“Django administration”不太合适。它可以用Django的模板系统轻松改变。 Django的管理站点是用Django自己制作出来的,它的界面代码使用的是Django自己的模板系统。

定制项目的模板

在你项目的文件夹内(包含 manage.py的目录)创建一个templates目录。 打开你的配置文件(记住是mysite/settings.py)在TEMPLATES 设置中添加一个DIRS 选项:

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

现在,在templates下创建一个名为admin的文件夹,然后从Django安装的原目录下(目录为django/contrib/admin/templates)将模板页面的源文件admin/base_site.html拷贝到这个文件夹里。

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

 如果不知道Django源文件路径,运行如下命令

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

然后,只需编辑该文件并替换{{ site_header|default:_('Django administration') }} (包括花括号)为你认为合适的自己站点的名称。 编辑完成后应该类似下面的代码片段:

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

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

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

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

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

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

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

改变之后效果:

 模型层

 1.字段选项

 null

如果为True,Django将在数据库中把空值存储为NULL。 默认为False

blank

如果为True,该字段允许为空值, 默认为False

   要注意,这与 null 不同。 null纯粹是数据库范畴,指数据库中字段内容是否允许为空,而 blank 是表单数据输入验证范畴的。 如果一个字段的blank=True,表单的验证将允              许输入一个空值。 如果字段的blank=False,该字段就是必填的。

 choices

由二项元组构成的一个可迭代对象(例如,列表或元组),用来给字段提供选择项。 如果设置了choices ,默认的表单将是一个选择框而不是标准的文本框,而且这个选择框的选项就是choices 中的选项。

这是一个关于 choices 列表的例子:

每个元组中的第一个元素是将被存储在数据库中的值。 第二个元素将由默认窗体小部件或ModelChoiceField显示。 给定一个模型实例,可以使用get_FOO_display()方法来访问选项字段的显示值。 例如:

from django.db import models

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

default

字段的默认值。 可以是一个值或者可调用对象。 如果可调用 ,每个新对象创建时它都会被调用。

help_text

表单部件额外显示的帮助内容。 即使字段不在表单中使用,它对生成文档也很有用。

primary_key

如果为True,那么这个字段就是模型的主键。

unique

如果为True, 则这个字段在整张表中必须是唯一的。

2.字段的自述名

ForeignKeyManyToManyField 和 OneToOneField 之外,每个字段类型都接受一个可选的位置参数(在第一的位置) — 字段的自述名。 如果没有给定自述名,Django 将根据字段的属性名称自动创建自述名 —— 将属性名称的下划线替换成空格。

 

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

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

在这个例子中,自述名是 "first name"

first_name = models.CharField(max_length=30)

ForeignKeyManyToManyField 和 OneToOneField 都要求第一个参数是一个模型类,所以要使用 verbose_name 关键字参数才能指定自述名:

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

习惯上,verbose_name 的首字母不用大写。 Django 在必要的时候会自动大写首字母。

 模型继承

在Django 中有3种风格的继承。

  1. 通常,你只想使用父类来持有一些信息,你不想在每个子模型中都敲一遍。 这个类永远不会单独使用,所以你要使用抽象的基类
  2. 如果你继承一个已经存在的模型且想让每个模型具有它自己的数据库表,那么应该使用多表继承
  3. 最后,如果你只是想改变一个模块Python 级别的行为,而不用修改模型的字段,你可以使用代理模型

3.抽象基类

当你想将一些共有信息放进其它一些model的时候,抽象化类是十分有用的。 你编写完基类之后,在 Meta类中设置 abstract=True这个模型就不会被用来创建任何数据表。 取而代之的是,当它被用来作为一个其他model的基类时,它的字段将被加入那些子类中。 如果抽象基类和它的子类有相同的字段名,那么将会出现error(并且Django将抛出一个exception)。

一个例子:

from django.db import models

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

    class Meta:
        abstract = True

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

Student 模型将有三个字段:nameagehome_groupCommonInfo 模型无法像一般的Django模型一样使用,因为它是一个抽象基类。 它无法生成一张数据表或者拥有一个管理器,并且不能实例化或者直接储存。

4.Meta继承

当一个抽象基类被创建的时候, Django把你在基类内部定义的 Meta 类作为一个属性使其可用。 如果子类没有声明自己的Meta类, 它将会继承父类的Meta如果子类想要扩展父类的Meta类,它可以子类化它。 例如:

from django.db import models

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

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

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

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

5.多表继承

这是 Django 支持的第二种继承方式。使用这种继承方式时,每一个层级下的每个 model 都是一个真正意义上完整的 model 。 每个 model 都有专属的数据表,都可以查询和创建数据表。 继承关系在子 model 和它的每个父类之间都添加一个链接 (通过一个自动创建的 OneToOneField来实现)。 例如:

from django.db import models

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

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

Place里面的所有字段在 Restaurant中也是有效的,只不过没有保存在数据库中的Restaurant表中。 所以下面两个语句都是可以运行的:

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

6.代理模型

使用 多表继承时,model 的每个子类都会创建一张新数据表, 通常情况下,这正是我们想要的操作。这是因为子类需要一个空间来存储不包含在基类中的字段数据。 但有时,你可能只想更改 model 在 Python 层的行为实现。比如:更改默认的 manager ,或是添加一个新方法。

而这,正是代理继承要做的:为原始模型创建一个代理你可以创建,删除,更新代理 model 的实例,而且所有的数据都可以像使用原始 model 一样被保存。 不同之处在于:你可以在代理 model 中改变默认的排序设置和默认的 manager ,更不会对原始 model 产生影响。

声明代理 model 和声明普通 model 没有什么不同。 设置Meta类中 proxy 的值为 True,就完成了对代理 model 的声明。

举个例子,假设你想给 Person 模型添加一个方法。 你可以这样做:

from django.db import models

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

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

    def do_something(self):
        # ...
        pass

Person类和它的父类 Person 操作同一个数据表。 特别的是,Person 的任何实例也可以通过 MyPerson访问,反之亦然:

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

 7.执行查询

一个博客应用

from django.db import models

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

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

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

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

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

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

(1)使用过滤器检索特定对象

all() 方法返回了一个包含数据库表中所有记录QuerySet但在通常情况下,你往往想要获取的是完整数据集的一个子集。

要创建这样一个子集,你需要在原始的的QuerySet上增加一些过滤条件。 QuerySet两个最普遍的途径是:

filter(**kwargs)
返回一个新的QuerySet,它包含满足查询参数的对象。
exclude(**kwargs)
返回一个新的QuerySet,它包含满足查询参数的对象。

每次你筛选一个QuerySet,得到的都是全新的另一个QuerySet,它和之前的QuerySet之间没有任何绑定关系。 每次筛选都会创建一个独立的QuerySet,它可以被存储及反复使用。

QuerySets 是惰性执行的 —— 创建QuerySet不会带来任何数据库的访问。 你可以将过滤器保持一整天,直到QuerySet 需要求值时,Django 才会真正运行这个查询。 

(2)使用get()检索单个对象

filter() 始终给你一个QuerySet,即使只有一个对象满足查询条件 —— 这种情况下,QuerySet将只包含一个元素。

如果你知道只有一个对象满足你的查询,你可以使用Managerget() 方法,它直接返回该对象:

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

可以对get() 使用任何查询表达式,和filter() 一样 —— 同样请查看下文的字段查询

注意,使用get() 和使用filter() 的切片[0] 有一点区别。 如果没有结果满足查询,get() 将引发一个DoesNotExist 异常。 这个异常是正在查询的模型类的一个属性 —— 所以在上面的代码中,如果没有主键(pk) 为1 的Entry对象,Django 将引发一个Entry.DoesNotExist

类似地,如果有多条记录满足get() 的查询条件,Django 也将报错。 这种情况将引发MultipleObjectsReturned,它同样是模型类自身的一个属性。

(3)限制QuerySet

可以使用Python 的切片语法来限制QuerySet记录的数目 。 它等同于SQL 的OFFSETLIMIT 子句。

例如,下面的语句返回前面5 个对象(LIMIT 5):

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

下面这条语句返回第6 至第10 个对象(OFFSET 5 LIMIT 5):

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

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

(3)字段查找

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

查询条件中指定的字段必须是模型字段的名称。 但有一个例外,对于ForeignKey你可以使用字段名加上_id 后缀。 在这种情况下,该参数的值应该是外键的原始值。 像这样:

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

大小写不敏感的匹配。 所以,查询:

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

将匹配标题为"Beatles Blog""beatles blog" 甚至"BeAtlES blOG"Blog

contains

大小写敏感的包含关系测试。 像这样:

Entry.objects.get(headline__contains='Lennon')
startswithendswith
分别表示以XXX开头和以XXX结尾。 当然还有大小写不敏感的版本,叫做istartswithiendswith

(4)跨关联关系的查询

Django 提供一种强大而又直观的方式来“处理”查询中的关联关系,它在后台自动帮你处理JOIN若要跨越关联关系,只需使用关联的模型字段的名称,并使用双下划线分隔,直至你想要的字段:

下面这个例子获取所有Blogname'Beatles Blog'Entry 对象:

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

这种跨越可以是任意的深度。

它还可以反向工作。 若要引用一个“反向”的关系,只需要使用该模型的小写的名称。

下面的示例获取所有的Blog 对象,它们至少有一个Entryheadline包含'Lennon'

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

选择所有包含同时满足两个条件的entry的blog,这两个条件是headline 包含Lennon 和发表时间是2008 (同一个entry 满足两个条件),我们的代码是:

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

要选择所有这样的blog,有一个entry的headline包含“Lennon”有一个entry发表时间是2008,我们将这样编写:

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

(5)F()

如果你想将模型的一个字段与同一个模型的另外一个字段进行比较该怎么办?

Django 提供F表达式 来允许这样的比较。 F() 返回的实例用作查询内部对模型字段的引用。 这些引用可以用于查询的filter 中来比较相同模型实例上不同字段之间值的比较。

例如,为了查找comments 数目多于pingbacks 的Entry,我们将构造一个F() 对象来引用pingback 数目,并在查询中使用该F() 对象:

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

Django 支持对F() 对象使用加法、减法、乘法、除法、取模以及幂计算等算术操作,两个操作数可以都是常数和其它F() 对象。 为了查找comments 数目比pingbacks 两倍还要多的Entry,我们将查询修改为:

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

为了查询rating 比pingback 和comment 数目总和要小的Entry,我们将这样查询:

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

你还可以在F() 对象中使用双下划线标记来跨越关联关系。 带有双下划线的F() 对象将引入任何需要的join 操作以访问关联的对象。 例如,如要获取author 的名字与blog 名字相同的Entry,我们可以这样查询:

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

对于date 和date/time 字段,你可以给它们加上或减去一个timedelta 对象。 下面的例子将返回发布超过3天后被修改的所有Entry:

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

(6)PK查找快捷方式

为了方便,Django 提供一个查询快捷方式pk ,它表示“primary key” 的意思。

在示例Blog模型中,主键pk是id字段,所以这三个语句是等价的:

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

(7)缓存和QuerySet

每个QuerySet都包含一个缓存来最小化对数据库的访问。 理解它是如何工作的将让你编写最高效的代码。

在一个新创建的QuerySet中,缓存为空。 首次对QuerySet进行求值 —— 同时发生数据库查询 ——Django 将保存查询的结果到QuerySet的缓存中并返回明确请求的结果(例如,如果正在迭代QuerySet,则返回下一个结果)。 接下来对该QuerySet 的求值将重用缓存的结果。

请牢记这个缓存行为,因为对QuerySet使用不当的话,它会坑你的。 例如,下面的语句创建两个QuerySet,对它们求值,然后扔掉它们:

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

这意味着相同的数据库查询将执行两次,显然倍增了你的数据库负载。 同时,还有可能两个结果列表并不包含相同的数据库记录,因为在两次请求期间有可能有Entry被添加进来或删除掉。

为了避免这个问题,只需保存QuerySet并重新使用它:

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

(8)使用Q对象进行复杂查找

filter()中的关键字参数查询 — — 是“AND”的关系。 如果你需要执行更复杂的查询(例如OR 语句),你可以使用Q对象

Q object (django.db.models.Q) 对象用于封装一组关键字参数。 这些关键字参数就是上文“字段查询” 中所提及的那些。

例如,下面的LIKE 对象封装一个Q 查询:

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

Q对象可以使用&|操作符组合起来。 当一个操作符在两个Q 对象上使用时,它产生一个新的Q 对象。

例如,下面的语句产生一个"question__startswith" 对象,表示两个Q 查询的“OR” :

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

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

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

你可以组合&| 操作符以及使用括号进行分组来编写任意复杂的Q 对象。 同时,~ 对象可以使用NOT 操作符取反,这允许组合正常的查询和取反(Q) 查询:

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

每个接受关键字参数的查询函数(例如filter()exclude()get())都可以传递一个或多个Q 对象作为位置(不带名的)参数。 如果一个查询函数有多个Q 对象参数,这些参数的逻辑关系为“AND"。 像这样:

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

查询函数可以混合使用Q和关键字参数。 所有提供给查询函数的参数(关键字参数或Q 对象)都将"AND”在一起。 但是,如果出现Q 对象,它必须位于所有关键字参数的前面。 像这样:

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

错误例子:

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

(9)反向查询

如果模型有一个ForeignKey,那么该ForeignKey所指的模型实例可以通过一个Manager返回第一个模型的所有实例。 默认情况下,这个Manager的名字为FOO_set,其中FOO是源模型的小写名称。 Manager返回QuerySets,可以用上一节提到的方式进行过滤和操作。

例如:

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

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

你可以在ForeignKey 定义时设置related_name 参数来覆盖FOO_set 的名称。 例如,如果Entry模型更改为blog = ForeignKey(Blog, on_delete=models.CASCADE, related_name='entries'),上述示例代码如下所示:

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

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

(10)多对多关系

多对多关系的两端都会自动获得访问另一端的API。 这些API 的工作方式与上面提到的“方向”一对多关系一样。

唯一的区别在于属性的命名:定义 ManyToManyField 的模型使用该字段的属性名称,而“反向”模型使用源模型的小写名称加上'_set' (和一对多关系一样)。

一个例子可以让它更好理解:

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

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

(11)一对一关系

对一关系与多对一关系非常相似。 如果你在模型中定义一个OneToOneField,该模型的实例将可以通过该模型的一个简单属性访问关联的模型。

像这样:

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

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

在“反向”查询中有所不同。 一对一关系中的关联模型同样具有一个Manager对象,但是该Manager表示一个单一的对象而不是对象的集合:

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

反向关联的关系是如何实现的

其它对象关系映射要求你在关联关系的两端都要定义。 Django 的开发人员相信这是对DRY(不要重复你自己的代码)原则的违背,所以Django 只要求你在一端定义关联关系。

但是这怎么可能?因为一个模型类直到其它模型类被加载之后才知道哪些模型类是关联的。

答案在app registry 中。 当Django 启动时,它导入INSTALLED_APPS 中列出的每个应用,然后导入每个应用中的models 模块。 每创建一个新的模型时,Django 添加反向的关系到所有关联的模型。 如果关联的模型还没有导入,Django 将保存关联关系的记录并在最终关联的模型导入时添加这些关联关系。

由于这个原因,你使用的所有模型都定义在INSTALLED_APPS 列出的应用中就显得特别重要。 否则,反向的关联关系将不能正确工作。

 8.聚合

Django抽象的数据库API描述使用Django查询来增删查改单个对象的方法。 然而,有时候你需要获取的值需要根据一组对象聚合后才能得到。 这份指南描述通过Django 查询来生成和返回聚合值的方法。

整篇指南我们都将引用以下模型。 这些模型用来记录多个网上书店的库存。

from django.db import models

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

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

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

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

以下是在上述模型的基础上,进行一般的聚合查询的方法:

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

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

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

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

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

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

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

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

(1)通过QuerySet生成聚合

QuerySet.对象上计算出总价格。 这可以通过在aggregate()后面附加QuerySet 子句来完成。

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

aggregate()QuerySet 的一个终止子句,意思是说,它返回一个包含一些键值对的字典。 该名称是总值的标识符;该值是计算的聚合。 键的名称是按照字段和聚合函数的名称自动生成出来的。 如果你想要为聚合值指定一个名称,可以向聚合子句提供它。

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

如果你希望生成不止一个聚合,你可以向aggregate()子句中添加另一个参数。 所以,如果你也想知道所有图书价格的最大值和最小值,可以这样查询:

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

(2)位QuerySet中每个项目生成聚合

生成汇总值的第二种方法,是为QuerySet中每一个对象都生成一个独立的汇总值。 比如,如果你在检索一列图书,你可能想知道每一本书有多少作者参与。 每本书与作者有多对多的关系;我们想在QuerySet中总结每本书的这种关系。

逐个对象的汇总结果可以由annotate()子句生成。 annotate()子句被指定之后,QuerySet中的每个对象都会被注上特定的值。

这些注解的语法都和aggregate()子句所使用的相同。 annotate()的每个参数都描述了将要被计算的聚合。 比如,给图书添加作者数量的注解:

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

和使用 aggregate()一样,注解的名称也根据聚合函数的名称和聚合字段的名称得到的。 你可以在指定注解时,为默认名称提供一个别名:

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

aggregate() 不同的是, annotate() 不是一个终止子句。 annotate()子句的输出是一个QuerySet;可以使用任何其他QuerySet操作修改QuerySet,包括filter()order_by()或甚至附加调用annotate()

要查找每个商店提供的图书的价格范围,您可以使用注释:

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

这段代码告诉 Django 获取Store模型,并连接(通过多对多关系)Book模型,然后对每本书的价格进行聚合,得出最小值和最大值。

同样的规则也用于 aggregate() 子句。 如果您想知道任何商店中可出售的任何图书的最低价格和最高价格,您可以使用汇总:

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

关系链可以按你的要求一直延伸。 例如,想得到所有作者当中最小的年龄是多少,就可以这样写:

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

(3)反向

在你所查询的模型的关联模型或者字段上的聚合和注解可以遍历"反转"关系。 关联模型的小写名称和双下划线也用在这里。

例如,我们可以查询所有出版商,并注上它们一共出了多少本书(注意我们如何用 Publisher指定Book -> 'book' 的外键反转关系):

查询所有图书中最旧的那本:

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

这不仅仅可以应用挂在外键上面。 还可以用到多对多关系上。 例如,我们可以查询每个作者,注上它写的所有书(以及合著的书)一共有多少页(注意我们如何使用 Author来指定Book -> 'book'的多对多的反转关系):

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

查询所有图书的平均评分,这些图书由我们存档过的作者所写:

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

使用annotate() 子句时,过滤器有限制注解对象的作用。 例如,你想得到每本以 "Django" 为书名开头的图书作者的总数:

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

使用aggregate()子句时,过滤器有限制聚合对象的作用。 例如,你可以算出所有以 "Django" 为书名开头的图书平均价格:

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

(4)过滤注释

注解值也可以被过滤。 像使用其他模型字段一样,注解也可以在filter()exclude() 子句中使用别名。

例如,要得到不止一个作者的图书,可以用:

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

这个查询首先生成一个注解结果,然后再生成一个作用于注解上的过滤器。

(5)order_by()

注解可以用来做为排序项。 在你定义 order_by() 子句时,你提供的聚合可以引用定义的任何别名做为查询中 annotate()子句的一部分。

例如,根据一本图书作者数量的多少对查询集 QuerySet进行排序:

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

(6)values()

通常,注解会添加到每个对象上 —— 一个被注解的QuerySet会为初始QuerySet的每个对象返回一个结果集。 但是,如果使用了values()子句,它就会限制结果中列的范围,对注解赋值的方法就会完全不同。 不是在原始的 QuerySet返回结果中对每个对象中添加注解,而是根据定义在values() 子句中的字段组合先对结果进行唯一的分组, 然后为每个唯一组提供注释;在组的所有成员上计算注释。

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

9.返回新的QuerySet 方法

Django 提供了一系列 的QuerySet筛选方法,用于改变 QuerySet 返回的结果类型或者SQL查询执行的方式。

filter()

filter(**kwargs)

返回一个新的QuerySet,它包含满足查询参数的对象

exclude()

exclude(**kwargs)

返回一个新的QuerySet,它包含满足给定的查找参数的对象。

下面的示例排除所有pub_date 晚于2005-1-3 且headline 为“Hello” 的记录:

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

annotate()

annotate(*args, **kwargs)

例如,如果你正在操作一个Blog列表,你可能想知道每个Blog有多少Entry:

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

order_by()

order_by(*fields)

默认情况下,Meta 根据模型ordering 类的QuerySet 选项排序。 你可以使用QuerySet 方法给每个order_by 指定特定的排序。

例如:

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

上面的结果将按照pub_date 降序排序,然后再按照headline 升序排序。 "-pub_date"前面的负号表示降序顺序。 升序是隐含的。

reverse()

reverse()

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

如要获取QuerySet 中最后五个元素,你可以这样做:

my_queryset.reverse()[:5]

注意,这与Python 中从一个序列的末尾进行切片有点不一样。 上面的例子将首先返回最后一个元素,然后是倒数第二个元素,以此类推。 如果我们有一个Python 序列,当我们查看seq[-5:] 时,我们将一下子得到倒数五个元素。 Django 不支持这种访问模型(从末尾进行切片),因为它不可能利用SQL 高效地实现。

values()

values(*fields, **expressions)

返回一个返回字典的QuerySet,而不是使用模型实例作为一个迭代。

每个字典表示一个对象,键对应于模型对象的属性名称。

下面的例子将values() 与普通的模型对象进行比较:

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

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

SELECT 接收可选的位置参数*fields,它指定values() 应该限制哪些字段。 如果指定字段,每个字典将只包含指定的字段的键/值。 如果没有指定字段,每个字典将包含数据库表中所有字段的键和值。

例如:

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

values_list()

values_list(*fields, flat=False)

values() 类似,只是在迭代时返回的是元组而不是字典。 每个元组包含传递给values_list()调用的相应字段或表达式的值,因此第一个项目是第一个字段等。 像这样:

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

select_related()

返回一个QuerySet,当执行它的查询时它沿着外键关系查询关联的对象的数据。 它会生成一个复杂的查询并引起性能的损耗,但是在以后使用外键关系时将不需要数据库查询。

defer()

defer(*fields)

在一些复杂的数据建模情况下,你的模型可能包含大量字段,其中一些可能包含大量数据(例如文本字段),或者需要昂贵的处理来将它们转换为Python对象。 当你最初获取数据时不知道是否需要这些特定字段的情况下,如果你正在使用查询集的结果,你可以告诉Django不要从数据库中检索它们。

它通过传递字段名称到defer()实现不加载:

# 延迟body和headline两个字段。
Entry.objects.defer("body").filter(rating=5).defer("headline")

如果要清除延迟字段集,请将None作为参数传递到defer()

# 立即加载所有的字段。
my_queryset.defer(None)

only()

only(*fields)

only()方法或多或少与defer()相反。 你以应该在检索模型时延迟的字段调用它。 如果你有一个模型几乎所有的字段需要延迟,使用only()指定补充的字段集可以导致更简单的代码。

10.不返回QuerySet的方法

get()

get(**kwargs)

返回按照查询参数匹配到的对象

 

count()

count()

返回在数据库中对应的 QuerySet.对象的个数。 count() 永远不会引发异常。

in_bulk()

in_bulk(id_list=None)

获取主键值的列表,并返回将每个主键值映射到具有给定ID的对象的实例的字典。 如果未提供列表,则会返回查询集中的所有对象。

例如:

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

latest()

latest(field_name=None)

使用作为日期字段提供的field_name,按日期返回表中的最新对象。

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

Entry.objects.latest('pub_date')

first()

first()

返回结果集的第一个对象, 当没有找到时返回None. 如果 QuerySet 没有设置排序,则将会自动按主键进行排序

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

last()

last()

工作方式类似first(),只是返回的是查询集中最后一个对象。

aggregate()

aggregate(*args, **kwargs)

返回汇总值的字典(平均值,总和等) 通过QuerySet进行计算。 aggregate() 的每个参数指定返回的字典中将要包含的值。

exists()

exists()

如果QuerySet 包含任何结果,则返回True,否则返回False

 

11.Field查找

exact

精确匹配。

iexact

不区分大小写的精确匹配

contains

大小写敏感的包含关系测试。

例如:

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

icontains

测试是否包含,不区分大小写。

in

在给定的列表。

例如:

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

gt

大于

例如:

Entry.objects.filter(id__gt=4)

gte

大于或等于

lt

小于

lte

小于或等于

startswith

区分大小写,开始位置匹配

例如:

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

istartswith

不区分大小写,开始位置匹配

endswith

区分大小写。

iendswith

不区分大小写。

date

对于datetime字段,将值作为日期转换。 允许链接附加字段查找。 获取日期值。

例如:

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

year

对于日期和日期时间字段,确切的年匹配。 允许链接附加字段查找。 整数年。

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

month

对于日期和日期时间字段,确切的月份匹配。

day

对于日期和日期时间字段,具体到某一天的匹配。

 12.管理器

你可以在模型中使用自定义的Manager,方法是继承Manager 基类并实例化你的自定义Manager

你有两个原因可能会自己定义Manager:向Manager类中添加额外的方法,或者修改Manager返回的原始QuerySet

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

管理器自带的QuerySet返回系统中所有的对象。 例如,使用下面这个模型:

from django.db import models

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

...语句Book.objects.all()将返回数据库中的所有书籍。

你可以通过重写Manager.get_queryset()方法来覆盖Manager自带的QuerySetget_queryset()应该返回一个带有你需要的属性的QuerySet

例如,下面的模型有两个Manager,一个返回所有的对象,另一个则只返回作者是Roald Dahl 的对象:

...语句Book.objects.all()将返回数据库中的所有书籍。

你可以通过重写Manager.get_queryset()方法来覆盖Manager自带的QuerySetget_queryset()应该返回一个带有你需要的属性的QuerySet

例如,下面的模型有两个Manager,一个返回所有的对象,另一个则只返回作者是Roald Dahl 的对象:

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

# 然后将它显式地放入Book模型。
class Book(models.Model):
    title = models.CharField(max_length=100)
    author = models.CharField(max_length=50)

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

在这个简单的例子中,Book.objects.all()将返回数据库中所有的图书,而Book.dahl_objects.all()只返回Roald Dahl写作的图书。

当然,因为get_queryset()返回QuerySet对象,你可以使用filter()exclude()和所有其他QuerySet方法。 所以下面这些例子都是可用的:

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

该例还展示了另外一个很有意思的技巧:同一模型使用多个管理器。 你可以依据你自己的偏好在一个模型里面添加多个 Manager() 实例。 这是给模型添加通用过滤器(选择器)的一个简单方法:

像这样:

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

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

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

这个例子让你可以使用Person.authors.all()Person.editors.all()以及Person.people.all(),都会得到预期的结果

(2)从管理器调用自定义QuerySet方法

虽然大多数标准QuerySet的方法可以从Manager中直接访问到,但是如果你需要将一些被定义到一个自定义QuerySet中的额外方法也在Manager上实现,下面所展示的这个例子是唯一可行办法:

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

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

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

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

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

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

这个例子允许你直接从管理器Person.people中调用authors()editors()

13.Meta选项

可用的Meta选项

abstract

Options.abstract

如果 abstract True, 就表示模型是抽象基类

app_label

Options.app_label

如果该项目下有多个app,有一个model不是定义在本app下默认的model.py,而是在其他app,也即它在本app settings的INSTALLED_APPS没有声明,则必须使用app_lable声明其属于哪个app:

app_label = 'myapp'

如果要表示具有格式app_label.object_nameapp_label.model_name的模型,可以使用model._meta.labelmodel._meta.label_lower

base_manager_name

Options.base_manager_name
Django中的新功能1.10。

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

db_table

Options.db_table

该模型所用的数据表的名称:

db_table = 'music_album'

Table names

为了节省时间,Django 会自动的使用你的 model class 的名称和包含这个 model 的 app 名称来构建 数据库的表名称。 一个 model 的数据库表名称是通过将 “app label” – 你在 manage.py startapp 中使用的名称 – 和 model 的类名称,加上一个下划线在他们之间来构成。

举个例子,bookstore应用(使用manage.py startapp bookstore 创建),以class Book定义的模型的数据表的名称将是bookstore_book 。

使用 Meta类中的 db_table 参数来重写数据表的名称。

如果你的数据库表名称是SQL保留字,或包含Python变量名称中不允许的字符,特别是连字符 — 没有问题。 Django在后台引用列和表名。

db_tablespace

Options.db_tablespace

当前模型所使用的database tablespace 的名字。 默认值是项目设置中的DEFAULT_TABLESPACE,如果它存在的话。 如果后端并不支持表空间,这个选项可以忽略。

default_manager_name

Options.default_manager_name
Django中的新功能1.10。

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

from django.db import models

class Foo(models.Model):
    pass

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

    class Meta:
        default_related_name = 'bars'
>>> bar = Bar.objects.get(pk=1)
>>> # 使用名称"bar"作为查询名称已弃用。
>>> Foo.objects.get(bar=bar)
>>> # 你应该使用default_related_name "bars"。
>>> Foo.objects.get(bars=bar)

get_latest_by

Options.get_latest_by

模型中某个可排序的字段的名称,比如DateFieldDateTimeField或者IntegerField。 它指定了Managerlatest()earliest()中使用的默认字段。

例如:

get_latest_by = "order_date"

managed

Options.managed

默认为True,表示Django会通过migrate创建合适的数据表,并且可通过flush管理命令移除这些数据库表。 换句话说,Django会管理这些数据表的生命周期。

如果是False,Django 就不会为当前模型创建和删除数据表。 如果当前模型表示一个已经存在的且是通过其它方法创建的者数据表或数据库视图,这会相当有用。 这是设置为managed=False唯一的不同之处。 模型处理的其它任何方面都和平常一样。 这包括:

  1. 如果你不声明它的话,会向你的模型中添加一个自增主键。 为了避免给后面的代码读者带来混乱,当你在使用未被管理的模型时,强烈推荐你指定(specify)数据表中所有的列。

  2. 如果一个模型设置了managed=False且含有ManyToManyField,且这个多对多字段指向其他同样也是未被管理模型的,那么这两个未被管理的模型的多对多中介表也不会被创建。 但是,一个被管理模型和一个未被管理模型之间的中介表就会被创建。

    如果你需要修改这一默认行为,创建中介表作为显式的模型(也要设置managed),并且使用ManyToManyField.through为你的自定义模型创建关联。

如果你进行测试,测试中涉及非托管 model (managed=False),那么在测试之前,你应该要确保在 测试启动时 已经创建了正确的数据表。

如果你对在Python层面修改模型类的行为感兴趣,你可以设置 managed=False ,并且为一个已经存在的模型创建一个副本。 不过在面对这种情况时还有个更好的办法就是使 用Proxy models.

order_with_respect_to

Options.order_with_respect_to

使此对象相对于给定字段可以排序,通常为ForeignKey。 这可以用于使关联的对象相对于父对象可排序。 比如,如果AnswerQuestion相关联,一个问题有至少一个答案,并且答案的顺序非常重要,你可以这样做:

from django.db import models

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

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

    class Meta:
        order_with_respect_to = 'question'

order_with_respect_to 设置之后,模型会提供两个额外的用于设置和获取关联对象顺序的方法:get_RELATED_order()set_RELATED_order(),其中RELATED是小写的模型名称。 例如,假设一个Question对象有很多相关联的Answer对象,返回的列表中含有与之相关联Answer对象的主键:

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

Question对象相关联的Answer对象的顺序,可以通过传入一个包含Answer主键的列表来设置:

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

相关联的对象也有两个方法, get_next_in_order() 和get_previous_in_order(),用于按照合适的顺序访问它们。 假设Answer对象按照 id来排序:

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

ordering

Options.ordering

对象默认的顺序,在获取对象的列表时使用:

ordering = ['-order_date']

它是一个字符串的列表或元组。 每个字符串是一个字段名,前面带有可选的“-”前缀表示倒序。 前面没有“-”的字段表示正序。 使用字符串“?”来随机排序。

例如,要按照pub_date字段的正序排序,这样写:

ordering = ['pub_date']

按照pub_date字段的倒序排序,这样写:

ordering = ['-pub_date']

先按照pub_date的倒序排序,再按照 author 的正序排序,这样写:

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

permissions

Options.permissions

设置创建对象时权限表中额外的权限。 增加、删除和修改权限会自动为每个模型创建。 这个例子指定了一种额外的权限,can_deliver_pizzas

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

它是一个包含二元组的元组或者列表,格式为 (permission_code, human_readable_permission_name)

default_permissions

Options.default_permissions

默认为('add', 'change', 'delete')。 你可以自定义这个列表,比如,如果你的应用不需要默认权限中的任何一项,可以把它设置成空列表。 在模型被migrate命令创建之前,这个属性必须被指定,以防一些遗漏的属性被创建。

proxy

Options.proxy

如果proxy True, 它作为另一个模型的子类,将会作为一个proxy model

required_db_features

Options.required_db_features

当前连接应具有的数据库功能列表,以便在迁移阶段考虑该模型。 例如,如果将此列表设置为['gis_enabled'],则模型将仅在启用GIS的数据库上同步。 在使用多个数据库后端进行测试时,跳过某些模型也很有用。 避免与ORM无关的模型之间的关系。

required_db_vendor

Options.required_db_vendor

此型号特定于受支持的数据库供应商的名称。 当前内置的供应商名称是:sqlitepostgresqlmysqloracle。 如果此属性不为空,并且当前连接供应商不匹配,则该模型将不会同步。

select_on_save

Options.select_on_save

该选项决定Django是否采用1.6之前的django.db.models.Model.save()算法。 旧的算法使用SELECT来判断是否存在需要更新的行。 而新的算法直接尝试使用UPDATE。 在某些少见的情况下,一个已存在行的UPDATE操作对Django不可见。 一个例子是PostgreSQL的返回NULLON UPDATE触发器。 这种情况下,新式的算法最终会执行INSERT操作,即使这一行已经在数据库中存在。

通常这个属性不需要设置。 默认为False

关于旧式和新式两种算法,请参见django.db.models.Model.save()

indexes

Options.indexes
Django中的新功能1.11。

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

from django.db import models

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

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

unique_together

Options.unique_together

用来设置的不重复的字段组合:

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

它是一个元组的元组,组合起来的时候必须是唯一的。 它在Django admin层面使用,在数据库层上进行数据约束(比如,在 CREATE TABLE 语句中包含 UNIQUE语句)。

为了方便起见,处理单一字段的集合时,unique_together 可以是一维的元组:

unique_together = ("driver", "restaurant")

ManyToManyField不能包含在unique_together中。 (不清楚它的含义是什么!) 如果你需要验证ManyToManyField关联的唯一性,试着使用信号或者显式的through模型。

unique_together的约束被违反时,模型校验期间会抛出ValidationError异常。

index_together

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

列表中的字段将会建立索引(例如,会在CREATE INDEX语句中被使用)。

为了方便起见,当需要处理的字段的集合只有一个的时候(集合只有一个!),index_together可以只用一个中括号。也就是只用一个一维列表。

index_together = ["pub_date", "deadline"]

verbose_name

Options.verbose_name

对象的一个易于理解的名称,为单数:

verbose_name = "pizza"

如果此项没有设置,Django会把类名拆分开来作为自述名,比如CamelCase 会变成camel case

verbose_name_plural

Options.verbose_name_plural

该对象复数形式的名称:

verbose_name_plural = "stories"

如果此项没有设置,Django 会使用 verbose_name + "s"

只读的Meta属性

label

Options.label

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

label_lower

Options.label_lower

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

视图层

1.URL配置

当一个用户请求Django 站点的一个页面,下面是Django 系统决定执行哪个Python 代码遵循的算法:

  1. Django 决定要使用的根URLconf 模块。 通常,这是ROOT_URLCONF设置的值,但是如果传入的HttpRequest对象具有urlconf属性(由中间件设置),则其值将被用于代替ROOT_URLCONF设置。
  2. Django 加载该Python 模块并寻找可用的urlpatterns它是django.conf.urls.url() 实例的一个Python 列表。
  3. Django 依次匹配每个URL 模式,在与请求的URL 匹配的第一个模式停下来。
  4. 一旦正则表达式匹配,Django将导入并调用给定的视图,该视图是一个简单的Python函数(或基于类的class-based view)。 视图将获得如下参数:
    • 一个HttpRequest 实例。
    • 如果匹配的正则表达式返回了没有命名的组,那么正则表达式匹配的内容将作为位置参数提供给视图。
    • 关键字参数由正则表达式匹配的命名组组成,但是可以被django.conf.urls.url()的可选参数kwargs覆盖。
  5. 如果没有匹配到正则表达式,或者如果过程中抛出一个异常,Django 将调用一个适当的错误处理视图。 请参见下面的错误处理

(1)示例

下面是一个简单的 URLconf:

from django.conf.urls import url

from . import views

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

注:

  • 若要从URL 中捕获一个值,只需要在它周围放置一对圆括号
  • 不需要添加一个前导的反斜杠,因为每个URL 都有。 例如,应该是^articles 而不是 ^/articles
  • 每个正则表达式前面的'r' 是可选的但是建议加上。 它告诉Python 这个字符串是“原始的” —— 字符串中任何字符都不应该转义。 

一些请求的例子:

  • /articles/2005/03/ 请求将匹配列表中的第三个模式。 Django 将调用函数views.month_archive(request, '2005', '03')
  • /articles/2005/3/ 不匹配任何URL 模式,因为列表中的第三个模式要求月份应该是两个数字。
  • /articles/2003/ 将匹配列表中的第一个模式不是第二个,因为模式按顺序匹配,第一个会首先测试是否匹配。 请像这样自由插入一些特殊的情况来探测匹配的次序。 这里,Django会调用函数views.special_case_2003(request)
  • /articles/2003 不匹配任何一个模式,因为每个模式要求URL 以一个斜线结尾。
  • /articles/2003/03/03/ 将匹配最后一个模式。 Django 将调用函数views.article_detail(request, '2003', '03', '03')

(2)命名组

上面的示例使用简单的、没有命名的正则表达式组(通过圆括号)来捕获URL 中的值并以位置 参数传递给视图。 在更高级的用法中,可以使用命名的正则表达式组来捕获URL 中的值并以关键字 参数传递给视图。

在Python 正则表达式中,命名正则表达式组的语法是(?P<name>pattern),其中name 是组的名称,pattern 是要匹配的模式。

下面是以上URLconf 使用命名组的重写:

from django.conf.urls import url

from . import views

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

这个实现与前面的示例完全相同,只有一个细微的差别:捕获的值作为关键字参数而不是位置参数传递给视图函数。 像这样:

  • /articles/2005/03/ 请求将调用views.month_archive(request, year='2005', month='03')函数,而不是views.month_archive(request, '2005', '03')
  • /articles/2003/03/03/ 请求将调用函数views.article_detail(request, year='2003', month='03', day='03')

在实际应用中,这意味你的URLconf 会更加明晰且不容易产生参数顺序问题的错误 —— 你可以在你的视图函数定义中重新安排参数的顺序。 当然,这些好处是以简洁为代价的;一些开发人员发现命名组语法丑陋而且太冗长。

捕获的参数总是字符串

 

每个捕获的参数都作为一个普通的Python 字符串传递给视图,无论正则表达式使用的是什么匹配方式。 例如,下面这行URLconf 中:

url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive)
...传递给views.year_archive()year参数将是一个字符串,
不是整数,即使[0-9]{4}只匹配整数字符串。

(3)传递额外的参数来查看函数

URLconfs 具有一个钩子,让你传递一个Python 字典作为额外的参数传递给视图函数。

django.conf.urls.url() 函数可以接收一个可选的第三个参数,它是一个字典,表示想要传递给视图函数的额外关键字参数。

像这样:

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

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

在这个例子中,对于/blog/2005/请求,Django 将调用views.year_archive(request, year='2005', foo='bar')

(4)URL的反向解析

在 Django 项目中经常需要获取最终形式的 URL,这么做是为了在生成的内容中嵌入 URL(视图和素材资源网址,呈现给用户的网址,等等), 或者用于在服务器端处理导航流程(重定向等)

此时,一定不能硬编码 URL(费时、不可伸缩,而且容易出错), 或者参照 URL 配置创造一种生成 URL 的机制,因为这样非常容易导致线上 URL 失效。

换句话讲,我们需要的是一个 DRY 机制。 这种机制的一个优点是,当改进 URL 设计之后无需在项目源码中大范围搜索、替换失效的 URL。

我们可以获得URL的主要信息是负责处理URL的视图的标识(例如名称)。 必须参与正确URL查找的其他信息片段是视图参数的类型(位置,关键字)和值。

Django 提供了一种方案,只需在 URL 映射中设计 URL。 我们为其提供 URL 配置,然后就可以双向使用:

  • 根据用户/浏览器发起的URL 请求,它调用正确的Django 视图,并从URL 中提取它的参数需要的值。
  • 根据Django 视图的标识和将要传递给它的参数的值,获取与之关联的URL。

第一种方式是我们在前面的章节中一直讨论的用法。 第二种方式叫做反向解析URL反向URL匹配反向URL查询或者简单的URL反查

在需要URL 的地方,对于不同层级,Django 提供不同的工具用于URL 反查:

  • 在模板中:使用url 模板标签。
  • 在Python代码中:使用reverse()函数。
  • 在更高层的与处理Django 模型实例相关的代码中:使用get_absolute_url() 方法。

2.视图函数

一个视图函数,简称视图,是一个简单的Python 函数,它接受Web请求并且返回Web响应。 此响应可以是网页的HTML内容,重定向,404错误,XML文档或图像。 . . 或任何东西,真的。 无论视图本身包含什么逻辑,都要返回响应。 代码写在哪里也无所谓,只要它在你的Python目录下面。 除此之外没有更多的要求了——可以说“没有什么神奇的地方”。 为了将代码放在某处,约定是将视图放置在项目或应用程序目录中的名为views.py的文件中。

(1)Django的快捷函数

render()

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

结合一个给定的模板和一个给定的上下文字典,并返回一个渲染后的 HttpResponse 对象。

Django 不提供返回TemplateResponse 的快捷函数,因为TemplateResponse 的构造与render() 提供的便利是一个层次的。

必需参数

request
该request用于生成response
template_name
要使用的模板的完整名称或者模板名称的一个序列。 如果给出的是一个序列,将使用存在的第一个模板。 关于如何查找模板的更多信息请参见 template loading documentation

可选参数

context
添加到模板上下文的一个字典。 默认是一个空字典。 如果字典中的某个值是可调用的,视图将在渲染模板之前调用它。
content_type
用于生成的文档的MIME类型。 默认为DEFAULT_CONTENT_TYPE设置的值。
status
响应的状态代码。 默认为200
using
用于加载模板使用的模板引擎的NAME

render_to_response()

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

此功能在引入render()之前进行,除了不能使request可用于响应之外,它的工作方式类似。 不推荐,以后可能会被弃用

redirect()

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

为传递进来的参数返回HttpResponseRedirect 给正确的URL 。

参数可以是:

  • 一个模型:将调用模型的get_absolute_url() 函数
  • 视图名称,可能带有参数:reverse()将用于反向解析名称。
  • 一个绝对的或相对的URL,将原封不动的作为重定向的位置。

默认情况下会发出临时重定向;通过permanent=True发出永久重定向。

get_object_or_404()

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

在一个给定的模型管理器上调用get(),但是引发Http404 而不是模型的DoesNotExist 异常。

必需参数

klass
获取该对象的一个Model 类,ManagerQuerySet 实例。
**kwargs
查询的参数,格式应该可以被get()filter()接受。

实例

下面的示例从MyModel 中使用主键1 来获取对象:

from django.shortcuts import get_object_or_404

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

 2.基于类的视图

基于类的视图使用Python 对象实现视图,它提供除函数视图之外的另外一种方式。 它们不替换基于函数的视图,但与基于函数的视图相比具有一定的区别和优势:

  • 组织与特定HTTP方法相关的代码(GETPOST等) 可以通过单独的方法而不是条件分支来解决。
  • 面向对象的技术例如Mixin(多继承)可以将代码分解成可重用的组件。

基于类的视图的核心是允许你用不同的实例方法来响应不同的HTTP 请求方法,而不是在一个视图函数中使用条件分支代码来实现。

所以,视图函数中处理HTTP GET 的代码看上去将像:

from django.http import HttpResponse

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

在基于类的视图中,它将变成:

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

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

因为Django的URL解析器希望将请求和关联的参数发送到可调用函数,而不是类,基于类的视图具有一个as_view()类方法,它返回一个可以在请求时调用的函数到达与相关模式匹配的URL。 该函数创建一个类的实例并调用其dispatch()方法。 GET 查看请求是POST 还是dispatch 等等,并将请求转发给相应的方法,如果该方法没有定义则引发HttpResponseNotAllowed

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

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

值得注意的是,方法的返回值与基于函数的视图的返回值完全相同,即HttpResponse 的某种形式。 这表示在基于类的视图中可以使用http shortcutsTemplateResponse 对象。

虽然基于类的视图的最小实现不需要任何类属性来完成它的功能,但是在许多基于类的设计中类属性非常重要,有两种方式来设置类属性。

第一种方式是Python 标准的方式,子类化并在子类中覆盖属性和方法。 所以,如果父类有一个greeting 属性:

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

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

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

你可以在子类中覆盖它:

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

另外一种方式是在URLconf 中用as_view() 调用的关键字参数配置类的属性:

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

3.Mixins的使用

Mixin 是多继承的一种形式,其来自多个父类的行为和属性可以组合在一起。

例如,在通用的基于类的视图中,有一个Mixin 叫做 TemplateResponseMixin,它的主要目的是定义render_to_response() 方法。 它与View 基类的组合是TemplateView 类,这个类可以调度请求给正确的方法(TemplateResponseMixin 基类中定义的行为),同时还具有一个render_to_response() 方法,该方法使用template_name 属性来返回一个TemplateResponse 对象( View 中定义的行为)。

Mixin 是重用多个类的代码的一种极好的方法,但是它们需要一些代价。 代码在Mixin 中越分散,子类将越难阅读并知道它的行为;如果你的继承很深,将难以知道应该覆盖哪一个Mixin 的方法。

还要注意,只能继承一个通用视图 —— 也就是说,只能有一个父类继承View,其它的父类必须是Mixin。 继承多个继承自View 的类 将不能像预期的那样工作

一个最基本的用于处理表单的视图函数可能是这样的:

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

from .forms import MyForm

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

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

类似的一个基于类的视图看上去是这样:

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

from .forms import MyForm

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

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

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

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

这是一个非常简单的例子,但您可以看到,您可以选择通过覆盖任何类属性来定制此视图,例如。 form_class,通过URLconf配置,或子类化和覆盖一个或多个方法(或两者都)!)。

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

4.装饰基于类的视图

基于类的视图的扩展不仅仅局限于使用Mixin。 你还可以使用装饰器。 由于基于类的视图不是函数,对它们的装饰取决于你使用as_view() 还是创建一个子类。

(1)在URLconf中进行装饰

装饰基于类的视图的最简单的方法是装饰as_view() 方法的结果。 最方便的地方是URLconf 中部署视图的位置:

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

from .views import VoteView

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

这个方法在每个实例的基础上运用装饰器。 如果想让视图的每个实例都被装饰,你需要一种不同的方法。

(2)装饰类

若要装饰基于类的视图的每个实例,你需要装饰类本身。 可以将装饰器运用到类的dispatch() 方法上来实现这点。

类的方法和独立的函数不完全相同,所以你不可以直接将函数装饰器运用到方法上 —— 你首先需要将它转换成一个方法装饰器。 method_decorator 装饰器将函数装饰器转换成方法装饰器,这样它就可以用于实例方法上。 像这样:

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

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

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

或者,更简洁的是,您可以装饰类,并将要装饰的方法的名称作为关键字参数name传递:

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

如果您在几个地方使用了一组常用的装饰器,您可以定义一个列表或元组的装饰器,并使用它,而不是多次调用method_decorator()这两个类是相当的:

decorators = [never_cache, login_required]

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

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

装饰器将按照传递给装饰器的顺序处理请求。 在这个例子中,never_cache()将在login_required()之前处理请求。

5.中间件

中间件是一个钩子框架,它们可以介入Django 的请求和响应处理过程。 它是一个轻量级、底层的“插件”系统,用于在全局修改Django 的输入或输出。

每个中间件组件负责完成某个特定的功能。 例如,Django 包含的一个中间件组件AuthenticationMiddleware ,它使用会话将用户和请求关联起来。

(1)编写自己的中间件

一个中间件工厂是一个可调用的,它采用一个get_response可调用并返回一个中间件。 中间件是一个可调用的函数,它接受请求并返回响应,就像视图一样。

中间件可以写成一个如下所示的功能:

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

    def middleware(request):
        # 在调用视图(以及稍后的中间件)之前
        # 要为每个请求执行代码。

        response = get_response(request)

        # 为每个请求/响应执行的代码
        # 在调用视图之后

        return response

    return middleware

或者它可以写成一个类,其实例是可调用的,如下所示:

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

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

        response = self.get_response(request)

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

        return response

__init__(get_response)

中间件工厂必须接受get_response参数。 您也可以初始化中间件的全局状态。 记住几个注意事项:

  • Django只使用get_response参数初始化您的中间件,因此您不能将__init__()定义为需要任何其他参数。
  • 与每个请求一次调用的__call__()方法不同,当Web服务器启动时,__init__()仅被调用一次

(2)激活中间件

要激活中间件组件,请将其添加到Django设置中的MIDDLEWARE列表中。

MIDDLEWARE中,每个中间件组件由一个字符串表示:完整的Python路径到中间件工厂的类或函数名称。 例如,使用 django-admin startproject创建工程的时候生成的默认值:

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

(3)中间件顺序和分层

在请求阶段,在调用视图之前,Django以MIDDLEWARE(自上而下)定义的顺序应用中间件。

你可以像洋葱一样想起来:每个中间件类都是一个“层”,它覆盖了洋葱核心的视图。 如果请求通过洋葱的所有层(每个调用get_response将请求传递到下一层),一直到核心的视图,响应将通过在每一层(以相反的顺序)的路上退出。

如果其中一个层决定短路并返回响应而不调用其get_response,那么该层(包括视图)内的洋葱层都不会看到请求或响应。 响应将只返回通过请求传递的相同的层。

(4)其它中间件钩子

除了前面描述的基本请求/响应中间件模式,您还可以向基于类的中间件添加三种其他特殊方法:

process_view()

process_viewrequestview_funcview_argsview_kwargs

request是一个HttpRequest 对象。 view_func是 Django会调用的一个Python的函数。 (它是一个真实的函数对象,不是函数的字符名称。) view_args是一个会被传递到视图的位置参数列表,而view_kwargs 是一个会被传递到视图的关键字参数字典。 view_argsview_kwargs 都不包括第一个视图参数(request)。

process_view()会在Django 调用视图之前被调用。

它应该返回一个None 或一个HttpResponse对象。 如果返回None,Django 将会继续处理这个请求,执行其它的process_view() 中间件,然后调用对应的视图。 如果它返回一个HttpResponse对象,Django不会打扰调用相应的视图;它将应用响应中间件到HttpResponse并返回结果。

process_exception()

process_exceptionrequestexception

request是一个HttpRequest 对象。 Exception是一个被视图中的方法抛出来的 exception对象。

当一个视图抛出异常时,Django会调用process_exception()来处理。 None应该返回一个process_exception() 或者一个HttpResponse对象。 如果它返回一个HttpResponse对象,则将应用模板响应和响应中间件,并将生成的响应返回给浏览器。 否则,default exception handling开始。

再次提醒,在处理响应期间,中间件的执行顺序是倒序执行的,这包括process_exception如果异常中间件返回响应,那么中间件上面的中间件类的process_exception方法根本就不会被调用。

process_template_response()

process_template_response请求响应

request是一个HttpRequest 对象。 response是一个TemplateResponse对象(或等价的对象),由Django视图或者中间件返回。

如果响应的实例有render()方法,process_template_response()在视图刚好执行完毕之后被调用,这表明了它是一个TemplateResponse对象(或等价的对象)。

这个方法必须返回一个实现了render方法的响应对象。 它可以修改给定的response.template_name对象,通过修改 responseresponse.context_data或者它可以创建一个全新的 TemplateResponse或等价的对象。

你不需要显式渲染响应 —— 一旦所有的模板响应中间件被调用,响应会自动被渲染。

在一个响应的处理期间,中间件以相反的顺序运行,这包括process_template_response()

 模板层

 1.配置

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

BACKEND 是一个指向实现了Django模板后端API的模板引擎类的带点的Python路径。 内置的后端有 django.template.backends.django.DjangoTemplatesdjango.template.backends.jinja2.Jinja2

由于绝大多数引擎都是从文件加载模板的,所以每种模板引擎都包含两项通用设置:

  • DIRS 定义了一个目录列表,模板引擎按列表顺序搜索这些目录以查找模板源文件。
  • APP_DIRS 告诉模板引擎是否应该进入每个已安装的应用中查找模板。 每种模板引擎后端都定义了一个惯用的名称作为应用内部存放模板的子目录名称。(译者注:例如django为它自己的模板引擎指定的是 ‘templates’ ,为jinja2指定的名字是‘jinja2’)

特别的是,django允许你有多个模板引擎后台实例,且每个实例有不同的配置选项。 在这种情况下你必须为每个配置指定一个唯一的NAME .

OPTIONS 中包含了具体的backend设置

2.模板语言

模板

模版是纯文本文件。 它可以生成任何基于文本的格式(HTML,XML,CSV等))。

模版包括在使用时会被值替换掉的 变量,和控制模版逻辑的 标签

下面是一个小模版,它说明了一些基本的元素。 后面的文档中会解释每个元素。

{% extends "base_generic.html" %}

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

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

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

变量

变量看起来就像是这样: {{ variable }}当模版引擎遇到一个变量,它将计算这个变量,然后用结果替换掉它本身。 变量的命名包括任何字母数字以及下划线 ("_")的组合。 点(".") 也在会变量部分中出现,不过它有特殊的含义,我们将在后面说明。 重要的, 变量名称中不能有空格或标点符号。在前文的例子中, {{ section.title }}将被替换为 title 对象的 section 属性。

过滤器

您可以通过使用 过滤器来改变变量的显示。

过滤器看起来是这样的:{{ name|lower }}这将在变量 {{ name }} 被过滤器 lower 过滤后再显示它的值,该过滤器将文本转换成小写。 使用管道符号 (|)来应用过滤器。

过滤器可以“链接”。一个过滤器的输出应用于下一个过滤器。 {{ text|escape|linebreaks }} 就是一个常用的过滤器链,它编码文本内容,然后把行打破转成<p> 标签。

一些过滤器带有参数。 过滤器的参数看起来像是这样: {{ bio|truncatewords:30 }}这将显示 bio 变量的前30个词。

过滤器参数包含空格的话,必须被引号包起来;例如,使用逗号和空格去连接一个列表中的元素,你需要使用 {{ list|join:", " }}

常用模板过滤器

默认

如果一个变量是false或者为空,使用给定的默认值。 否则,使用变量的值。 像这样:

{{ value|default:"nothing" }}

如果 value没有被提供,或者为空, 上面的例子将显示“nothing”。

长度

返回值的长度。 它对字符串和列表都起作用。 像这样:

{{ value|length }}

如果 value['a', 'b', 'c', 'd'],那么输出是 4

filesizeformat

格式化为“人类可读”文件大小(即'13 KB't4> MB''102 bytes'等)。 像这样:

{{ value|filesizeformat }}

如果 value 是 123456789,输出将会是 117.7 MB

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

标签

标签看起来像是这样的: {% tag %}标签比变量复杂得多:有些用于在输出中创建文本,有些用于控制循环或逻辑,有些用于加载外部信息到模板中供以后的变量使用。

一些标签需要开始和结束标签(即 {% 标签 %} ... 标签 内容 ... {% ENDTAG %})。

常用标签,更多-->>http://usyiyi.cn/translate/Django_111/ref/templates/builtins.html#ref-templates-builtins-tags

对于循环数组中的每个元素。 例如,显示 athlete_list中提供的运动员列表:

for

<ul>
{% for athlete in athlete_list %}
    <li>{{ athlete.name }}</li>
{% endfor %}
</ul>
变量     描述
forloop.counter     循环的当前迭代(1索引)
forloop.counter0     循环的当前迭代(0索引)
forloop.revcounter     循环结束的迭代次数(1索引)
forloop.revcounter0     循环结束的迭代次数(0索引)
forloop.first     如果这是第一次通过循环,则为真
forloop.last     如果这是最后一次循环,则为真
forloop.parentloop     对于嵌套循环,这是围绕当前循环的循环

if,elif和else

计算一个变量,并且当变量是“true”时,显示块中的内容:

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

在上面的例子中,如果 athlete_list 不是空的,运动员的数量将显示为 {{ athlete_list|length }} 的输出。 否则,如果athlete_in_locker_room_list不为空,将显示“运动员应该出...”。 如果两个列表都是空的,将显示 “No athletes.” 。

block

block标签可以被子模板覆盖. 

comment

{% comment %}{% endcomment %},之间的内容会被忽略,作为注释。 在第一个标签可以插入一个可选的记录。 比如,当要注释掉一些代码时,可以用此来记录代码被注释掉的原因。

例如:

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

comment标签不能嵌套使用。

csrf_token

这个标签用于跨站请求伪造保护

extends

表示当前模板继承自一个父模板

注释

要注释模版中一行的部分内容,使用注释语法 {# #}.

例如,这个模版将被渲染为 'hello'

{# greeting #}hello

3.模板继承

Django模版引擎中最强大也是最复杂的部分就是模版继承了。 模版继承可以让您创建一个基本的“骨架”模版,它包含您站点中的全部元素,并且可以定义能够被子模版覆盖的 blocks

通过从下面这个例子开始,可以容易的理解模版继承:

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

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

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

这个模版,我们把它叫作 base.html, 它定义了一个可以用于两列排版页面的简单HTML骨架。 “子模版”的工作是用它们的内容填充空的blocks。

在这个例子中, block 标签定义了三个可以被子模版内容填充的block。 block 告诉模版引擎: 子模版可能会覆盖掉模版中的这些位置。

子模版可能看起来是这样的:

{% extends "base.html" %}

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

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

extends 标签是这里的关键。 它告诉模版引擎,这个模版“继承”了另一个模版。 当模版系统处理这个模版时,首先,它将定位父模版——在此例中,就是“base.html”。

那时,模版引擎将注意到 base.html 中的三个 block 标签,并用子模版中的内容来替换这些block。

请注意,子模版并没有定义 sidebar block,所以系统使用了父模版中的值。 父模版的 {% block %} 标签中的内容总是被用作备选内容(fallback)。

您可以根据需要使用多级继承。 使用继承的一个常用方式是类似下面的三级结构:

  • 创建一个 base.html 模版来控制您整个站点的主要视觉和体验。
  • 为您的站点的每一个“分支”创建一个base_SECTIONNAME.html 模版。 例如, base_news.html, base_sports.html这些模版都继承自 base.html ,并且包含了每部分特有的样式和设计。
  • 为每一种页面类型创建独立的模版,例如新闻内容或者博客文章。 这些模版继承对应分支的模版。

这种方式使代码得到最大程度的复用,并且使得添加内容到共享的内容区域更加简单,例如分支范围内的导航。

这里是使用继承的一些提示:

  • 如果你在模版中使用 {% extends %} 标签,它必须是模版中的第一个标签。 其他的任何情况下,模版继承都将无法工作。

  • 在base模版中设置越多的 {% block %} 标签越好。 请记住,子模版不必定义全部父模版中的blocks,所以,你可以在大多数blocks中填充合理的默认内容,然后,只定义你需要的那一个。 多一点钩子总比少一点好。

  • 如果你发现你自己在大量的模版中复制内容,那可能意味着你应该把内容移动到父模版中的一个 {% block %} 中。

  • 如果需要获取父模板中的block 的内容,可以使用{{ block.super }} 变量。 如果你想要在父block 中新增内容而不是完全覆盖它,它将非常有用。 使用{{ block.super }} 插入的数据不会被自动转义,因为父模板中的内容已经被转义。

  • {% block %}之外创建的变量使用模板标签as语法不能在块内使用。 例如,此模板不会显示任何内容:

最后,请注意不能在一个模版中定义多个相同名字的block 标签。 这个限制的存在是因为block标签的作用是“双向”的。 这个意思是,block 标签不仅提供了一个坑去填,它定义向父模版的坑中所填的内容。 如果在一个模版中有两个名字一样的 block 标签,模版的父模版将不知道使用哪个block的内容。

include

加载模板并以标签内的参数渲染。 这是一种可以引入别的模板的方法。

模板名可以是变量或者是硬编码的字符串,可以用单引号也可以是双引号.

下面这个示例包括模板"foo/bar.html"的内容:

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

load

加载自定义模板标签集。

举个例子, 下面这模板将会从package包中载入所有otherlibrarysomelibrary 中已经注册的标签和过滤器:

{% load somelibrary package.otherlibrary %}

4.自动html转义

当从模版中生成HTML时,总会有这样一个风险:值可能会包含影响HTML最终呈现的字符。 例如,思考这个模版片段:

Hello, {{ name }}

首先,它看起来像是一个无害的方式来显示用户的名字,但是设想一下,如果用户像下面这样输入他的名字,会发生什么:

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

...这意味着浏览器会弹出一个JavaScript警报框!

显然,用户提交的数据都被不应该被盲目的信任,并且被直接插入到你的网页中,因为一个怀有恶意的用户可能会使用这样的漏洞来做一些可能的坏事。 这种类型的安全问题被叫做 跨站脚本(Cross Site Scripting) (XSS) 攻击。

为避免这个问题,你有两个选择:

  • 第一, 你可以对每个不被信任的值运行escape 过滤器(下面的文档中将提到),它将把潜在的有害HTML 字符转换成无害的。 在Django 最初的几年里,这是默认的解决方案,但问题是它将责任放在你们这些开发人员/模板作者身上,以确保转义了所有内容。 而且很容易忘记转义数据。
  • 第二,你可以利用Django的自动HTML转义。 本节其余部分描述自动转义是如何工作的。

默认情况下,Django 中的每个模板会自动转义每个变量的输出。 明确地说,下面五个字符被转义:

  • < 会转换为&lt;
  • > 会转换为&gt;
  • '(单引号)转换为&#39;
  • " (双引号)会转换为 &quot;
  • & 会转换为 &amp;

我们要再次强调这个行为是默认打开的。 如果你使用Django的模板系统,会处于保护之下。

如何关闭

如果你不希望数据自动转义,无论是在站点、模板还是变量级别,你可以使用几种方法来关闭它。

然而你为什么想要关闭它呢? 由于有时,模板变量含有一些你打算渲染成原始HTML的数据,你并不想转义这些内容。 例如,你可能会在数据库中储存一些HTML代码,并且直接在模板中嵌入它们。 或者,你可能使用Django的模板系统来生成不是HTML的文本 -- 比如邮件信息。

对于单个变量

使用safe过滤器来关闭独立变量上的自动转义:

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

对于模板库

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

autoescape标签接受on 或者 off作为它的参数。 有时你可能想在自动转义关闭的情况下强制使用它。 下面是一个模板的示例

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

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

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

自动转义标签作用于扩展了当前模板的模板,以及通过 include 标签包含的模板,就像所有block标签那样。 像这样:

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

5.自定义模板标签和过滤器

指定自定义模板标签和过滤器的最常见的地方在Django应用程序中。 如果它们与现有的应用程序相关联,则将它们捆绑在一起是有意义的;否则,它们可以添加到新的应用程序。 当将Django应用程序添加到INSTALLED_APPS中时,在下面描述的常规位置中定义的任何标签将自动在模板中加载。

这个应用应该包含一个templatetags 目录,和views.pymodels.py等文件处于同一级别目录下。 如果目录不存在则创建它——不要忘记创建__init__.py 文件以使得该目录可以作为Python 的包。

你的自定义的标签和过滤器将放在templatetags 目录下的一个模块里。 这个模块的名字是你稍后将要载入标签时使用的,所以要谨慎的选择名字以防与其他应用下的自定义标签和过滤器名字冲突。

例如,你的自定义标签/过滤器在一个名为poll_extras.py的文件中,那么你的app目录结构看起来应该是这样的:

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

然后你可以在模板中像如下这样使用:

{% load poll_extras %}

(1)编写自定义过滤器

自定义过滤器就是一个带有一个或两个参数的Python 函数:

  • (输入的)变量的值 —— 不一定是字符串形式。
  • 参数的值 —— 可以有一个初始值,或者完全不要这个参数。

例如,在{{ var|foo:"bar" }}中,foo过滤器应当传入变量var和参数 "bar"

由于模板语言没有提供异常处理,任何从过滤器中抛出的异常都将会显示为服务器错误。 因此,如果有合理的值可以返回,过滤器应该避免抛出异常。 在模板中有一个明显错误的情况下,引发一个异常可能仍然要好于用静默的失败来掩盖错误。

这是一个定义过滤器的例子:

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

下面是这个过滤器应该如何使用:

{{ somevariable|cut:"0" }}

大多数过滤器没有参数。 在这种情况下,你的函数不带这个参数即可。 例如:

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

(2)注册自定义过滤器

django.template.Library.filter()

一旦你写好了你的自定义过滤器函数,你就开始需要把它注册为你的 Library实例,来让它在Django模板语言中可用:

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

Library.filter()方法需要两个参数:

  1. 过滤器的名称(一个字符串对象)
  2. 编译的函数 – 一个Python函数(不要把函数名写成字符串)

你还可以把register.filter()用作装饰器:

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

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

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

(3)编写自定义模板标签

标签比过滤器更复杂,因为标签可以做任何事情。

简单标签

django.template.Library.simple_tag()

许多模板标签需要许多参数 - 字符串或模板变量,并且仅在基于输入参数和一些外部信息进行一些处理后返回结果。 例如,current_time 标签可能接受一个格式字符串,并返回与之对应的格式化后的时间。

为了简单化这些类型标签的创建,Django 提供一个辅助函数simple_tag这个函数是django.template.Library 的一个方法,接受一个任意数目的参数的函数,将其包装在一个render 函数和上面提到的其他必要部分中,并在模板系统中注册它。

我们的current_time 函数从而可以这样写

import datetime
from django import template

register = template.Library()

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

关于simple_tag 辅助函数几件值得注意的事项︰

  • 检查所需参数的数量等等,在我们的函数调用的时刻已经完成,所以我们不需要做了。
  • 参数(如果有)的引号都已经被截掉,所以我们收到的只是一个普通字符串。
  • 如果该参数是一个模板变量,传递给我们的函数是当前变量的值,不是变量本身。

和其他标签程序不同, 如果模板上下文中开启了自动转义模式 simple_tag 的输出将通过conditional_escape() 转义, 来保证正确的HTML和防御XSS漏洞.

如果不需要额外的转义,您将需要使用mark_safe(),如果您绝对确保您的代码不包含XSS漏洞。 要建立小型HTML片段,强烈建议您使用format_html()而不是mark_safe()

如果你的模板标签需要访问当前上下文,你可以在注册标签时使用takes_context 参数︰

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

 表单

GET和POST

处理表单时候只会用到POSTGET 方法。

Django 的登录表单使用POST 方法,在这个方法中浏览器组合表单数据、对它们进行编码以用于传输、将它们发送到服务器然后接收它的响应。

相反,GET 组合提交的数据为一个字符串,然后使用它来生成一个URL。 这个URL 将包含数据发送的地址以及数据的键和值。 如果你在Django 文档中做一次搜索,你会立即看到这点,此时将生成一个https://docs.djangoproject.com/search/?q=forms&release=1 形式的URL。

POSTGET 用于不同的目的。

用于改变系统状态的请求 —— 例如,给数据库带来变化的请求 —— 应该使用POSTGET 只应该用于不会影响系统状态的请求。

GET 还不适合密码表单,因为密码将出现在URL 中,以及浏览器的历史和服务器的日志中,而且都是以普通的文本格式。 它还不适合数据量大的表单和二进制数据,例如一张图片。 使用GET 请求作为管理站点的表单具有安全隐患:攻击者很容易模拟表单请求来取得系统的敏感数据。 POST,如果与其它的保护措施结合将对访问提供更多的控制,例如Django 的CSRF protection

另一个方面,GET 适合网页搜索这样的表单,因为这种表示一个GET 请求的URL 可以很容易地作为书签、分享和重新提交。

Django在表单中的角色

处理表单是一件很复杂的事情。 考虑一下Django 的Admin 站点,不同类型的大量数据项需要在一个表单中准备好、渲染成HTML、使用一个方便的界面编辑、返回给服务器、验证并清除,然后保存或者向后继续处理。

Django 的表单功能可以简化并自动化大部分这些工作,而且还可以比大部分程序员自己所编写的代码更安全。

Django 会处理表单工作中的三个显著不同的部分:

  • 准备数据、重构数据,以便下一步提交。
  • 为数据创建HTML 表单
  • 接收并处理客户端提交的表单和数据

可以手工编写代码来实现,但是Django 可以帮你完成所有这些工作。

Django的Form类

表单系统的核心部分是Django 的Form 类。 Django 的模型描述一个对象的逻辑结构、行为以及展现给我们的方式,与此类似,Form 类描述一个表单并决定它如何工作和展现。

就像模型类的属性映射到数据库的字段一样,表单类的字段会映射到HTML 的<input>表单的元素。 ModelForm 通过一个Form 映射模型类的字段到HTML 表单的<input> 元素;Django 的Admin 站点就是基于这个)。

一个表单的字段本身就是类;他们管理表单数据,并在提交表单时执行验证。 DateFieldFileField 处理的数据类型差别很大,必须完成不同的事情。

表单字段在浏览器中呈现给用户的是一个HTML 的“widget” —— 用户界面的一个片段。 每个字段类型都有一个合适的默认Widget class,需要时可以覆盖。

实例化、处理和渲染表单

在Django 中渲染一个对象时,我们通常:

  1. 在视图中获得它(例如,从数据库中获取)
  2. 将它传递给模板的context
  3. 使用模板变量将它扩展为HTML 标记

除了几个关键点不同之外,在模板中渲染表单和渲染其它类型的对象几乎一样。

在模型实例不包含数据的情况下,在模板中对它做处理很少有什么用处。 但是渲染一个未填充的表单却非常有意义 —— 我们希望用户去填充它。

所以当我们在视图中处理模型实例时,我们一般从数据库中获取它。 当我们处理表单时,我们一般在视图中实例化它。

当我们实例化表单时,我们可以选择让它为空还是预先填充它,例如使用:

  • 来自一个保存后的模型实例的数据(例如用于编辑的管理表单)
  • 我们从其它地方获得的数据
  • 从前面一个HTML 表单提交过来的数据

获取HTML表单数据是最有趣的,因为这样做可以让用户不仅可以阅读网站,还可以将信息发送回来。

假设您想在您的网站上创建一个简单的表单,以获取用户的名字。 你需要类似这样的模板:

<form action="/your-name/" method="post">
    <label for="your_name">Your name: </label>
    <input id="your_name" type="text" name="your_name" value="{{ current_name }}">
    <input type="submit" value="OK">
</form>

这告诉浏览器使用POST方法将表单数据返回到URL / your-name /它将显示一个标签为"Your name:"的文本字段,和一个"OK"按钮。 如果模板上下文包含current_name变量​​,则将用于预填your_name字段。

您将需要一个视图来渲染包含HTML表单的模板,并且可以根据需要提供current_name字段。

当表单提交时,发往服务器的POST 请求将包含表单数据。

现在你还需要一个对应/your-name/ URL 的视图,它在请求中找到正确的键/值对,然后处理它们。

这是一个非常简单的表单。 实际应用中,一个表单可能包含几十上百个字段,其中大部分需要预填充,而且我们预料到用户将来回编辑-提交几次才能完成操作。

即使在提交表单之前,我们也可能需要在浏览器中进行一些验证。我们可能想要使用更复杂的字段,这样可以让用户做一些事情,例如从日历中选择日期等等。

这个时候,让Django 来为我们完成大部分工作是很容易的。

 1.创建一个表单

(1)form.py

我们已经计划好了我们的 HTML 表单应该呈现的样子。 在Django 中,我们的起始点是这里:

from django import forms

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

它定义一个Form 类,只带有一个字段(your_name)。 我们已经对这个字段使用一个人性化的标签,当渲染时它将出现在<label> 中(在这个例子中,即使我们省略它,我们指定的label还是会自动生成)。

字段允许的最大长度通过max_length 定义。 它完成两件事情。 首先,它在HTML 的<input> 上放置一个maxlength="100" (这样浏览器将在第一时间阻止用户输入多于这个数目的字符)。 它还意味着当Django 收到浏览器发送过来的表单时,它将验证数据的长度。

Form 的实例具有一个is_valid() 方法,它为所有的字段运行验证的程序。 当调用这个方法时,如果所有的字段都包含合法的数据,它将:

  • 返回True
  • 将表单的数据放到cleaned_data 属性中。

完整的表单,第一次渲染时,看上去将像:

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

注意它不包含 <form> 标签和提交按钮。 我们必须自己在模板中提供它们。

(2)视图

发送回Django网站的表单数据由视图处理,通常是发布表单的相同视图。 这允许我们重用一些相同的逻辑。

要操作一个通过URL发布的表单,我们要在视图中实例化它。

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

from .forms import NameForm

def get_name(request):
    # 如果这是一个POST请求,我们就需要处理表单数据
    if request.method == 'POST':
        # 创建一个表单实例,并且使用表单数据填充request请求:
        form = NameForm(request.POST)
        # 检查数据有效性:
        if form.is_valid():
            # 在需要时,可以在form.cleaned_date中处理数据
            # ...
            # 重定向到一个新的URL:
            return HttpResponseRedirect('/thanks/')

    # 如果是GET或者其它请求方法,我们将创建一个空的表单。
    else:
        form = NameForm()

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

如果访问视图的是一个GET 请求,它将创建一个空的表单实例并将它放置到要渲染的模板的上下文中。 这是我们在第一次访问该URL 时预期发生的情况。

如果使用POST请求提交表单,该视图将再次创建一个表单实例,并使用请求中的数据填充表单:形式 = NameForm(request.POST)这被称为“将数据绑定到表单”(现在是绑定的形式)。

我们调用窗体的is_valid()方法;如果不是True,我们返回到表单的模板。 这时表单不再为空(未绑定),所以HTML 表单将用之前提交的数据填充,然后可以根据要求编辑并改正它。

如果Trueis_valid(),我们将能够在cleaned_data 属性中找到所有合法的表单数据。 在发送HTTP 重定向给浏览器告诉它下一步的去向之前,我们可以用这个数据来更新数据库或者做其它处理。

(3)模板

我们不需要在name.html 模板中做很多工作。 最简单的例子是:

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

Django会根据模型类的字段和属性,在HTML中自动生成对应表单标签和标签属性。生成的标签会被放置到{{ form }}所在的位置。

现在我们有了一个可以工作的网页表单,它通过Django Form 描述、通过视图处理并渲染成一个HTML <form>

这是你入门所需要知道的所有内容,但是表单框架为了便利提供了更多的内容。 一旦你理解了上面描述的基本处理过程,你应该可以理解表单系统的其它功能并准备好学习更多的底层机制。

 更多字段

考虑一个比我们上面的最小例子更有用的形式,我们可以用它来在个人网站上实现“联系我”功能:

from django import forms

class ContactForm(forms.Form):
    subject = forms.CharField(max_length=100)
    message = forms.CharField(widget=forms.Textarea)
    sender = forms.EmailField()
    cc_myself = forms.BooleanField(required=False)

我们前面的表单只使用一个字段your_name,它是一个CharField在这个例子中,我们的表单具有四个字段:messagesubjectsendercc_myselfCharFieldEmailFieldBooleanField只是三种可用的字段类型

窗口小部件

每个表单字段都有一个对应的Widget class,它对应一个HTML 表单Widget,例如<input type="text">

在大部分情况下,字段都具有一个合理的默认Widget。 例如,默认情况下,CharField 具有一个TextInput Widget,它在HTML 中生成一个<input type="text">如果你需要message,在定义表单字段时你应该指定一个合适的Widget,例如我们定义的<textarea> 字段。

字段数据

不管表单提交的是什么数据,一旦通过调用is_valid() 成功验证(is_valid() 返回True),验证后的表单数据将位于form.cleaned_data 字典中。 这些数据已经为你转换好为Python 的类型。

在上面的联系表单示例中,cc_myself 将是一个布尔值。 类似地,IntegerFieldFloatField 字段分别将值转换为Python 的intfloat

下面是在视图中如何处理表单数据:

from django.core.mail import send_mail

if form.is_valid():
    subject = form.cleaned_data['subject']
    message = form.cleaned_data['message']
    sender = form.cleaned_data['sender']
    cc_myself = form.cleaned_data['cc_myself']

    recipients = ['info@example.com']
    if cc_myself:
        recipients.append(sender)

    send_mail(subject, message, sender, recipients)
    return HttpResponseRedirect('/thanks/')

2.表单API

(1)绑定和绑定形式

Form要么是绑定的,要么是未绑定的

  • 如果是绑定的,那么它能够验证数据,并渲染表单及其数据成HTML。
  • 如果未绑定,则无法进行验证(因为没有数据可以验证!),但它仍然可以以HTML形式呈现空白表

若要创建一个未绑定的Form实例,只需简单地实例化该类:

>>> f = ContactForm()

若要绑定数据到表单,可以将数据以字典的形式传递给Form类的构造函数的第一个参数:

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

如果你有一个绑定的Form实例但是想改下数据,或者你想绑定一个未绑定的Form表单到某些数据,你需要创建另外一个Form实例。 Form 实例的数据没有办法修改。 Form实例一旦创建,你应该将它的数据视为不可变的,无论它有没有数据。

(2)使用表单验证数据

让我们试下非法的数据。 下面的情形中,subject 为空(默认所有字段都是必需的)且sender 是一个不合法的邮件地址:

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

访问errors 属性可以获得错误信息的一个字典:

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

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

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

返回JSON 序列化后的错误。

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

(3)访问干净数据

Form.cleaned_data

Form类中的每个字段不仅负责验证数据,还负责“清洁”它们 —— 将它们转换为正确的格式。 这是个非常好用的功能,因为它允许字段以多种方式输入数据,并总能得到一致的输出。

例如,DateField 将输入转换为Python 的 datetime.date 对象。 无论你传递的是DateField 格式的字符串、datetime.date 对象、还是其它格式的数字,'1994-07-15' 将始终将它们转换成datetime.date 对象,只要它们是合法的。

一旦你创建一个Form实例并通过验证后,你就可以通过它的cleaned_data 属性访问清洁的数据:

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

(4)输出表单为HTML

as_p()

Form.as_p()

<p> 渲染表单为一系列的<p> 标签,每个as_p() 标签包含一个字段:

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

as_ul()

Form.as_ul()

<li> 渲染表单为一系列的<li>标签,每个as_ul() 标签包含一个字段。 包含</ul><ul>,所以你可以自己指定<ul> 的任何HTML 属性:

as_table()

Form. as_table T0>()

最后,as_table()输出表单为一个HTML <table>它与print 完全相同。 事实上,当你print 一个表单对象时,在后台调用的就是as_table() 方法:

3.表单字段

class Field(**kwargs)[source]

创建一个Form类时,最重要的部分是定义表单的字段。 每个字段都可以有自定义的验证逻辑,以及一些其它的钩子。

Field.clean(value)[source]

虽然Field类主要使用在Form类中,但你也可以直接实例化它们来使用,以便更好地了解它们是如何工作的。 每个django.forms.ValidationError实例都有一个clean()方法, 它接受一个参数,然后返回“清洁的”数据或者抛出一个Field异常:

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

核心字段参数

每个Field类的构造函数至少接受这些参数。 有些Field类接受额外的、字段特有的参数,但以下参数应该总是能接受:

required

Field.required

默认情况下,每个"" 类都假设必需有值,所以如果你传递一个空的值 —— 不管是None 还是空字符串(Field) —— clean() 将引发一个ValidationError 异常:

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

若要表示一个字段是必需的,请传递Fieldrequired=False 的构造函数:

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

如果ValidationError 具有clean(),而你传递给required=False 一个空值,Field 将返回一个转换后的空值而不是引发clean()例如CharField,它将是一个空的Unicode 字符串。 对于其它Field类,它可能是None(每个字段各不相同)。

label

Field.label

label 参数让你指定字段“对人类友好”的label。 FieldForm中显示时将用到它。

正如在前面“输出表单为HTML”中解释的,Field默认label 是通过将字段名中所有的下划线转换成空格并大写第一个字母生成的。 如果默认的标签不合适,可以指定label

下面是一个完整示例,Form为它的两个字段实现了label我们指定auto_id=False来让输出简单一些:

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

label_suffix

Field.label_suffix

label_suffix 参数让你基于每个字段覆盖表单的label_suffix

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

initial

Field.initial

Form 参数让你指定渲染未绑定的Field中的initial时使用的初始值。

若要指定动态的初始数据,参见Form.initial 参数。

这个参数的使用场景是当你想要显示一个“空”的表单,其某个字段初始化为一个特定的值。 像这样:

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

widget

Field.widget

Field 参数让你指定渲染Widget时使用的widget

help_text

Field.help_text

help_text 参数让你指定Field的描述文本。 如果提供Field,在通过Field的便捷方法(例如,help_text)渲染Form时,它将紧接着as_ul()显示。

像模型字段的help_text一样,此值不会以自动生成的形式进行HTML转义。

下面是一个完整的示例,Form为它的两个字段实现了help_text我们指定auto_id=False来让输出简单一些:

>>> from django import forms
>>> class HelpTextContactForm(forms.Form):
...     subject = forms.CharField(max_length=100, help_text='100 characters max.')
...     message = forms.CharField()
...     sender = forms.EmailField(help_text='A valid email address, please.')
...     cc_myself = forms.BooleanField(required=False)
>>> f = HelpTextContactForm(auto_id=False)
>>> print(f.as_table())
<tr><th>Subject:</th><td><input type="text" name="subject" maxlength="100" required /><br /><span class="helptext">100 characters max.</span></td></tr>
<tr><th>Message:</th><td><input type="text" name="message" required /></td></tr>
<tr><th>Sender:</th><td><input type="email" name="sender" required /><br />A valid email address, please.</td></tr>
<tr><th>Cc myself:</th><td><input type="checkbox" name="cc_myself" /></td></tr>
>>> print(f.as_ul()))
<li>Subject: <input type="text" name="subject" maxlength="100" required /> <span class="helptext">100 characters max.</span></li>
<li>Message: <input type="text" name="message" required /></li>
<li>Sender: <input type="email" name="sender" required /> A valid email address, please.</li>
<li>Cc myself: <input type="checkbox" name="cc_myself" /></li>
>>> print(f.as_p())
<p>Subject: <input type="text" name="subject" maxlength="100" required /> <span class="helptext">100 characters max.</span></p>
<p>Message: <input type="text" name="message" required /></p>
<p>Sender: <input type="email" name="sender" required /> A valid email address, please.</p>
<p>Cc myself: <input type="checkbox" name="cc_myself" /></p>

error_messages

Field.error_messages

error_messages 参数让你覆盖字段引发的异常中的默认信息。 传递的是一个字典,其键为你想覆盖的错误信息。 例如,下面是默认的错误信息:

>>> from django import forms
>>> generic = forms.CharField()
>>> generic.clean('')
Traceback (most recent call last):
  ...
ValidationError: ['This field is required.']

而下面是自定义的错误信息:

>>> name = forms.CharField(error_messages={'required': 'Please enter your name'})
>>> name.clean('')
Traceback (most recent call last):
  ...
ValidationError: ['Please enter your name']

validators

Field.validators

validators 参数让你可以为字段提供一个验证函数的列表。

localize

Field.localize

localize参数可以实现表单数据输入的定位,以及渲染输出。

disabled

Field.disabled

disabled布尔参数,当设置为True时,使用disabled HTML属性禁用表单域,以使用户无法编辑。 即使用户篡改了提交给服务器的字段的值,它也将被忽略,有利于表单初始数据中的值。

has_changed()

Field.has_changed()[source]

has_changed() 方法用于决定字段的值是否从初始值发生了改变。 返回TrueFalse

4.内置Field类

BooleanField

class BooleanField(**kwargs)[source]
  • 默认的Widget:CheckboxInput
  • 空值:False
  • 规范化为:Python 的True 或 False
  • 如果字段带有True,验证值是否为required=True(例如复选框被勾上)。
  • 错误信息的键:required

CharField

class CharField(**kwargs)[source]
  • 默认的Widget:TextInput
  • 空值:与empty_value给出的任何值。
  • 规范化为:一个Unicode 对象。
  • 如果提供,验证max_length 或min_length。 否则,所有的输入都是合法的。
  • 错误信息的键:min_lengthmax_lengthrequired

有三个可选参数进行验证:

max_length
min_length

如果提供,这两个参数将确保字符串的最大和最小长度。

strip

如果True(默认),该值将被剥离前导和尾随空格。

empty_value
Django中的新功能1.11。

用来表示“空”的值。 默认为空字符串。

ChoiceField

class ChoiceField(**kwargs)[source]
  • 默认的Widget:Select
  • 空值:''(一个空字符串)
  • 规范化为:一个Unicode 对象。
  • 验证给定的值在选项列表中存在。
  • 错误信息的键:requiredinvalid_choice

invalid_choice 错误消息可能包含%(value)s,它将被选择的选项替换掉。

还有一个参数:

choices

用来作为该字段选项的一个二元组组成的可迭代对象(例如,列表或元组)或者一个可调用对象。 参数的格式与模型字段的choices 参数相同。

TypedChoiceField

class TypedChoiceField(**kwargs)[source]

就像ChoiceField一样,除了TypedChoiceField还有两个额外的参数:coerceempty_value

  • 默认的Widget:Select
  • 空值:与empty_value给出的任何值。
  • 规范化为:coerce 参数类型的值。
  • 验证给定的值在选项列表中存在并且可以被强制转换。
  • 错误信息的键:requiredinvalid_choice

接收的额外参数:

coerce

接收一个参数并返回强制转换后的值的一个函数。 例如内建的boolfloatint 和其它类型。 默认为id 函数。 注意强制转换在输入验证结束后发生,所以它可能强制转换不在 choices 中的值。

empty_value

用于表示“空”的值。默认为空字符串; None是这里的另一个常见选择。 注意这个值不会被coerce 参数中指定的函数强制转换,所以请根据情况进行选择。

DateField

class DateField(**kwargs)[source]
  • 默认的Widget:DateInput
  • 空值:None
  • 规范化为:一个Python datetime.date 对象。
  • 验证给出的值是一个datetime.datedatetime.datetime 或指定日期格式的字符串。
  • 错误信息的键:requiredinvalid

接收一个可选的参数:

input_formats

一个格式的列表,用于转换一个字符串为datetime.date 对象。

如果没有提供input_formats,默认的输入格式为:

['%Y-%m-%d',      # '2006-10-25'
 '%m/%d/%Y',      # '10/25/2006'
 '%m/%d/%y']      # '10/25/06'

DateTimeField

class DateTimeField(**kwargs)[source]
  • 默认的Widget:DateTimeInput
  • 空值:None
  • 规范化为:一个Python datetime.datetime 对象。
  • 验证给出的值是一个datetime.datetimedatetime.date 或指定日期格式的字符串。
  • 错误信息的键:requiredinvalid

接收一个可选的参数:

input_formats

一个格式的列表,用于转换一个字符串为datetime.datetime 对象。

如果没有提供input_formats,默认的输入格式为:

['%Y-%m-%d %H:%M:%S',    # '2006-10-25 14:30:59'
 '%Y-%m-%d %H:%M',       # '2006-10-25 14:30'
 '%Y-%m-%d',             # '2006-10-25'
 '%m/%d/%Y %H:%M:%S',    # '10/25/2006 14:30:59'
 '%m/%d/%Y %H:%M',       # '10/25/2006 14:30'
 '%m/%d/%Y',             # '10/25/2006'
 '%m/%d/%y %H:%M:%S',    # '10/25/06 14:30:59'
 '%m/%d/%y %H:%M',       # '10/25/06 14:30'
 '%m/%d/%y']             # '10/25/06'
DecimalField 
class DecimalField(**kwargs)[source]
  • 默认的Widget:当Field.localize 是False 时为NumberInput,否则为TextInput
  • 空值:None
  • 规范化为:一个Python decimal
  • 验证给定的值为一个十进制数。 忽略前导和尾随的空白。
  • 错误信息的键:max_whole_digitsmax_digitsmax_decimal_placesmax_valueinvalidrequiredmin_value

%(limit_value)s 和min_value 错误信息可能包含max_value,它们将被真正的限制值替换。 类似地,max_whole_digitsmax_decimal_places 和 max_digits 错误消息可能包含%(max)s

接收四个可选的参数:

max_value
min_value

它们控制字段中允许的值的范围,应该以decimal.Decimal 值给出。

max_digits

值允许的最大位数(小数点之前和之后的数字总共的位数,前导的零将被删除)。

decimal_places

允许的最大小数位。

DurationField

class DurationField(**kwargs)[source]
  • 默认的Widget:TextInput
  • 空值:None
  • 规范化为:一个Python timedelta
  • 验证给出的值是一个字符串,而可以给转换为timedelta
  • 错误信息的键:requiredinvalid.

接收任何可以被parse_duration() 理解的格式。

EmailField

class EmailField(**kwargs)[source]
  • 默认的Widget:EmailInput
  • 空值:''(一个空字符串)
  • 规范化为:一个Unicode 对象。
  • 验证给出的值是一个合法的邮件地址,使用一个适度复杂的正则表达式。
  • 错误信息的键:requiredinvalid

具有两个可选的参数用于验证,max_length 和min_length。 如果提供,这两个参数将确保字符串的最大和最小长度。

FileField

class FileField(**kwargs)[source]
  • 默认的Widget:ClearableFileInput
  • 空值:None
  • 规范化为:一个UploadedFile 对象,它封装文件内容和文件名为一个单独的对象。
  • 可以验证非空的文件数据已经绑定到表单。
  • 错误信息的键:missinginvalidrequiredemptymax_length

具有两个可选的参数用于验证,max_length 和 allow_empty_file。 如果提供,这两个参数确保文件名的最大长度,而且即使文件内容为空时验证也会成功。

FloatField

class FloatField(**kwargs)[source]
  • 默认的Widget:当Field.localize 是False 时为NumberInput,否则为TextInput
  • 空值:None
  • 规范化为:一个Float 对象。
  • 验证给定的值是一个浮点数。 和Python 的float() 函数一样,允许前导和尾随的空白符。
  • 错误信息的键:max_valueinvalidrequiredmin_value

接收两个可选的参数用于验证,max_value 和min_value。 它们控制字段中允许的值的范围。

IntergerField

class IntegerField(**kwargs)[source]
  • 默认的Widget:当Field.localize 是False 时为NumberInput,否则为TextInput
  • 空值:None
  • 规范化为:一个Python 整数或长整数。
  • 验证给定值是一个整数。 允许前导和尾随空格,如Python的int()函数。
  • 错误信息的键:max_valueinvalidrequiredmin_value

%(limit_value)s 和min_value 错误信息可能包含max_value,它们将被真正的限制值替换。

采用两个可选参数进行验证:

max_value
min_value

它们控制字段中允许的值的范围。

GenericIPAddressField

class GenericIPAddressField(**kwargs)[source]

包含IPv4或IPv6地址的字段。

  • 默认的Widget:TextInput
  • 空值:''(一个空字符串)
  • 规范化为:一个Unicode 对象。 IPv6地址如下所述进行归一化。
  • 验证给定值是有效的IP地址。
  • 错误信息的键:requiredinvalid

IPv6地址规范化遵循 RFC 4291#section-2.2第2.2节,包括使用该段第3段中建议的IPv4格式,如::ffff:192.0.2.0 例如,::ffff:0a0a:0a0a将被标准化为2001::12001:0::0:01 ::ffff:10.10.10.10。 所有字符都转换为小写。

有两个可选参数:

protocol

限制指定协议的有效输入。 接受的值为IPv6(默认值),IPv4both。 匹配不区分大小写。

unpack_ipv4

解开IPv4映射地址,例如::ffff:192.0.2.1。 如果启用此选项,则该地址将解包到192.0.2.1。 默认为禁用。 只能在protocol设置为'both'时使用。

MultipleChoiceField

class MultipleChoiceField(**kwargs)[source]
  • 默认的Widget:SelectMultiple
  • 空值:[](一个空列表)
  • 规范化为:一个Unicode 对象列表。
  • 验证给定值列表中的每个值都存在于选择列表中。
  • 错误信息的键:invalid_listinvalid_choicerequired

invalid_choice 错误消息可能包含%(value)s,它将被选择的选项替换掉。

对于choices,需要一个额外的必需参数ChoiceField

TypedMultipleChoiceField

class TypedMultipleChoiceField(**kwargs)[source]

就像MultipleChoiceField,除了TypedMultipleChoiceField需要两个额外的参数,coerceempty_value

  • 默认的Widget:SelectMultiple
  • 空值:empty_value
  • 规范化为:coerce参数提供的类型值列表。
  • 验证给定值存在于选项列表中并且可以强制。
  • 错误信息的键:requiredinvalid_choice

invalid_choice 错误消息可能包含%(value)s,它将被选择的选项替换掉。

对于TypedChoiceField,需要两个额外的参数empty_valuecoerce

RegexField

class RegexField(**kwargs)[source]
  • 默认的Widget:TextInput
  • 空值:''(一个空字符串)
  • 规范化为:一个Unicode 对象。
  • 验证给定值与某个正则表达式匹配。
  • 错误信息的键:requiredinvalid

需要一个必需的参数:

regex

指定为字符串或编译的正则表达式对象的正则表达式。

还需要max_lengthmin_lengthstrip,它们与CharField一样工作。

strip

默认为False。 如果启用,则将在正则表达式验证之前应用剥离

SlugField

class SlugField(**kwargs)[source]
  • 默认的Widget:TextInput
  • 空值:''(一个空字符串)
  • 规范化为:一个Unicode 对象。
  • 验证给定的字符串只包括字母、数字、下划线及连字符。
  • 错误信息的键:requiredinvalid

此字段用于在表单中表示模型SlugField

使用可选参数:

allow_unicode

布尔型指令除了ASCII字母外,还可以接受Unicode字母。 默认为False

TimeField

class TimeField(**kwargs)[source]
  • 默认的Widget:TextInput
  • 空值:None
  • 规范化为:一个Python 的datetime.time 对象。
  • 验证给定值是datetime.time或以特定时间格式格式化的字符串。
  • 错误信息的键:requiredinvalid

接收一个可选的参数:

input_formats

用于尝试将字符串转换为有效的datetime.time对象的格式列表。

如果没有提供input_formats,默认的输入格式为:

URLField

class URLField(**kwargs)[source]
  • 默认的Widget:URLInput
  • 空值:''(一个空字符串)
  • 规范化为:一个Unicode 对象。
  • 验证给定值是有效的URL。
  • 错误信息的键:requiredinvalid

采用以下可选参数:

max_length
min_length

这些与CharField.max_lengthCharField.min_length相同。

更多-->>http://usyiyi.cn/translate/Django_111/ref/forms/fields.html

5.窗口小部件

不要将Widget 与form fields搞混淆。 表单字段负责验证输入并直接在模板中使用。 Widget 负责渲染网页上HTML 表单的输入元素和提取提交的原始数据

每当你指定表单的一个字段的时候,Django 将使用适合其数据类型的默认Widget。然而,如果你想要使用一个不同的Widget,你可以在定义字段时使用widget 参数。 像这样:

from django import forms

class CommentForm(forms.Form):
    name = forms.CharField()
    url = forms.URLField()
    comment = forms.CharField(widget=forms.Textarea)

这将使用一个Textarea Widget来设置表单的评论 ,而不是默认的TextInput Widget

许多小部件具有可选的额外参数;在字段上定义窗口小部件时可以设置它们。 在下面的示例中,设置了SelectDateWidget 的years属性:

from django import forms

BIRTH_YEAR_CHOICES = ('1980', '1981', '1982')
FAVORITE_COLORS_CHOICES = (
    ('blue', 'Blue'),
    ('green', 'Green'),
    ('black', 'Black'),
)

class SimpleForm(forms.Form):
    birth_year = forms.DateField(widget=forms.SelectDateWidget(years=BIRTH_YEAR_CHOICES))
    favorite_colors = forms.MultipleChoiceField(
        required=False,
        widget=forms.CheckboxSelectMultiple,
        choices=FAVORITE_COLORS_CHOICES,
    )

(1)小部件继承自select小部件

继承自Select 的Widget 负责处理HTML 选项。 它们呈现给用户一个可以选择的选项列表。 不同的小部件呈现出不同的选择;Select小部件本身使用<select> HTML列表表示,而RadioSelect使用单选按钮。

ChoiceField 字段默认使用Select。 Widget 上显示的选项来自ChoiceField,对ChoiceField.choices 的改变将更新Select.choices。 像这样:

>>> from django import forms
>>> CHOICES = (('1', 'First',), ('2', 'Second',))
>>> choice_field = forms.ChoiceField(widget=forms.RadioSelect, choices=CHOICES)
>>> choice_field.choices
[('1', 'First'), ('2', 'Second')]
>>> choice_field.widget.choices
[('1', 'First'), ('2', 'Second')]
>>> choice_field.widget.choices = ()
>>> choice_field.choices = (('1', 'First and only',),)
>>> choice_field.widget.choices
[('1', 'First and only')]

提供choices 属性的Widget 也可以用于不是基于选项的字段 , 例如CharField —— 当选项与模型有关而不只是Widget 时,建议使用基于ChoiceField 的字段。

(2)样式化小部件

如果你想让某个Widget 实例与其它Widget 看上去不一样,你需要在Widget 对象实例化并赋值给一个表单字段时指定额外的属性(以及可能需要在你的CSS 文件中添加一些规则)。

例如下面这个简单的表单:

from django import forms

class CommentForm(forms.Form):
    name = forms.CharField()
    url = forms.URLField()
    comment = forms.CharField()

这个表单包含三个默认的TextInput Widget,以默认的方式渲染 —— 没有CSS 类、没有额外的属性。 这表示每个Widget 的输入框将渲染得一模一样:

>>> f = CommentForm(auto_id=False)
>>> f.as_table()
<tr><th>Name:</th><td><input type="text" name="name" required /></td></tr>
<tr><th>Url:</th><td><input type="url" name="url" required /></td></tr>
<tr><th>Comment:</th><td><input type="text" name="comment" required /></td></tr>

在真正得网页中,你可能不想让每个Widget 看上去都一样。 你可能想要给comment 一个更大的输入元素,你可能想让‘name’ Widget 具有一些特殊的CSS 类。 可以指定‘type’ 属性使用的是新式的HTML5 输入类型。 在创建Widget 时使用Widget.attrs 参数可以实现:

class CommentForm(forms.Form):
    name = forms.CharField(widget=forms.TextInput(attrs={'class': 'special'}))
    url = forms.URLField()
    comment = forms.CharField(widget=forms.TextInput(attrs={'size': '40'}))

Django 将在渲染的输出中包含额外的属性:

>>> f = CommentForm(auto_id=False)
>>> f.as_table()
<tr><th>Name:</th><td><input type="text" name="name" class="special" required /></td></tr>
<tr><th>Url:</th><td><input type="url" name="url" required /></td></tr>
<tr><th>Comment:</th><td><input type="text" name="comment" size="40" required /></td></tr>

(3)基于小部件类

Widget 和MultiWidget 是所有built-in widgets 的基类,并可用于自定义Widget 的基类。

Widget

class Widget(attrs=None)[source]

这是个抽象类,它不可以渲染,但是提供基本的属性attrs。 你可以在自定义的Widget 中实现或覆盖render() 方法。

ATTRS T0> 

包含渲染后的Widget 将要设置的HTML 属性。

>>> from django import forms
>>> name = forms.TextInput(attrs={'size': 10, 'title': 'Your name',})
>>> name.render('name', 'A name')
'<input title="Your name" type="text" name="name" value="A name" size="10" required />'

如果你给一个属性赋值True 或False,它将渲染成一个HTML5 风格的布尔属性:

>>> name = forms.TextInput(attrs={'required': True})
>>> name.render('name', 'A name')
'<input name="name" type="text" value="A name" required />'
>>>
>>> name = forms.TextInput(attrs={'required': False})
>>> name.render('name', 'A name')
'<input name="name" type="text" value="A name" />'
supports_microseconds T0> 

属性默认为True。 如果设置为False,则datetimetime值的微秒部分将被设置为0

format_value(value)[source]

清除并返回一个用于小部件模板的值。 value不能保证是有效的输入,因此子类的实现应该防御性地编程。

在Django更改1.10:

在旧版本中,此方法是名为_format_value()的私有API。 旧的名称将工作,直到Django 2.0。

get_contextnamevalueattrs[source] 
Django中的新功能1.11。

返回在渲染窗口小部件模板时要使用的值的字典。 默认情况下,该字典包含一个单一的键'widget',它是包含以下键的小部件的字典表示形式:

  • 'name'name参数中的字段的名称。
  • 'is_hidden':一个布尔值,表示该小部件是否被隐藏。
  • 'required':一个布尔值,表示是否需要此窗口小部件的字段。
  • 'value':由format_value()返回的值。
  • 'attrs':要在已渲染的小部件上设置HTML属性。 attrs属性和attrs参数的组合。
  • 'template_name'self.template_name的值。

Widget子类可以通过覆盖此方法来提供自定义上下文值。

id_for_label(id_)[source]

给定该字段的ID,返回此小部件的HTML ID属性,以供<label>使用。 如果ID不可用,则返回None

这个钩子是必要的,因为一些小部件具有多个HTML元素,因此具有多个ID。 在这种情况下,该方法应该返回与widget的标签中的第一个ID相对应的ID值。

render(namevalueattrs=Nonerenderer=None)[source]

使用给定的渲染器将小部件渲染为HTML。 如果rendererNone,则使用FORM_RENDERER设置中的渲染器。

在Django更改1.11:

添加了renderer参数。 支持不接受的子类将在Django 2.1中被删除。

value_from_datadictdatafilesname[source] 

根据一个字典和该Widget 的名称,返回该Widget 的值。 files可能包含来自request.FILES的数据。 如果没有提供value,则返回None。 在处理表单数据的过程中,value_from_datadict 可能调用多次,所以如果你自定义并添加额外的耗时处理时,你应该自己实现一些缓存机制。

value_omitted_from_data数据文件名称[source] 
Django中的新功能1.10.2。

给定datafiles字典和此小部件的名称,返回是否有数据或文件的小部件。

该方法的结果会影响模型窗体falls back to its default

特殊情况是CheckboxInputCheckboxSelectMultipleSelectMultiple,它始终返回False,因为未选中的复选框并未选择&lt; select multiple&gt;不会出现在HTML表单提交的数据中,因此用户是否提交了值是未知的。

use_required_attribute(initial)[source]
Django中的新功能1.10.1。

给定一个表单域的initial值,返回是否可以使用required 表单使用此方法与Field.requiredForm.use_required_attribute一起确定是否显示每个字段的required属性。

默认情况下,为隐藏的小部件返回False,否则返回True。 特殊情况是ClearableFileInput,当initial未设置时返回FalseCheckboxSelectMultiple,它始终返回False,因为浏览器验证将需要检查所有复选框,而不是至少一个。

在与浏览器验证不兼容的自定义小部件中覆盖此方法。 例如,由隐藏的textarea元素支持的WSYSIWG文本编辑器小部件可能希望始终返回False,以避免在隐藏字段上进行浏览器验证。

MultiWidget

class MultiWidgetwidgetsattrs = None[source] 

由多个Widget 组合而成的Widget。 MultiWidget 始终与MultiValueField 联合使用。

MultiWidget 具有一个必选参数:

widgets

一个包含需要的Widget 的可迭代对象。

以及一个必需的方法:

decompress(value)[source]

这个方法接受来自字段的一个“压缩”的值,并返回“解压”的值的一个列表。 可以假设输入的值是合法的,但不一定是非空的。

子类必须实现 这个方法,而且因为值可能为空,实现必须要防卫这点。

“解压”的基本原理是需要“分离”组合的表单字段的值为每个Widget 的值。

有个例子是,SplitDateTimeWidget 将datetime 值分离成两个独立的值分别表示日期和时间:

from django.forms import MultiWidget

class SplitDateTimeWidget(MultiWidget):

    # ...

    def decompress(self, value):
        if value:
            return [value.date(), value.time().replace(microsecond=0)]
        return [None, None]

它提供一些自定义上下文:

get_contextnamevalueattrs[source] 

除了Widget.get_context()中描述的'widget'之外,MultiValueWidget添加了一个widget['subwidgets']

这些可以在窗口小部件模板中循环:

{% for subwidget in widget.subwidgets %}
    {% include widget.template_name with widget=subwidget %}
{% endfor %}

下面示例中的Widget 继承MultiWidget 以在不同的选择框中显示年、月、日。 这个Widget 主要想用于DateField 而不是MultiValueField,所以我们实现了value_from_datadict()

from datetime import date
from django.forms import widgets

class DateSelectorWidget(widgets.MultiWidget):
    def __init__(self, attrs=None):
        # create choices for days, months, years
        # example below, the rest snipped for brevity.
        years = [(year, year) for year in (2011, 2012, 2013)]
        _widgets = (
            widgets.Select(attrs=attrs, choices=days),
            widgets.Select(attrs=attrs, choices=months),
            widgets.Select(attrs=attrs, choices=years),
        )
        super(DateSelectorWidget, self).__init__(_widgets, attrs)

    def decompress(self, value):
        if value:
            return [value.day, value.month, value.year]
        return [None, None, None]

    def value_from_datadict(self, data, files, name):
        datelist = [
            widget.value_from_datadict(data, files, name + '_%s' % i)
            for i, widget in enumerate(self.widgets)]
        try:
            D = date(
                day=int(datelist[0]),
                month=int(datelist[1]),
                year=int(datelist[2]),
            )
        except ValueError:
            return ''
        else:
            return str(D)

构造器在一个元组中创建了多个Select widget。 super类使用这个元组来启动widget。

必需的decompress()方法将datetime.date 值拆成年、月和日的值,对应每个widget。 注意这个方法如何处理valueNone的情况。

value_from_datadict()的默认实现会返回一个列表,对应每一个Widget。 当和MultiValueField一起使用MultiWidget的时候,这样会非常合理,但是由于我们想要和拥有单一值得DateField一起使用这个widget,我们必须覆写这一方法,将所有子widget的数据组装成datetime.date。 这个方法从POST 字典中获取数据,并且构造和验证日期。 如果日期有效,会返回它的字符串,否则会返回一个空字符串,它会使form.is_valid返回False

6.内建的Wedgit

Django 提供所有基本的HTML Widget,并在django.forms.widgets 模块中提供一些常见的Widget 组,包括the input of textvarious checkboxes and selectorsuploading fileshandling of multi-valued input

TextInput

class TextInput[source]
  • input_type'text'
  • template_name'django/forms/widgets/text.html'
  • 呈现为:<input type =“text” ...>

NumberInput

class NumberInput[source]
  • input_type'number'
  • template_name'django/forms/widgets/number.html'
  • 呈现为:<input type =“number” ...>

注意,不是所有浏览器的number输入类型都支持输入本地化的数字。 Django本身避免将它们用于将localize属性设置为True的字段。

EmailInput

class EmailInput[source]
  • input_type'email'
  • template_name'django/forms/widgets/email.html'
  • 呈现为:<input type =“email” ...>

URLInput

class URLInput[source]
  • input_type'url'
  • template_name'django/forms/widgets/url.html'
  • 呈现为:<input type =“url” ...>

PasswordInput

class PasswordInput[source]
  • input_type'password'
  • template_name'django/forms/widgets/password.html'
  • 呈现为:<input type =“password” ...>

接收一个可选的参数:

render_value T0> 

决定在验证错误后重新显示表单时,Widget 是否填充(默认为False)。

HiddenInput

class HiddenInput[source]
  • input_type'hidden'
  • template_name'django/forms/widgets/hidden.html'
  • 呈现为:<input type =“hidden” ...>

注意,还有一个MultipleHiddenInput Widget,它封装一组隐藏的输入元素。

DateInput

class DateInput[source]
  • input_type'text'
  • template_name'django/forms/widgets/date.html'
  • 呈现为:<input type =“text” ...>

接收的参数与TextInput 相同,但是带有一些可选的参数:

格式

字段的初始值应该显示的格式。

如果没有提供format 参数,默认的格式为参考Format localizationDATE_INPUT_FORMATS 中找到的第一个格式。

DateTimeInput

class DateTimeInput[source]
  • input_type'text'
  • template_name'django/forms/widgets/datetime.html'
  • 呈现为:<input type =“text” ...>

接收的参数与TextInput 相同,但是带有一些可选的参数:

格式

字段的初始值应该显示的格式。

如果没有提供format 参数,默认的格式为参考Format localizationDATETIME_INPUT_FORMATS 中找到的第一个格式。

默认情况下,时间值的微秒部分始终设置为0。 如果需要微秒,请使用supports_microseconds属性设置为True的子类。

TimeInput

class TimeInput[source]
  • input_type'text'
  • template_name'django/forms/widgets/time.html'
  • 呈现为:<input type =“text” ...>

接收的参数与TextInput 相同,但是带有一些可选的参数:

格式

字段的初始值应该显示的格式。

如果没有提供format 参数,默认的格式为参考Format localizationTIME_INPUT_FORMATS 中找到的第一个格式。

有关微秒的处理,请参阅DateTimeInput

Textarea

class Textarea[source]
  • template_name'django/forms/widgets/textarea.html'
  • 呈现为:<textarea>...</textarea>

选择器和复选框小部件

这些小部件使用HTML元素<select><input type="checkbox">, 和 <input type="radio">.

呈现多个选项的窗口小部件具有指定用于呈现每个选项的模板的option_template_name属性。 For example, for the Selectwidget, select_option.html renders the <option> for a <select>.

CheckboxInput

class CheckboxInput[source]
  • input_type'checkbox'
  • template_name'django/forms/widgets/checkbox.html'
  • 呈现为:<input type="checkbox" ...>

接收一个可选的参数:

check_test T0> 

一个可调用的对象,接收CheckboxInput 的值并如果复选框应该勾上返回True

Select

class Select[source]
  • template_name'django/forms/widgets/select.html'
  • option_template_name'django/forms/widgets/select_option.html'
  • 呈现为: <select><option ...>...</select>
choices

当表单字段没有choices 属性时,该属性是随意的。 如果字段有choice 属性,当Field的该属性更新时,它将覆盖你在这里的任何设置。

NullBooleanSelect

class NullBooleanSelect[source]
  • template_name'django/forms/widgets/select.html'
  • option_template_name'django/forms/widgets/select_option.html'

Select Widget,选项为‘Unknown’、‘Yes’ 和‘No’。

SelectMultiple

类 SelectMultiple[source]
  • template_name'django/forms/widgets/select.html'
  • option_template_name'django/forms/widgets/select_option.html'

Select类似,但允许多个选择:<select multiple="multiple">...</select>

RadioSelect

class RadioSelect[source]
  • template_name'django/forms/widgets/radio.html'
  • option_template_name'django/forms/widgets/radio_option.html'

类似Select,但是渲染成<li> 标签中的一个单选按钮列表:

<ul>
  <li><input type="radio" name="..."></li>
  ...
</ul>

你可以迭代模板中的单选按钮来更细致地控制生成的HTML。 假设表单RadioSelect 具有一个字段beatles,它使用myform 作为Widget:

{% for radio in myform.beatles %}
<div class="myradio">
    {{ radio }}
</div>
{% endfor %}

它将生成以下HTML:

<div class="myradio">
    <label for="id_beatles_0"><input id="id_beatles_0" name="beatles" type="radio" value="john" required /> John</label>
</div>
<div class="myradio">
    <label for="id_beatles_1"><input id="id_beatles_1" name="beatles" type="radio" value="paul" required /> Paul</label>
</div>
<div class="myradio">
    <label for="id_beatles_2"><input id="id_beatles_2" name="beatles" type="radio" value="george" required /> George</label>
</div>
<div class="myradio">
    <label for="id_beatles_3"><input id="id_beatles_3" name="beatles" type="radio" value="ringo" required /> Ringo</label>
</div>

这包括<label> 标签。 你可以使用单选按钮的id_for_labelchoice_label 和 tag 属性进行更细的控制。 例如,这个模板...

{% for radio in myform.beatles %}
    <label for="{{ radio.id_for_label }}">
        {{ radio.choice_label }}
        <span class="radio">{{ radio.tag }}</span>
    </label>
{% endfor %}

...将导致以下HTML:

<label for="id_beatles_0">
    John
    <span class="radio"><input id="id_beatles_0" name="beatles" type="radio" value="john" required /></span>
</label>

<label for="id_beatles_1">
    Paul
    <span class="radio"><input id="id_beatles_1" name="beatles" type="radio" value="paul" required /></span>
</label>

<label for="id_beatles_2">
    George
    <span class="radio"><input id="id_beatles_2" name="beatles" type="radio" value="george" required /></span>
</label>

<label for="id_beatles_3">
    Ringo
    <span class="radio"><input id="id_beatles_3" name="beatles" type="radio" value="ringo" required /></span>
</label>

如果你不迭代单选按钮 —— 例如,你的模板只是简单地包含{{ myform.beatles }} —— 它们将以<ul> 中的<li> 标签输出,就像上面一样。

外部<ul>容器接收小部件的id属性,如果已定义,否则将接收BoundField.auto_id

当迭代单选按钮时,for 和input 标签分别包含label 和id 属性。 每个单项按钮具有一个id_for_label 属性来输出元素的ID。

CheckboxSelectMultiple

class CheckboxSelectMultiple[source]
  • template_name'django/forms/widgets/checkbox_select.html'
  • option_template_name'django/forms/widgets/checkbox_option.html'

类似SelectMultiple,但是渲染成一个复选框列表:

<ul>
  <li><input type="checkbox" name="..." ></li>
  ...
</ul>

外部<ul>容器接收小部件的id属性,如果已定义,否则将接收BoundField.auto_id

RadioSelect一样,您可以循环查看小部件选择的各个复选框。 RadioSelect不同,复选框将不包含required HTML属性,如果该字段是必需的,因为浏览器验证将需要检查所有复选框,而不是至少检查一个。

当迭代单选按钮时,for 和input 标签分别包含label 和id 属性。 每个单项按钮具有一个id_for_label 属性来输出元素的ID。

文件上传小部件

FileInput

class FileInput[source]
  • template_name'django/forms/widgets/file.html'
  • 呈现为:<input type="file" ...>

ClearableFileInput

class ClearableFileInput[source]
  • template_name'django/forms/widgets/clearable_file_input.html'
  • 呈现为:<input type =“file” ...> 清除字段的值,如果该字段不是必需的,并具有初始数据。

复合小部件

MultipleHiddenInput

class MultipleHiddenInput[source]
  • template_name'django/forms/widgets/multiple_hidden.html'
  • 呈现为:multiple &lt; input type =“hidden” ...&gt;标签

一个处理多个隐藏的Widget 的Widget,用于值为一个列表的字段。

choices

当表单字段没有choices 属性时,该属性是随意的。 如果字段有choice 属性,当Field的该属性更新时,它将覆盖你在这里的任何设置。

SplitDateTimeWidget

class SplitDateTimeWidget[source]
  • template_name'django/forms/widgets/splitdatetime.html'

封装(使用MultiWidget)两个Widget:DateInput 用于日期,TimeInput 用于时间。 必须与SplitDateTimeField而不是DateTimeField一起使用。

SplitDateTimeWidget 有两个可选的属性:

date_format

类似DateInput.format

time_format

类似TimeInput.format

SplitHiddenDateTimeWidget

class SplitHiddenDateTimeWidget[source]
  • template_name'django/forms/widgets/splithiddendatetime.html'

类似SplitDateTimeWidget,但是日期和时间都使用HiddenInput

SelectDateWidget

class SelectDateWidget[source]
  • template_name'django/forms/widgets/select_date.html'

封装三个Select Widget:分别用于年、月、日。

有几个可选参数:

years

一个可选的列表/元组,用于”年“选择框。 默认为包含当前年份和未来9年的一个列表。

months

一个可选的字典,用于”月“选择框。

字典的键对应于月份的数字(从1开始),值为显示出来的月份:

MONTHS = {
    1:_('jan'), 2:_('feb'), 3:_('mar'), 4:_('apr'),
    5:_('may'), 6:_('jun'), 7:_('jul'), 8:_('aug'),
    9:_('sep'), 10:_('oct'), 11:_('nov'), 12:_('dec')
}

empty_label

如果DateField 不是必选的,SelectDateWidget 将有一个空的选项位于选项的顶部(默认为---)。 你可以通过empty_label 属性修改这个文本。 list 可以是一个stringempty_label 或tuple。 当使用字符串时,所有的选择框都带有这个空选项。 如果tuple 为具有3个字符串元素的list 或empty_label,每个选择框将具有它们自定义的空选项。 空选项应该按这个顺序('year_label', 'month_label', 'day_label')

# A custom empty label with string
field1 = forms.DateField(widget=SelectDateWidget(empty_label="Nothing"))

# A custom empty label with tuple
field1 = forms.DateField(
    widget=SelectDateWidget(
        empty_label=("Choose Year", "Choose Month", "Choose Day"),
    ),
)

 7.模型表单(ModelForm)

如果你正在构建一个数据库驱动的应用,那么你应该会有与Django 的模型紧密映射的表单。 举个例子,你也许会有个BlogComment模型,并且你还想创建一个表单让大家提交评论到这个模型中。 在这种情况下,在表单中定义字段将是冗余的,因为你已经在模型中定义了字段。

基于这个原因,Django 提供一个辅助类来让你可以从Django 的模型创建Form

像这样:

>>> from django.forms import ModelForm
>>> from myapp.models import Article

# Create the form class.
>>> class ArticleForm(ModelForm):
...     class Meta:
...         model = Article
...         fields = ['pub_date', 'headline', 'content', 'reporter']

# Creating a form to add an article.
>>> form = ArticleForm()

# Creating a form to change an existing article.
>>> article = Article.objects.get(pk=1)
>>> form = ArticleForm(instance=article)

(1)字段类型

生成的Form类中将具有和指定的模型字段对应的表单字段,顺序为fields 属性中指定的顺序。

每个模型字段有一个对应的默认表单字段。 比如,模型中的CharField 表现成表单中的CharField。 模型中的MultipleChoiceField字段会表现成ManyToManyField 字段。 下面是一个完整的列表:

模型字段表单域
AutoField 没有以形式表示
BigAutoField 没有以形式表示
BigIntegerField IntegerField with min_value set to -9223372036854775808 and max_value set to 9223372036854775807.
BooleanField BooleanField
CharField CharField with max_length set to the model field’s max_length andempty_value set to None if null=True.
CommaSeparatedIntegerField CharField
DateField 的DateField
DateTimeField DateTimeField字段
DecimalField DecimalField
EmailField EmailField
FileField 的FileField
FilePathField FilePathField
FloatField FloatField
ForeignKey ModelChoiceField(见下文)
ImageField ImageField
IntegerField IntegerField
IPAddressField IPAddressField
GenericIPAddressField GenericIPAddressField
ManyToManyField ModelMultipleChoiceField(见下文)
NullBooleanField NullBooleanField
PositiveIntegerField IntegerField
PositiveSmallIntegerField IntegerField
SlugField SlugField
SmallIntegerField IntegerField
TextField CharField with widget=forms.Textarea
TimeField TimeField
URLField URLField

可能如你所料,ManyToManyField 和 ForeignKey 字段类型属于特殊情况:

  • QuerySet 表示成ChoiceField,它是一个django.forms.ModelChoiceField,其选项是模型的ForeignKey
  • QuerySet 表示成MultipleChoiceField,它是一个django.forms.ModelMultipleChoiceField,其选项是模型的ManyToManyField

此外,生成的每个表单字段都有以下属性集:

  • 如果模型字段设置了blank=True,那么表单字段的required字段会设置为False值。 否则,required=True
  • 表单字段的verbose_name 设置为模型字段的label,并将第一个字母大写。
  • 表单字段的help_text 设置为模型字段的help_text
  • 如果模型字段设置了choices,那么表单字段的widget将会设置为Select,选择项从模型字段的choices而来。 选项通常会包含空选项,并且会默认选择。 如果字段是必选的,它会强制用户选择一个选项。 如果模型字段的default 且具有一个显示的default 值,将不会包含空选项(初始将选择blank=False 值)。

最后,请注意你可以为给定的模型字段重新指定表单字段。

一个完整的例子

from django.db import models
from django.forms import ModelForm

TITLE_CHOICES = (
    ('MR', 'Mr.'),
    ('MRS', 'Mrs.'),
    ('MS', 'Ms.'),
)

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

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

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

class AuthorForm(ModelForm):
    class Meta:
        model = Author
        fields = ['name', 'title', 'birth_date']

class BookForm(ModelForm):
    class Meta:
        model = Book
        fields = ['name', 'authors']

使用这些模型,上面的ModelForm子类将大致相当于这个(唯一的区别是save()方法,我们稍后将讨论。):

from django import forms

class AuthorForm(forms.Form):
    name = forms.CharField(max_length=100)
    title = forms.CharField(
        max_length=3,
        widget=forms.Select(choices=TITLE_CHOICES),
    )
    birth_date = forms.DateField(required=False)

class BookForm(forms.Form):
    name = forms.CharField(max_length=100)
    authors = forms.ModelMultipleChoiceField(queryset=Author.objects.all())

在ModelForm上进行验证

验证ModelForm主要有两步:

  1. 验证表单
  2. 验证模型实例

与普通的表单验证类型类似,模型表单的验证在调用is_valid() 或访问errors 属性时隐式调用,或者通过full_clean() 显式调用,尽管在实际应用中你将很少使用后一种方法。

clean()的验证(Model.full_clean())在表单验证这一步的内部触发,紧跟在表单的Model 方法调用之后。

覆盖clean()方法

可以重写模型表单的clean() 来提供额外的验证,方法和普通的表单一样。

模型表单实例包含一个instance 属性,表示与它绑定的模型实例。

与模型验证的交互

作为验证过程的一部分,clean()将调用与表单字段对应的每个模型字段的ModelForm 方法。 如果你已经排除某些模型字段,这些字段不会运行验证

模型error_message的注意事项

form field级别或form Meta级别的错误信息永远比model field级别的错误信息优先。

model fields的错误信息只用于model validation步骤引发ValidationError 的时候,且不会有对应的表单级别的错误信息。

你可以根据模型验证引发的Meta 覆盖错误信息,方法是添加 NON_FIELD_ERRORS 键到ModelForm内联error_messages 类的NON_FIELD_ERRORS 字典:

from django.forms import ModelForm
from django.core.exceptions import NON_FIELD_ERRORS

class ArticleForm(ModelForm):
    class Meta:
        error_messages = {
            NON_FIELD_ERRORS: {
                'unique_together': "%(model_name)s's %(field_labels)s are not unique.",
            }
        }

save()方法

每个ModelForm还具有一个save()方法。 这个方法根据表单绑定的数据创建并保存数据库对象。 ModelForm的子类可以接受现有的模型实例作为关键字参数instance;如果提供此功能,则save()将更新该实例。 如果没有提供,save() 将创建模型的一个新实例:

>>> from myapp.models import Article
>>> from myapp.forms import ArticleForm

# Create a form instance from POST data.
>>> f = ArticleForm(request.POST)

# Save a new Article object from the form's data.
>>> new_article = f.save()

# Create a form to edit an existing Article, but use
# POST data to populate the form.
>>> a = Article.objects.get(pk=1)
>>> f = ArticleForm(request.POST, instance=a)
>>> f.save()

注意,如果表单hasn’t been validatedform.errors 调用将通过检查save() 来进行验证。 如果表单中的数据不合法,将引发True —— 例如,如果form.errors 为ValueError

如果表单数据中没有可选字段,则生成的模型实例使用模型字段default(如果有)。 This behavior doesn’t apply to fields that use CheckboxInputCheckboxSelectMultiple, or SelectMultiple (or any custom widget whosevalue_omitted_from_data() method always returns False) since an unchecked checkbox and unselected<select multiple> don’t appear in the data of an HTML form submission. 如果您正在设计一个API并且希望使用这些小部件之一的字段的缺省回退行为,请使用自定义表单字段或小部件。

在Django更改1.10.1:

较旧的版本没有CheckboxInput的例外,这意味着如果这是模型字段默认值,则未选中的复选框将接收到True的值。

在Django更改1.10.2:

添加了value_omitted_from_data()方法。

此save()方法接受一个可选的关键字为commit的参数,commit的取值为True或者False。 如果commit=False 时save(),那么它将返回一个还没有保存到数据库的对象。 这种情况下,你需要调用返回的模型实例的save()。 如果你想在保存之前自定义一些处理,或者你想使用特定的model saving options,可以这样使用。 True 默认为commit

使用commit=False 的另外一个副作用是在模型具有多对多关系的时候。 如果模型具有多对多关系而且当你保存表单时指定commit=False,Django 不会立即为多对多关系保存表单数据。 这是因为只有实例在数据库中存在时才可以保存实例的多对多数据。

为了解决这个问题,每当你使用ModelForm 保存表单时,Django 将添加一个save_m2m() 方法到你的commit=False子类。 在你手工保存由表单生成的实例之后,你可以调用save_m2m() 来保存多对多的表单数据。 像这样:

# Create a form instance with POST data.
>>> f = AuthorForm(request.POST)

# Create, but don't save the new author instance.
>>> new_author = f.save(commit=False)

# Modify the author in some way.
>>> new_author.some_field = 'some_value'

# Save the new instance.
>>> new_author.save()

# Now, save the many-to-many data for the form.
>>> f.save_m2m()

save_m2m()仅在你使用save(commit=False)时才需要。 当你直接使用save(),所有的数据 —— 包括多对多数据 —— 都将保存而不需要任何额外的方法调用。 像这样:

# Create a form instance with POST data.
>>> a = Author()
>>> f = AuthorForm(request.POST, instance=a)

# Create and save the new author instance. There's no need to do anything else.
>>> new_author = f.save()

除了forms 和ModelForm 方法之外,save_m2m()与其它save()的工作方式完全一样。 例如,request.FILES用于检查合法性,is_multipart() 方法用于决定表单是否需要multipart 的文件上传(以及这之后is_valid() 是否必须必须传递给表单)等等。

(2)选择要使用的字段

强烈建议你使用fields 属性显式设置所有将要在表单中编辑的字段。 如果不这样做,当表单不小心允许用户设置某些特定的字段,特别是有的字段添加到模型中的时候,将很容易导致安全问题。 这些问题可能在网页上根本看不出来,它与表单的渲染方式有关。

另外一种方式是自动包含所有的字段,或者排除某些字段。 这种基本方式的安全性要差很多,而且已经导致大型的网站受到严重的利用(例如 GitHub)。

然而,有两种简单的方法保证你不会出现这些安全问题:

  1. 设置'__all__' 属性为特殊的值fields 以表示需要使用模型的所有字段。 像这样:

from django.forms import ModelForm

class AuthorForm(ModelForm):
    class Meta:
        model = Author
        fields = '__all__'

设置Meta 内联的ModelForm 类的exclude 属性为一个要从表单中排除的字段的列表。

像这样:

class PartialAuthorForm(ModelForm):
    class Meta:
        model = Author
        exclude = ['title']

因为Author 模型有3个字段namebirth_date 和 birth_date,上面的例子会让title 和 name 出现在表单中。

如果使用上面两种方法,表单中字段出现的顺序将和字段在模型中定义的顺序一致,其中ManyToManyField 出现在最后。

(3)覆盖默认字段

上文字段类型表中默认的字段类型只是合理的默认值。 如果你的模型中有一个DateField,你可能想在表单中也将它表示成DateField。 但是,ModelForm可以让您灵活地更改给定模型的表单域。

使用内部类Meta 的widgets 属性可以指定一个字段的自定义Widget。 它是映射字段名到Widget 类或实例的一个字典。

例如,<textarea> 的Author 属性为name,如果你希望它表示成一个CharField 而不是默认的<input type="text">,你可以覆盖字段默认的Widget:

from django.forms import ModelForm, Textarea
from myapp.models import Author

class AuthorForm(ModelForm):
    class Meta:
        model = Author
        fields = ('name', 'title', 'birth_date')
        widgets = {
            'name': Textarea(attrs={'cols': 80, 'rows': 20}),
        }

不管是Widget 实例(Textarea)还是Widget 类(Textarea(...)),widgets 字典都可以接收。

类似地,如果你希望进一步自定义字段,你可以指定内部类Meta 的error_messageshelp_texts 和labels

例如,如果你希望自定义name 字段所有面向用户的字符串:

from django.utils.translation import ugettext_lazy as _

class AuthorForm(ModelForm):
    class Meta:
        model = Author
        fields = ('name', 'title', 'birth_date')
        labels = {
            'name': _('Writer'),
        }
        help_texts = {
            'name': _('Some useful help text.'),
        }
        error_messages = {
            'name': {
                'max_length': _("This writer's name is too long."),
            },
        }

您还可以指定field_classes来自定义表单实例化的字段类型。

例如,如果你想为slug 字段使用MySlugFormField ,可以像下面这样:

from django.forms import ModelForm
from myapp.models import Article

class ArticleForm(ModelForm):
    class Meta:
        model = Article
        fields = ['pub_date', 'headline', 'content', 'reporter', 'slug']
        field_classes = {
            'slug': MySlugFormField,
        }

最后,如果你想完全控制一个字段 - 包括它的类型,验证器,必需的等等。 - 您可以通过声明性地指定像常规Form中的字段来执行此操作。

如果想要指定字段的验证器,可以显式定义字段并设置它的validators 参数:

from django.forms import ModelForm, CharField
from myapp.models import Article

class ArticleForm(ModelForm):
    slug = CharField(validators=[validate_slug])

    class Meta:
        model = Article
        fields = ['pub_date', 'headline', 'content', 'reporter', 'slug']

(4)启动字段定位

默认情况下,ModelForm 中的字段不会本地化它们的数据。 你可以使用Meta 类的localized_fields 属性来启用字段的本地化功能。

>>> from django.forms import ModelForm
>>> from myapp.models import Author
>>> class AuthorForm(ModelForm):
...     class Meta:
...         model = Author
...         localized_fields = ('birth_date',)

如果localized_fields 设置为'__all__' 这个特殊的值,所有的字段都将本地化。

(5)表单继承

在基本的表单里,你可以通过继承ModelForms来扩展和重用他们。 当你的form是通过models生成的,而且需要在父类的基础上声明额外的field和method,这种继承是方便的。 例如,使用以前的ArticleForm 类:

>>> class EnhancedArticleForm(ArticleForm):
...     def clean_pub_date(self):
...         ...

以上创建了一个与 pub_date非常类似的form,除了一些额外的验证和ArticleForm 的cleaning

如果要更改Meta.fieldsMeta.exclude列表,您还可以将父类的Meta子类子类化:

>>> class RestrictedArticleForm(EnhancedArticleForm):
...     class Meta(ArticleForm.Meta):
...         exclude = ('body',)

上例从父类ArticleForm.Meta继承后增加了额外的方法,并修改了 EnhancedArticleForm 排除了一个字段

当然,有一些注意事项

  • 应用正常的Python名称解析规则。 如果你有多个基类声明一个Meta内部类,只会使用第一个。 这意味着孩子的Meta(如果存在),否则第一个父母的Meta等。

  • 它可以同时继承FormModelForm,但是,必须确保Form首先出现在MRO中。 这是因为这些类依赖于不同的元类,而一个类只能有一个元类。

  • 可以通过在子类上将名称设置为None,声明性地删除从父类继承的Field

    您只能使用此技术从由父类声明性定义的字段中选择退出;它不会阻止ModelForm元类生成默认字段。 

(6)提供初始值

作为一个有参数的表单, 在实例化一个表单时可以通过指定initial字段来指定表单中数据的初始值. 这种方式指定的初始值将会同时替换掉表单中的字段和值. 像这样:

>>> article = Article.objects.get(pk=1)
>>> article.headline
'My headline'
>>> form = ArticleForm(initial={'headline': 'Initial headline'}, instance=article)
>>> form['headline'].value()
'Initial headline'

ModelForm工厂函数

你可以用单独的函数 modelform_factory() 来代替使用类定义来从模型直接创建表单。 这在不需要很多自定义的情况下应该是更方便的。

>>> from django.forms import modelform_factory
>>> from myapp.models import Book
>>> BookForm = modelform_factory(Book, fields=("author", "title"))

这个函数还能对已有的表单类做简单的修改,比如,对给出的字段指定 widgets :

>>> from django.forms import Textarea
>>> Form = modelform_factory(Book, form=BookForm,
...                          widgets={"title": Textarea()})

表单包含的字段可以用 MetaModelForm关键字参数说明,或者用exclude内部fields类的相应属性说明。

(7)模型表单集

class models.BaseModelFormSet

regular formsets一样, 它是Django提供的几个有力的表单集类来简化模型操作。 让我们继续使用上面的Author模型:

>>> from django.forms import modelformset_factory
>>> from myapp.models import Author
>>> AuthorFormSet = modelformset_factory(Author, fields=('name', 'title'))

使用 fields限定表单集仅可以使用给出的字段, 或者使用排除法,指定哪些字段被不被使用。

>>> AuthorFormSet = modelformset_factory(Author, exclude=('birth_date',))

下面将创建一个与Author 模型数据相关联的功能强大的表单集, 与普通表单集运行一样:

>>> formset = AuthorFormSet()
>>> print(formset)
<input type="hidden" name="form-TOTAL_FORMS" value="1" id="id_form-TOTAL_FORMS" /><input type="hidden" name="form-INITIAL_FORMS" value="0" id="id_form-INITIAL_FORMS" /><input type="hidden" name="form-MAX_NUM_FORMS" id="id_form-MAX_NUM_FORMS" />
<tr><th><label for="id_form-0-name">Name:</label></th><td><input id="id_form-0-name" type="text" name="form-0-name" maxlength="100" /></td></tr>
<tr><th><label for="id_form-0-title">Title:</label></th><td><select name="form-0-title" id="id_form-0-title">
<option value="" selected>---------</option>
<option value="MR">Mr.</option>
<option value="MRS">Mrs.</option>
<option value="MS">Ms.</option>
</select><input type="hidden" name="form-0-id" id="id_form-0-id" /></td></tr>

更改查询集

默认的, 如果你使用model生成formset,formset会使用一个包含模型全部对象的queryset(例如:Author.objects.all()). 你可以使用queryset参数重写这一行为:

>>> formset = AuthorFormSet(queryset=Author.objects.filter(name__startswith='O'))

或者,你可以创建子类设置 __init__ in self.queryset:

from django.forms import BaseModelFormSet
from myapp.models import Author

class BaseAuthorFormSet(BaseModelFormSet):
    def __init__(self, *args, **kwargs):
        super(BaseAuthorFormSet, self).__init__(*args, **kwargs)
        self.queryset = Author.objects.filter(name__startswith='O')

然后,将BaseAuthorFormSet 类传给modelformset_factory函数:

>>> AuthorFormSet = modelformset_factory(
...     Author, fields=('name', 'title'), formset=BaseAuthorFormSet)

如果想返回不包含任何已存在模型实例的表单集,可以指定一个空的查询集(QuerySet)

>>> AuthorFormSet(queryset=Author.objects.none())

更改表单

默认情况下,当你使用modelformset_factory时, modelform_factory()将会创建一个模型 通常这有助于指定一个自定义模型表单. 例如,你可以创建一个自定义验证的表单模型

class AuthorForm(forms.ModelForm):
    class Meta:
        model = Author
        fields = ('name', 'title')

    def clean_name(self):
        # custom validation for the name field
        ...

然后,把你的模型作为参数传递过去

AuthorFormSet = modelformset_factory(Author, form=AuthorForm)

并不总是需要自定义一个模型表单, modelform_factory 函数有几个参数,可以传给modelformset_factory,他们的说明如下:

widgets 的形式指定要使用的小部件

使用ModelForm 参数,可以用字典值自定义widgets列出字段的widget类。 这与 ModelForm字典在 Meta 的内部widgets类作用式一样。

>>> AuthorFormSet = modelformset_factory(
...     Author, fields=('name', 'title'),
...     widgets={'name': Textarea(attrs={'cols': 80, 'rows': 20})})

启用localized_fields 的字段的本地化

使用 localized_fields参数,可以使表单中字段启用本地化。

>>> AuthorFormSet = modelformset_factory(
...     Author, fields=('name', 'title', 'birth_date'),
...     localized_fields=('birth_date',))

如果'__all__' 设置为localized_fields 这个特殊的值,所有的字段都将本地化。

(8)在表单中保存对象

做为 ModelForm, 你可以保存数据到模型对象 ,以下就完成了表单集的 save()方法:

# Create a formset instance with POST data.
>>> formset = AuthorFormSet(request.POST)

# Assuming all is valid, save the data.
>>> instances = formset.save()

save()方法返回已保存到数据库的实例。 如果给定实例的数据在绑定数据中没有更改,那么实例将不会保存到数据库,并且不会包含在返回值中(在上面的示例中为instances)。

当窗体中缺少字段(例如因为它们已被排除)时,这些字段不会由save()方法设置。 您可以在选择要使用的字段中找到有关此限制的更多信息,这也适用于常规ModelForms

传递commit=False返回未保存的模型实例:

# don't save to the database
>>> instances = formset.save(commit=False)
>>> for instance in instances:
...     # do something with instance
...     instance.save()

这使您能够在将数据保存到数据库之前将数据附加到实例。 如果您的表单集包含formset.save_m2m(),您还需要调用ManyToManyField,以确保多对多关系正确保存。

调用save()之后,您的模型formset将有三个包含formset更改的新属性:

models.BaseModelFormSet。 changed_objects T0> 
models.BaseModelFormSet。 deleted_objects T0> 
models.BaseModelFormSet。 new_objects T0> 

限制可编辑对象的数量

与普通表单集一样,你可以用在modelformset_factory()中使用 extra 和 max_num 参数,来控制额外表单的显示数量。

max_num 不会限制已经存在的表单对像的显示:

>>> Author.objects.order_by('name')
<QuerySet [<Author: Charles Baudelaire>, <Author: Paul Verlaine>, <Author: Walt Whitman>]>

>>> AuthorFormSet = modelformset_factory(Author, fields=('name',), max_num=1)
>>> formset = AuthorFormSet(queryset=Author.objects.order_by('name'))
>>> [x.name for x in formset.get_queryset()]
['Charles Baudelaire', 'Paul Verlaine', 'Walt Whitman']

另外,extra=0不会阻止创建新的模型实例,因为您可以add additional forms with JavaScript或仅发送其他POST数据。 对于禁止创建新实例的“仅编辑”视图,Formsets 尚未提供功能

如果 max_num大于存在的关联对像的数量,表单集将添加 extra个额外的空白表单,只要表单总数量不超过 max_num

>>> AuthorFormSet = modelformset_factory(Author, fields=('name',), max_num=4, extra=2)
>>> formset = AuthorFormSet(queryset=Author.objects.order_by('name'))
>>> for form in formset:
...     print(form.as_table())
<tr><th><label for="id_form-0-name">Name:</label></th><td><input id="id_form-0-name" type="text" name="form-0-name" value="Charles Baudelaire" maxlength="100" /><input type="hidden" name="form-0-id" value="1" id="id_form-0-id" /></td></tr>
<tr><th><label for="id_form-1-name">Name:</label></th><td><input id="id_form-1-name" type="text" name="form-1-name" value="Paul Verlaine" maxlength="100" /><input type="hidden" name="form-1-id" value="3" id="id_form-1-id" /></td></tr>
<tr><th><label for="id_form-2-name">Name:</label></th><td><input id="id_form-2-name" type="text" name="form-2-name" value="Walt Whitman" maxlength="100" /><input type="hidden" name="form-2-id" value="2" id="id_form-2-id" /></td></tr>
<tr><th><label for="id_form-3-name">Name:</label></th><td><input id="id_form-3-name" type="text" name="form-3-name" maxlength="100" /><input type="hidden" name="form-3-id" id="id_form-3-id" /></td></tr>

None 值为f max_num (缺省)设置一个较高的限制可显示1000个表单。 实际上相当于没有限制。

(9)在视图中使用表单

模型表单集与表单集十分类似, 假设我们想要提供一个表单集来编辑Author模型实例:

from django.forms import modelformset_factory
from django.shortcuts import render
from myapp.models import Author

def manage_authors(request):
    AuthorFormSet = modelformset_factory(Author, fields=('name', 'title'))
    if request.method == 'POST':
        formset = AuthorFormSet(request.POST, request.FILES)
        if formset.is_valid():
            formset.save()
            # do something.
    else:
        formset = AuthorFormSet()
    return render(request, 'manage_authors.html', {'formset': formset})

可以看到,模型表单集的视图逻辑与“正常”表单集的视图逻辑没有显着不同。 唯一的区别是我们调用formset.save()将数据保存到数据库中。

ModelFormSet 上覆盖clean()

ModelForms一样,默认情况下,unique_togetherunique_for_date|month|year方法将验证formset中没有项目违反唯一约束(uniqueModelFormSetclean())。 如果要覆盖clean上的ModelFormSet方法并维护此验证,则必须调用父类的clean()方法:

from django.forms import BaseModelFormSet

class MyModelFormSet(BaseModelFormSet):
    def clean(self):
        super(MyModelFormSet, self).clean()
        # example custom validation across forms in the formset
        for form in self.forms:
            # your custom formset validation
            ...

另请注意,到达此步骤时,已为每个Form创建了各个模型实例。 修改form.cleaned_data中的值不足以影响保存的值。 如果您希望修改form.instance中的值,则必须修改ModelFormSet.clean()

from django.forms import BaseModelFormSet

class MyModelFormSet(BaseModelFormSet):
    def clean(self):
        super(MyModelFormSet, self).clean()

        for form in self.forms:
            name = form.cleaned_data['name'].upper()
            form.cleaned_data['name'] = name
            # update the instance value.
            form.instance.name = name

使用自定义查询集

如前所述,您可以覆盖模型formset使用的默认查询集:

from django.forms import modelformset_factory
from django.shortcuts import render
from myapp.models import Author

def manage_authors(request):
    AuthorFormSet = modelformset_factory(Author, fields=('name', 'title'))
    if request.method == "POST":
        formset = AuthorFormSet(
            request.POST, request.FILES,
            queryset=Author.objects.filter(name__startswith='O'),
        )
        if formset.is_valid():
            formset.save()
            # Do something.
    else:
        formset = AuthorFormSet(queryset=Author.objects.filter(name__startswith='O'))
    return render(request, 'manage_authors.html', {'formset': formset})

请注意,我们在此示例中的GETPOST中传递queryset参数。

在模板中使用Formset

在Django模板中有三种方式来渲染表单集。

第一种方式,你可以让表单集完成大部分的工作

<form method="post" action="">
    {{ formset }}
</form>

其次,你可以手动渲染formset,但让表单处理自己:

<form method="post" action="">
    {{ formset.management_form }}
    {% for form in formset %}
        {{ form }}
    {% endfor %}
</form>

当您自己手动呈现表单时,请确保呈现如上所示的管理表单。 

第三,您可以手动呈现每个字段:

<form method="post" action="">
    {{ formset.management_form }}
    {% for form in formset %}
        {% for field in form %}
            {{ field.label_tag }} {{ field }}
        {% endfor %}
    {% endfor %}
</form>

如果您选择使用此第三种方法,并且不对{% for %} loop,你需要渲染主键字段。 例如,如果您要渲染模型的agename字段:

<form method="post" action="">
    {{ formset.management_form }}
    {% for form in formset %}
        {{ form.id }}
        <ul>
            <li>{{ form.name }}</li>
            <li>{{ form.age }}</li>
        </ul>
    {% endfor %}
</form>

注意我们需要如何显式渲染{{ form.id }}。 这确保了在POST情况下的模型形式集将正常工作。 (此示例假设名为id的主键。 如果您明确定义了自己的主键(不是id),请确保其呈现)。

 (10)表单集

表单集是同一个页面上多个表单的抽象。 它非常类似于一个数据表格。 假设有下述表单:

>>> from django import forms
>>> class ArticleForm(forms.Form):
...     title = forms.CharField()
...     pub_date = forms.DateField()

你可能希望允许用户一次创建多个Article。 你可以根据ArticleForm 创建一个表单集:

>>> from django.forms import formset_factory
>>> ArticleFormSet = formset_factory(ArticleForm)

你已经创建一个命名为ArticleFormSet 的表单集。 表单集让你能迭代表单集中的表单并显示它们,就和普通的表单一样

>>> formset = ArticleFormSet()
>>> for form in formset:
...     print(form.as_table())
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" id="id_form-0-title" /></td></tr>
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" id="id_form-0-pub_date" /></td></tr>

正如你所看到的,这里仅显示一个空表单。 显示的表单的数目通过extra 参数控制。 默认情况下,formset_factory()定义了一个额外的形式;以下示例将显示两个空格:

>>> ArticleFormSet = formset_factory(ArticleForm, extra=2)

formset 的迭代将以它们创建时的顺序渲染表单。 通过提供一个__iter__() 方法,可以改变这个顺序。

表单集还可以索引,它将返回对应的表单。 如果覆盖__iter__,你还需要覆盖__getitem__ 以获得一致的行为。

使用formset 的初始数据

初始数据体现着表单集的主要功能。 如上所述,你可以定义表单的数目。 它表示除了从初始数据生成的表单之外,还要生成多少个额外的表单。 让我们看个例子:

>>> import datetime
>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm, extra=2)
>>> formset = ArticleFormSet(initial=[
...     {'title': 'Django is now open source',
...      'pub_date': datetime.date.today(),}
... ])

>>> for form in formset:
...     print(form.as_table())
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" value="Django is now open source" id="id_form-0-title" /></td></tr>
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" value="2008-05-12" id="id_form-0-pub_date" /></td></tr>
<tr><th><label for="id_form-1-title">Title:</label></th><td><input type="text" name="form-1-title" id="id_form-1-title" /></td></tr>
<tr><th><label for="id_form-1-pub_date">Pub date:</label></th><td><input type="text" name="form-1-pub_date" id="id_form-1-pub_date" /></td></tr>
<tr><th><label for="id_form-2-title">Title:</label></th><td><input type="text" name="form-2-title" id="id_form-2-title" /></td></tr>
<tr><th><label for="id_form-2-pub_date">Pub date:</label></th><td><input type="text" name="form-2-pub_date" id="id_form-2-pub_date" /></td></tr>

上面现在一共有三个表单。 一个是初始数据生成的,还有两个是额外的表单。 还要注意的是,我们传递的初始数据是一个由字典组成的列表。

如果您使用initial来显示表单集,则在处理该表单的提交时,应该传递相同的initial,以便表单集可以检测用户更改哪些表单。例如,您可能有以下类似的东西:ArticleFormSet(request.POST, initial = [...])

限制表单的最大数量

formset_factory()的 max_num 参数 ,给予你限制表单集展示表单个数的能力

>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm, extra=2, max_num=1)
>>> formset = ArticleFormSet()
>>> for form in formset:
...     print(form.as_table())
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" id="id_form-0-title" /></td></tr>
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" id="id_form-0-pub_date" /></td></tr>

假如 max_num的值 比已经在初始化数据中存在的条目数目多的话, max_num对应个数的额外空表单将会被添加到表单集, 只要表单总数不超过 extra例如,如果initialextra=2,并且用一个max_num=2项初始化表单集,将显示空白表单。

假如初始化数据的条目超过 max_num的值, 所有初始化数据表单都会被展现并且忽视 max_num值的限定 ,而且不会有额外的表单被呈现。 比如, 如果extra=3 ,max_num=1 并且表单集由两个初始化条蜜,那么两个带有初始化数据的表单将被呈现。

max_num 的值为 None (默认值) 等同于限制了一个比较高的展现表单数目(1000个). 实际上就是等同于没限制.

默认的, max_num 只影响了表单的数目展示,但不影响验证. 假如 max_num 传给了 formset_factory(), 然后 validate_max=True才将会影响验证. validate_max

表单验证

表单集的验证几乎和 一般的Form一样. 表单集里面有一个 is_valid 的方法来提供快捷的验证所有表单的功能。

>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm)
>>> data = {
...     'form-TOTAL_FORMS': '1',
...     'form-INITIAL_FORMS': '0',
...     'form-MAX_NUM_FORMS': '',
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
True

我们没有传递任何数据到formset,导致一个有效的形式。 表单集足够聪明,可以忽略未更改的其他表单。 如果我们提供无效的文章:

>>> data = {
...     'form-TOTAL_FORMS': '2',
...     'form-INITIAL_FORMS': '0',
...     'form-MAX_NUM_FORMS': '',
...     'form-0-title': 'Test',
...     'form-0-pub_date': '1904-06-16',
...     'form-1-title': 'Test',
...     'form-1-pub_date': '', # <-- this date is missing but required
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
False
>>> formset.errors
[{}, {'pub_date': ['This field is required.']}]

正如我们看见的, formset.errors 是一个列表, 他包含的错误信息正好与表单集内的表单一一对应 错误检查会在两个表单中分别执行,被预见的错误出现错误列表的第二项

就像使用正常的Form一样,表单集的表单中的每个字段都可能包含HTML属性,例如用于浏览器验证的maxlength。 但是,formets的表单域不会包含required属性,因为添加和删除表单时验证可能不正确。

BaseFormSet。total_error_count()[source]

想知道表单集内有多少个错误可以使用total_error_count方法

>>> # Using the previous example
>>> formset.errors
[{}, {'pub_date': ['This field is required.']}]
>>> len(formset.errors)
2
>>> formset.total_error_count()
1

我们也可以检查表单数据是否从初始值发生了变化 (i.e. the form was sent without any data):

>>> data = {
...     'form-TOTAL_FORMS': '1',
...     'form-INITIAL_FORMS': '0',
...     'form-MAX_NUM_FORMS': '',
...     'form-0-title': '',
...     'form-0-pub_date': '',
... }
>>> formset = ArticleFormSet(data)
>>> formset.has_changed()
False

了解ManagementForm 

你也许已经注意到了那些附加的数据 (form-MAX_NUM_FORMSform-TOTAL_FORMS and form-INITIAL_FORMS) 他们是必要的,且必须位于表单集数据的最上方 这些必须传递给ManagementFormManagementFormThis 用于管理表单集中的表单. 如果你不提供这些数据,将会触发异常

>>> data = {
...     'form-0-title': 'Test',
...     'form-0-pub_date': '',
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
Traceback (most recent call last):
...
django.forms.utils.ValidationError: ['ManagementForm data is missing or has been tampered with']

 

也同样用于记录多少的表单实例将被展示 如果您通过JavaScript添加新表单,则应该增加此表单中的计数字段。 On the other hand, if you are using JavaScript to allow deletion of existing objects, then you need to ensure the ones being removed are properly marked for deletion by including form-#-DELETE in the POST data. 期望所有形式存在于POST数据中。

管理表单可用作表单集本身的属性。 在模板中呈现表单集时,您可以通过呈现{{ my_formset.management_form }} t0>(替换您的formset的名称适当)。

total_form_countinitial_form_count 

initial_form_count有一些与total_form_countBaseFormSetManagementForm密切相关的方法。

total_form_count返回此表单集中的表单总数。 initial_form_count返回Formset中预填充的表单数,也用于确定需要多少表单。你可能永远不需要重写这些方法,所以请确保你明白他们做什么之前这样做。

empty_form

__prefix__提供了一个附加属性BaseFormSet,它返回一个前缀为empty_form的表单实例,以便于使用JavaScript的动态表

自定义表单验证

一个formset有一个类似于Form类的clean方法。 这是您定义自己的验证,在formset级别工作:

>>> from django.forms import BaseFormSet
>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm

>>> class BaseArticleFormSet(BaseFormSet):
...     def clean(self):
...         """Checks that no two articles have the same title."""
...         if any(self.errors):
...             # Don't bother validating the formset unless each form is valid on its own
...             return
...         titles = []
...         for form in self.forms:
...             title = form.cleaned_data['title']
...             if title in titles:
...                 raise forms.ValidationError("Articles in a set must have distinct titles.")
...             titles.append(title)

>>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet)
>>> data = {
...     'form-TOTAL_FORMS': '2',
...     'form-INITIAL_FORMS': '0',
...     'form-MAX_NUM_FORMS': '',
...     'form-0-title': 'Test',
...     'form-0-pub_date': '1904-06-16',
...     'form-1-title': 'Test',
...     'form-1-pub_date': '1912-06-23',
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
False
>>> formset.errors
[{}, {}]
>>> formset.non_form_errors()
['Articles in a set must have distinct titles.']

在所有clean方法被调用后,调用formset Form.clean方法。 将使用表单集上的non_form_errors()方法找到错误。

验证表单集中的表单数

Django 提供了两种方法去检查表单能够提交的最大数和最小数, 应用如果需要更多的关于提交数量的自定义验证逻辑,应该使用自定义表单击验证

validate_max

I如果max_num 被提交给 formset_factory(), validation 将在数据集中检查被提交表单的数量, 减去被标记删除的, 必须小于等于validate_max=True.

>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm, max_num=1, validate_max=True)
>>> data = {
...     'form-TOTAL_FORMS': '2',
...     'form-INITIAL_FORMS': '0',
...     'form-MIN_NUM_FORMS': '',
...     'form-MAX_NUM_FORMS': '',
...     'form-0-title': 'Test',
...     'form-0-pub_date': '1904-06-16',
...     'form-1-title': 'Test 2',
...     'form-1-pub_date': '1912-06-23',
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
False
>>> formset.errors
[{}, {}]
>>> formset.non_form_errors()
['Please submit 1 or fewer forms.']

max_num validates 将会对validate_max=True 严格限制,即使提供的初始数据超过 max_num 而导致其无效

validate_min

如果min_num被传递到formset_factory(),验证也将检查数据集中的表格数量减去那些被标记为删除的表格数量大于或等于到validate_min=True

>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm, min_num=3, validate_min=True)
>>> data = {
...     'form-TOTAL_FORMS': '2',
...     'form-INITIAL_FORMS': '0',
...     'form-MIN_NUM_FORMS': '',
...     'form-MAX_NUM_FORMS': '',
...     'form-0-title': 'Test',
...     'form-0-pub_date': '1904-06-16',
...     'form-1-title': 'Test 2',
...     'form-1-pub_date': '1912-06-23',
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
False
>>> formset.errors
[{}, {}]
>>> formset.non_form_errors()
['Please submit 3 or more forms.']

处理表单的排序和删除

formset_factory()提供两个可选参数can_order 和can_delete 来实现表单集中表单的排序和删除。

can_order

BaseFormSet。 can_order T0> 

默认值:False

使你创建能排序的表单集。

>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm, can_order=True)
>>> formset = ArticleFormSet(initial=[
...     {'title': 'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
...     {'title': 'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
... ])
>>> for form in formset:
...     print(form.as_table())
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" value="Article #1" id="id_form-0-title" /></td></tr>
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" value="2008-05-10" id="id_form-0-pub_date" /></td></tr>
<tr><th><label for="id_form-0-ORDER">Order:</label></th><td><input type="number" name="form-0-ORDER" value="1" id="id_form-0-ORDER" /></td></tr>
<tr><th><label for="id_form-1-title">Title:</label></th><td><input type="text" name="form-1-title" value="Article #2" id="id_form-1-title" /></td></tr>
<tr><th><label for="id_form-1-pub_date">Pub date:</label></th><td><input type="text" name="form-1-pub_date" value="2008-05-11" id="id_form-1-pub_date" /></td></tr>
<tr><th><label for="id_form-1-ORDER">Order:</label></th><td><input type="number" name="form-1-ORDER" value="2" id="id_form-1-ORDER" /></td></tr>
<tr><th><label for="id_form-2-title">Title:</label></th><td><input type="text" name="form-2-title" id="id_form-2-title" /></td></tr>
<tr><th><label for="id_form-2-pub_date">Pub date:</label></th><td><input type="text" name="form-2-pub_date" id="id_form-2-pub_date" /></td></tr>
<tr><th><label for="id_form-2-ORDER">Order:</label></th><td><input type="number" name="form-2-ORDER" id="id_form-2-ORDER" /></td></tr>

它会给每个表单添加一个字段, 这个新字段名为ORDER,是一个forms.IntegerField。 它根据初始数据,为这些表单自动生成数值。 下面让我们看一下,如果用户改变这个值会发生什么变化:

>>> data = {
...     'form-TOTAL_FORMS': '3',
...     'form-INITIAL_FORMS': '2',
...     'form-MAX_NUM_FORMS': '',
...     'form-0-title': 'Article #1',
...     'form-0-pub_date': '2008-05-10',
...     'form-0-ORDER': '2',
...     'form-1-title': 'Article #2',
...     'form-1-pub_date': '2008-05-11',
...     'form-1-ORDER': '1',
...     'form-2-title': 'Article #3',
...     'form-2-pub_date': '2008-05-01',
...     'form-2-ORDER': '0',
... }

>>> formset = ArticleFormSet(data, initial=[
...     {'title': 'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
...     {'title': 'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
... ])
>>> formset.is_valid()
True
>>> for form in formset.ordered_forms:
...     print(form.cleaned_data)
{'pub_date': datetime.date(2008, 5, 1), 'ORDER': 0, 'title': 'Article #3'}
{'pub_date': datetime.date(2008, 5, 11), 'ORDER': 1, 'title': 'Article #2'}
{'pub_date': datetime.date(2008, 5, 10), 'ORDER': 2, 'title': 'Article #1'}

can_delete

BaseFormSet。 can_delete T0> 

默认值:False

使你创建一个表单集,可以选择删除一些表单。

>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm, can_delete=True)
>>> formset = ArticleFormSet(initial=[
...     {'title': 'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
...     {'title': 'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
... ])
>>> for form in formset:
...     print(form.as_table())
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" value="Article #1" id="id_form-0-title" /></td></tr>
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" value="2008-05-10" id="id_form-0-pub_date" /></td></tr>
<tr><th><label for="id_form-0-DELETE">Delete:</label></th><td><input type="checkbox" name="form-0-DELETE" id="id_form-0-DELETE" /></td></tr>
<tr><th><label for="id_form-1-title">Title:</label></th><td><input type="text" name="form-1-title" value="Article #2" id="id_form-1-title" /></td></tr>
<tr><th><label for="id_form-1-pub_date">Pub date:</label></th><td><input type="text" name="form-1-pub_date" value="2008-05-11" id="id_form-1-pub_date" /></td></tr>
<tr><th><label for="id_form-1-DELETE">Delete:</label></th><td><input type="checkbox" name="form-1-DELETE" id="id_form-1-DELETE" /></td></tr>
<tr><th><label for="id_form-2-title">Title:</label></th><td><input type="text" name="form-2-title" id="id_form-2-title" /></td></tr>
<tr><th><label for="id_form-2-pub_date">Pub date:</label></th><td><input type="text" name="form-2-pub_date" id="id_form-2-pub_date" /></td></tr>
<tr><th><label for="id_form-2-DELETE">Delete:</label></th><td><input type="checkbox" name="form-2-DELETE" id="id_form-2-DELETE" /></td></tr>

can_order类似,这为每个名为DELETE的表单添加一个新字段,是一个forms.BooleanField。 如下,你可以通过deleted_forms来获取标记删除字段的数据:

>>> data = {
...     'form-TOTAL_FORMS': '3',
...     'form-INITIAL_FORMS': '2',
...     'form-MAX_NUM_FORMS': '',
...     'form-0-title': 'Article #1',
...     'form-0-pub_date': '2008-05-10',
...     'form-0-DELETE': 'on',
...     'form-1-title': 'Article #2',
...     'form-1-pub_date': '2008-05-11',
...     'form-1-DELETE': '',
...     'form-2-title': '',
...     'form-2-pub_date': '',
...     'form-2-DELETE': '',
... }

>>> formset = ArticleFormSet(data, initial=[
...     {'title': 'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
...     {'title': 'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
... ])
>>> [form.cleaned_data for form in formset.deleted_forms]
[{'DELETE': True, 'pub_date': datetime.date(2008, 5, 10), 'title': 'Article #1'}]

如果你使用 ModelFormSet,调用 formset.save() 将删除那些有删除标记的表单的模型实例。

如果你调用formset.save(commit=False), 对像将不会被自动删除。 你需要调用formset.deleted_objects每个对像的 delete() 来真正删除他们。

将自定义参数传递给表单集

有时,您的表单类会使用自定义参数,例如MyArticleForm。 在实例化表单集时可以传递此参数:

>>> from django.forms import BaseFormSet
>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm

>>> class MyArticleForm(ArticleForm):
...     def __init__(self, *args, **kwargs):
...         self.user = kwargs.pop('user')
...         super(MyArticleForm, self).__init__(*args, **kwargs)

>>> ArticleFormSet = formset_factory(MyArticleForm)
>>> formset = ArticleFormSet(form_kwargs={'user': request.user})

form_kwargs也可能取决于具体的窗体实例。 formset基类提供了一个get_form_kwargs方法。 该方法采用单个参数 - 表单中的表单的索引。 empty_form的索引为None

>>> from django.forms import BaseFormSet
>>> from django.forms import formset_factory

>>> class BaseArticleFormSet(BaseFormSet):
...     def get_form_kwargs(self, index):
...         kwargs = super(BaseArticleFormSet, self).get_form_kwargs(index)
...         kwargs['custom_kwarg'] = index
...         return kwargs

在视图和模板中使用表单集

在视图中使用表单集就像使用标准的Form 类一样简单, 唯一要做的就是确信你在模板中处理表单。 让我们看一个简单视图:

from django.forms import formset_factory
from django.shortcuts import render
from myapp.forms import ArticleForm

def manage_articles(request):
    ArticleFormSet = formset_factory(ArticleForm)
    if request.method == 'POST':
        formset = ArticleFormSet(request.POST, request.FILES)
        if formset.is_valid():
            # do something with the formset.cleaned_data
            pass
    else:
        formset = ArticleFormSet()
    return render(request, 'manage_articles.html', {'formset': formset})

manage_articles.html 模板也可以像这样:

<form method="post" action="">
    {{ formset.management_form }}
    <table>
        {% for form in formset %}
        {{ form }}
        {% endfor %}
    </table>
</form>

不过,上面可以用一个快捷写法,让表单集来分发管理表单:

<form method="post" action="">
    <table>
        {{ formset }}
    </table>
</form>

上面表单集调用 as_table 方法。

手动呈现can_deletecan_order 

如果手动在模板中渲染字段,则可以使用{{ form.DELETE }}呈现can_delete参数/ T5> T2>:

<form method="post" action="">
    {{ formset.management_form }}
    {% for form in formset %}
        <ul>
            <li>{{ form.title }}</li>
            <li>{{ form.pub_date }}</li>
            {% if formset.can_delete %}
                <li>{{ form.DELETE }}</li>
            {% endif %}
        </ul>
    {% endfor %}
</form>

类似地,如果表单集有能力(can_order=True),则可以使用{{ form.ORDER t4> }}

在视图中使用多个表单集

可以在视图中使用多个表单集, 表单集从表单中借鉴了很多方法 你可以使用 prefix 给每个表单字段添加前缀,以允许多个字段传递给视图,而不发生命名冲突 让我们看看可以怎么做

from django.forms import formset_factory
from django.shortcuts import render
from myapp.forms import ArticleForm, BookForm

def manage_articles(request):
    ArticleFormSet = formset_factory(ArticleForm)
    BookFormSet = formset_factory(BookForm)
    if request.method == 'POST':
        article_formset = ArticleFormSet(request.POST, request.FILES, prefix='articles')
        book_formset = BookFormSet(request.POST, request.FILES, prefix='books')
        if article_formset.is_valid() and book_formset.is_valid():
            # do something with the cleaned_data on the formsets.
            pass
    else:
        article_formset = ArticleFormSet(prefix='articles')
        book_formset = BookFormSet(prefix='books')
    return render(request, 'manage_articles.html', {
        'article_formset': article_formset,
        'book_formset': book_formset,
    })

你可以以正常的方式渲染模板。 记住 prefix 在POST请求和非POST 请求中均需设置,以便他能渲染和执行正确

(11)表单和字段验证

表单验证发生在数据验证之后。 如果你想自定义这个过程,有不同的地方可以进行更改,每个都有不同的用途。 表单处理过程中要运行三种类别的验证方法。 它们通常在你调用表单的is_valid() 方法时执行。 还有其他一些事情也可以触发清理和验证(访问errors属性或直接调用full_clean()),但通常不需要它们。

一般情况下,如果处理的数据有问题,每个类别的验证方法都会引发ValidationError,并将相关信息传递给ValidationErrorSee below中引发ValidationError 的最佳实践。 如果没有引发ValidationError,这些方法应该返回验证后的(规整化的)数据的Python 对象。

大部分应该可以使用validators 完成,它们可以很容易地重用。 Validators 是简单的函数(或可调用对象),它们接收一个参数并对非法的输入抛出ValidationError。 Validators 在字段的to_python 和validate 方法调用之后运行。

表单的验证分为几个步骤,可以自定义或覆盖:

  • Field上的to_python()方法是每次验证的第一步。 它强制该值为正确的数据类型,并引发ValidationError,如果这是不可能的。 这个方法从Widget 接收原始的值并返回转换后的值。 例如,一个FloatField将数据转换成一个Python float或者提起一个ValidationError

  • Field上的validate()方法处理不适合验证器的字段特定验证。 它需要一个被强制为正确的数据类型的值,并在任何错误上引发ValidationError。 这个方法不返回任何东西且不应该改变任何值。 当你遇到不可以或不想放在validator 中的验证逻辑时,应该覆盖它来处理验证。

  • Field上的run_validators()方法运行所有字段的验证器,并将所有错误聚合到单个ValidationError中。 你应该不需要覆盖这个方法。

  • Field子类的clean()方法负责运行to_python()validate()run_validators()以正确的顺序传播错误。 如果任何时刻、任何方法引发ValidationError,验证将停止并引发这个错误。 这个方法返回验证后的数据,这个数据在后面将插入到表单的 cleaned_data 字典中。

  • 在表单子类中调用clean_<fieldname>()方法,其中<fieldname>替换为表单域属性的名称。 这个方法完成于特定属性相关的验证,这个验证与字段的类型无关。 这个方法没有任何传入的参数。 你需要查找clean() 中该字段的值,记住此时它已经是一个Python 对象而不是表单中提交的原始字符串(它位于cleaned_data 中是因为字段的self.cleaned_data 方法已经验证过一次数据)。

    例如,如果你想验证名为clean_serialnumber() 的serialnumber 的内容是否唯一, CharField 将是实现这个功能的理想之处。你需要的不是一个特别的字段(它只是一个CharField),而是一个特定于表单字段特定验证,并规整化数据。

    此方法的返回值将替换cleaned_data中的现有值,因此它必须是来自cleaned_data的字段值(即使此方法未更改)或新的清洁价值。

  • 表单子类的clean()方法可以执行需要访问多个表单字段的验证。 这是您可以在哪里进行检查,例如“如果提供了字段A,字段B必须包含有效的电子邮件地址”。 这个方法可以返回一个完全不同的字典,该字典将用作cleaned_data

    因为字段的验证方法在调用clean() 时会运行,你还可以访问表单的errors 属性,它包含验证每个字段时的所有错误。

    注意,你覆盖的Form.clean() 引发的任何错误将不会与任何特定的字段关联。 它们位于一个特定的“字段”(叫做__all__)中,如果需要可以通过 non_field_errors() 方法访问。 如果你想添加一个特定字段的错误到表单中,需要调用 add_error()

    还要注意,覆盖clean() 子类的ModelForm 方法需要特殊的考虑。 (更多信息参见ModelForm documentation)。

这些方法按以上给出的顺序执行,一次验证一个字段。 也就是说,对于表单中的每个字段(按它们在表单定义中出现的顺序),先运行Field.clean() ,然后运行clean_<fieldname>()。 每个字段的这两个方法都执行完之后,最后运行Form.clean() 方法,无论前面的方法是否抛出过异常。

下面有上面每个方法的示例。

我们已经提到过,所有这些方法都可以抛出ValidationError。 对于任何字段,如果Field.clean()方法引发了一个ValidationError,则不会调用任何字段特定的清除方法。 但是,剩余的字段的验证方法仍然会执行。

ValidationError 

为了让错误信息更加灵活或容易重写,请考虑下面的准则:给构造函数提供一个富有描述性的错误码code

# Good
ValidationError(_('Invalid value'), code='invalid')

# Bad
ValidationError(_('Invalid value'))

不要将变量强加到消息中;使用占位符和构造函数的params参数:

# Good
ValidationError(
    _('Invalid value: %(value)s'),
    params={'value': '42'},
)

# Bad
ValidationError(_('Invalid value: %s') % value)

使用字典参数而不要用位置参数。 这使得重写错误信息时不用考虑变量的顺序或者完全省略它们:

# Good
ValidationError(
    _('Invalid value: %(value)s'),
    params={'value': '42'},
)

# Bad
ValidationError(
    _('Invalid value: %s'),
    params=('42',),
)

gettext 封装错误消息使得它可以翻译:

# Good
ValidationError(_('Invalid value'))

# Bad
ValidationError('Invalid value')

所有的准则放在一起就是:

raise ValidationError(
    _('Invalid value: %(value)s'),
    code='invalid',
    params={'value': '42'},
)

如果你想编写可重用的表单、表单字段和模型字段,遵守这些准则是非常必要的。

如果你在验证的最后(例如,表单的clean() 方法)且知道永远 不需要重新错误信息,虽然不提倡但你仍然可以选择重写不详细的信息:

ValidationError(_('Invalid value: %s') % value)

Form.errors.as_data() 和Form.errors.as_json() 方法很大程度上受益于code(利用params 名和ValidationError 字典)。

提高多个错误

如果在一个验证方法中检查到多个错误并且希望将它们都反馈给表单的提交者,可以传递一个错误的列表给ValidationError 构造函数。

和上面一样,建议传递的列表中的params 实例都带有 code 和ValidationError,但是传递一个字符串列表也可以工作:

# Good
raise ValidationError([
    ValidationError(_('Error 1'), code='error1'),
    ValidationError(_('Error 2'), code='error2'),
])

# Bad
raise ValidationError([
    _('Error 1'),
    _('Error 2'),
])

在实践中使用验证

前面几节解释在一般情况下表单的验证是如何工作的。 因为有时直接看功能在实际中的应用会更容易掌握,下面是一些列小例子,它们用到前面的每个功能。

使用验证器

Django 的表单(以及模型)字段支持使用简单的函数和类用于验证,它们叫做Validator。 Validator 是可调用对象或函数,它接收一个值,如果该值合法则什么也不返回,否则抛出ValidationError。 它们可以通过字段的validators 参数传递给字段的构造函数,或者定义在Field 类的default_validators 属性中。

简单的Validator 可以用于在字段内部验证值,让我们看下Django 的SlugField

from django.forms import CharField
from django.core import validators

class SlugField(CharField):
    default_validators = [validators.validate_slug]

正如你所看到的,SlugField 只是一个带有自定义Validator 的CharField,它们验证提交的文本符合某些字符规则。 这也可以在字段定义时实现,所以:

slug = forms.SlugField()

等同于:

slug = forms.CharField(validators=[validators.validate_slug])

常见的情形,例如验证邮件地址和正则表达式,可以使用Django 中已经存在的Validator 类处理。 例如,validators.validate_slug 是RegexValidator 的一个实例,它构造时的第一个参数为:^[-a-zA-Z0-9_]+$。 writing validators 一节可以查到已经存在的Validator 以及如何编写Validator 的一个示例。

表单域默认清除

让我们首先创建一个自定义的表单字段,它验证其输入是一个由逗号分隔的邮件地址组成的字符串。 完整的类像这样:

from django import forms
from django.core.validators import validate_email

class MultiEmailField(forms.Field):
    def to_python(self, value):
        """Normalize data to a list of strings."""
        # Return an empty list if no input was given.
        if not value:
            return []
        return value.split(',')

    def validate(self, value):
        """Check if value consists only of valid emails."""
        # Use the parent's handling of required fields, etc.
        super(MultiEmailField, self).validate(value)
        for email in value:
            validate_email(email)

使用这个字段的每个表单都将在处理该字段数据之前运行这些方法。 这个验证特定于该类型的字段,与后面如何使用它无关。

让我们来创建一个简单的ContactForm 来向你演示如何使用这个字段:

class ContactForm(forms.Form):
    subject = forms.CharField(max_length=100)
    message = forms.CharField()
    sender = forms.EmailField()
    recipients = MultiEmailField()
    cc_myself = forms.BooleanField(required=False)

只需要简单地使用MultiEmailField,就和其它表单字段一样。 当调用表单的to_python() 方法时,MultiEmailField.clean() 方法将作为验证过程的一部分运行,它将调用自定义的is_valid() 和validate() 方法。

清理特定字段属性

继续前面的例子,假设在ContactForm中,我们要确保recipients字段始终包含地址"fred@example.com" 这是对我们表单特定的验证,所以我们不想把它放在一般的MultiEmailField类中。 相反,我们写一个在recipients字段上运行的清理方法,像这样:

from django import forms

class ContactForm(forms.Form):
    # Everything as before.
    ...

    def clean_recipients(self):
        data = self.cleaned_data['recipients']
        if "fred@example.com" not in data:
            raise forms.ValidationError("You have forgotten about Fred!")

        # Always return a value to use as the new cleaned data, even if
        # this method didn't change it.
        return data

清理和验证相互依赖的字段

假设我们向联系表单添加了另一个要求:如果cc_myself字段是True,则subject必须包含单词"help" 我们一次在多个字段上执行验证,因此表单的clean()方法是一个很好的选择。 请注意,我们正在谈论这里的表单上的clean()方法,而较早的我们在一个字段上写了一个clean()方法。 在确定哪些地方进行验证时,保持领域和形式差异很重要。 字段是单个数据点,表单是字段的集合。

在调用表单clean() 方法的时候,所有字段的验证方法已经执行完(前两节),所以self.cleaned_data 填充的是目前为止已经合法的数据。 所以你需要记住这个事实,你需要验证的字段可能没有通过初试的字段检查。

在这一步,有两种方法报告错误。 最简单的方法是在表单的顶端显示错误。 你可以在ValidationError 方法中抛出clean() 来创建错误。 像这样:

from django import forms

class ContactForm(forms.Form):
    # Everything as before.
    ...

    def clean(self):
        cleaned_data = super(ContactForm, self).clean()
        cc_myself = cleaned_data.get("cc_myself")
        subject = cleaned_data.get("subject")

        if cc_myself and subject:
            # Only do something if both fields are valid so far.
            if "help" not in subject:
                raise forms.ValidationError(
                    "Did not send for 'help' in the subject despite "
                    "CC'ing yourself."
                )

在这段代码中,如果抛出验证错误,表单将在表单的顶部显示(通常是)描述该问题的一个错误信息。

在示例代码中调用super(ContactForm, self).clean()可以确保父类中的任何验证逻辑都被维护。 If your form inherits another that doesn’t return a cleaned_data dictionary in its clean() method (doing so is optional), then don’t assigncleaned_data to the result of the super() call and use self.cleaned_data instead:

def clean(self):
    super(ContactForm, self).clean()
    cc_myself = self.cleaned_data.get("cc_myself")
    ...

报告验证错误的第二种方法可能包括将错误消息分配给其中一个字段。 在这种情况下,让我们在表单的显示中分别关联一个错误信息到“subject” 和“cc_myself” 行。 在实际应用中要小心,因为它可能导致表单的输出变得令人困惑。 我们只是向你展示这里可以怎么做,在特定的情况下,需要你和你的设计人员确定什么是好的方法。 我们的新代码(代替前面的示例)像这样:

from django import forms

class ContactForm(forms.Form):
    # Everything as before.
    ...

    def clean(self):
        cleaned_data = super(ContactForm, self).clean()
        cc_myself = cleaned_data.get("cc_myself")
        subject = cleaned_data.get("subject")

        if cc_myself and subject and "help" not in subject:
            msg = "Must put 'help' in subject when cc'ing yourself."
            self.add_error('cc_myself', msg)
            self.add_error('subject', msg)

add_error() 的第二个参数可以是一个简单的字符串,但更倾向是ValidationError 的一个实例。 更多细节参见Raising ValidationError。 注意,add_error() 将从cleaned_data 中删除相应的字段。

用户认证系统

1.概述

Django认证系统同时处理认证和授权。 简单地讲,认证验证一个用户是否它们声称的那个人,授权决定一个通过了认证的用户被允许做什么。 这里的词语“认证”同时指代这两项任务。

认证系统包含:

  • 用户
  • 权限:二元(是/否)标志指示一个用户是否可以做一个特定的任务。
  • 组:对多个用户运用标签和权限的一种通用的方式。
  • 一个可配置的密码哈希系统
  • 用户登录或内容显示的表单和视图
  • 一个可插拔的后台系统

Django中的认证系统致力于变得非常通用,但它不提供在web认证系统中某些常见的功能。 某些常见问题的解决方法已经在第三方包中实现:

  • 密码强度检查
  • 登录尝试的制约
  • 第三方认证(例如OAuth)

安装

认证的支持作为Django的一个contrib模块,打包于django.contrib.auth中。 默认情况下,要求的配置已经包含在django-admin startproject 生成的settings.py 中,它们的组成包括INSTALLED_APPS 设置中的两个选项:

  1. 'django.contrib.auth'包含认证框架的核心和默认的模型。
  2. 'django.contrib.contenttypes'是Django内容类型系统,它允许权限与你创建的模型关联。

MIDDLEWARE设置中的这些条目:

  1. SessionMiddleware跨请求管理sessions
  2. AuthenticationMiddleware使用会话将用户与请求关联起来。

有了这些设置,运行manage.py migrate命令将为认证相关的模型创建必要的数据库表并为你的应用中定义的任意模型创建权限。

 2.使用认证系统

 这篇文档解释默认配置下Django认证系统的使用。 这些配置已经逐步可以满足大部分常见项目的需要,可以处理范围非常广泛的任务,且具有一套细致的密码和权限实现。 对于需要与默认配置不同需求的项目,Django支持extension and customization认证。

Django的认证同时提供认证和授权,并通常统一称为认证系统,因为这些功能某些地方是耦合的。

User对象

User对象是认证系统的核心。 它们通常表示与你的站点进行交互的用户,并用于启用限制访问、注册用户信息和给创建者关联内容等。 在Django的认证框架中只存在一种类型的用户,因此诸如'superusers'或管理员'staff'用户只是具有特殊属性集的user对象,而不是不同类型的user对象。

默认user的基本属性有:

完整的参考请参阅full API documentation,以下的内容更偏重特定的任务。

创建用户

创建users最直接的方法是使用create_user()辅助函数:

>>> from django.contrib.auth.models import User
>>> user = User.objects.create_user('john', 'lennon@thebeatles.com', 'johnpassword')

# 到这里,user 这一个User对象已经保存于
# 数据库中了。 # 你可以继续修改它的属性。
# 如果你想要修改其他字段。
>>> user.last_name = 'Lennon'
>>> user.save()

如果你已经安装了Django admin,你也可以create users interactively.

创建超级用户

使用createsuperuser命令创建superusers:

$ python manage.py createsuperuser --username=joe --email=joe@example.com

将会提示你输入一个密码。 在你输入一个密码后,该user将会立即创建。 如果您离开--username--email选项,它将提示您输入这些值。

更改密码

Django不会在user模型上存储原始的(明文)密码,而只是一个哈希(完整的细节参见documentation of how passwords are managed)。 因为这个原因,不要尝试直接操作user的password属性。 这也是为什么创建一个user时要使用辅助函数。

若要修改一个用户的密码,你有几种选择:

manage.py changepassword *username*提供了一种从命令行更改用户密码的方法。 它提示你修改一个给定user的密码,你必须输入两次。 如果它们匹配,新的密码将会立即修改。 如果你没有提供user,命令行将尝试修改与当前系统用户匹配的用户名的密码。

你也可以通过程序修改密码,使用set_password()

>>> from django.contrib.auth.models import User
>>> u = User.objects.get(username='john')
>>> u.set_password('new password')
>>> u.save()

如果你安装了Django admin,你还可以在authentication system’s admin pages修改user的密码。

Django还提供viewsforms用于允许user修改他们自己密码。

更改用户密码将会注销所有会话。 详细信息请参阅Session invalidation on password change

认证用户

authenticate(request=None**credentials)[source]

使用authenticate()来验证一组凭据。 它以credentials为关键字参数,默认为usernamepassword,根据每个认证的后端进行检查,如果credentials对某个后端有效则返回一个User对象。 如果credentials对任何后端都无效,或者如果后端引发了PermissionDenied,则返回None。 像这样:

from django.contrib.auth import authenticate
user = authenticate(username='john', password='secret')
if user is not None:
    # A backend authenticated the credentials
else:
    # No backend authenticated the credentials

request是可选的HttpRequest,它在认证后端的authenticate()方法上传递。

权限和授权

Django本身提供了一个简单的权限系统。 它提供了一种为特定用户和用户组分配权限的方法。

它被Django的admin站点使用,但欢迎你在你自己的代码中使用。

Django admin 站点使用如下的权限:

  • 拥有该类型对象"add"权限的用户才可以访问"add"表单以及添加一个该类型对象。
  • 查看修改列表、查看“change”表单以及修改一个对象的权利只限于具有该类型对象的“change”权限的用户拥有。
  • 用户必须在一个对象上具有“delete”权限,才能删除这个对象。

权限不但可以根据每个对象的类型,而且可以根据特定的对象实例设置。 通过使用ModelAdmin类提供的has_add_permission()has_change_permission()has_delete_permission()方法,可以针对相同类型的不同对象实例自定义权限。

User对象具有两个多对多的字段:groupsuser_permissions。 User对象可以用和其它Django模型一样的方式访问它们相关联的对象:

myuser.groups.set([group_list])
myuser.groups.add(group, group, ...)
myuser.groups.remove(group, group, ...)
myuser.groups.clear()
myuser.user_permissions.set([permission_list])
myuser.user_permissions.add(permission, permission, ...)
myuser.user_permissions.remove(permission, permission, ...)
myuser.user_permissions.clear()

默认权限

django.contrib.auth在你的INSTALLED_APPS设置中列出时,它将确保为你安装的应用中的每个Django模型创建3个默认的权限 – add、change和delete。

当你运行manage.py migrate时,将创建这些权限;在django.contrib.auth添加到INSTALLED_APPS之后,首次运行migrate时,将为所有先前安装的模型创建默认权限,以及当时安装的任何新模型。 之后,每次运行manage.py migrate,它将为新的模型创建默认的权限(创建权限的函数与post_migrate信号连接)。

假设你有个app_label叫做foo的应用,这个应用有一个名为Bar的模型,要测试基本的权限,你应该使用:

  • 添加:user.has_perm('foo.add_bar')
  • 更改:user.has_perm('foo.change_bar')
  • 删除:user.has_perm('foo.delete_bar')

很少直接访问Permission模型。

Groups

django.contrib.auth.models.Group模型是用户分类的一种通用的方式,通过这种方式你可以应用权限或其它标签到这些用户。 一个用户可以属于任意多个组。

组中某个用户自动具有赋给那个组的权限。 例如,如果组Site editors具有权限 can_edit_home_page,那么该组中的任何用户都具有该权限。

除权限之外,组还是给用户分类的一种方便的方法以给他们某些标签或扩展的功能。 例如,你可以创建一个组'Special users',然后你可以这样写代码,给他们访问你的站点仅限会员的部分,或者给他们发仅限于会员的邮件。

以编程方式创建权限

虽然custom permissions可以定义在模型的Meta类中,但你也可以直接创建权限。 例如,您可以在myapp中为BlogPost模型创建can_publish权限:

from myapp.models import BlogPost
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType

content_type = ContentType.objects.get_for_model(BlogPost)
permission = Permission.objects.create(
    codename='can_publish',
    name='Can Publish Posts',
    content_type=content_type,
)

然后该权限可以通过user_permissions属性分配给一个User,或者通过permissions属性分配给Group

权限的缓存

在第一次获取权限用于检查后,模型的后端将在该用户对象上缓存这些权限。 这对于常见的请求-响应周期通常没问题,因为通常在添加权限后不会立即检查权限(例如在管理后台中)。 如果你要添加权限并立即检查它们,例如在测试中或视图中,最简单的解决方案是从数据库重新获取用户。 像这样:

from django.contrib.auth.models import Permission, User
from django.contrib.contenttypes.models import ContentType
from django.shortcuts import get_object_or_404

from myapp.models import BlogPost

def user_gains_perms(request, user_id):
    user = get_object_or_404(User, pk=user_id)
    # any permission check will cache the current set of permissions
    user.has_perm('myapp.change_blogpost')

    content_type = ContentType.objects.get_for_model(BlogPost)
    permission = Permission.objects.get(
        codename='change_blogpost',
        content_type=content_type,
    )
    user.user_permissions.add(permission)

    # Checking the cached permission set
    user.has_perm('myapp.change_blogpost')  # False

    # Request new instance of User
    # Be aware that user.refresh_from_db() won't clear the cache.
    user = get_object_or_404(User, pk=user_id)

    # Permission cache is repopulated from the database
    user.has_perm('myapp.change_blogpost')  # True

    ...

Web请求中的认证

Django使用会话和中间件来拦截认证系统到请求对象中。

它们在每个请求上提供一个request.user属性,表示当前的用户。 如果当前的用户没有登入,该属性将设置成AnonymousUser的一个实例,否则它将是User的实例。

你可以使用is_authenticated将它们区分开,如下所示:

if request.user.is_authenticated:
    # Do something for authenticated users.
    ...
else:
    # Do something for anonymous users.
    ...

如何登录用户

如果你有一个认证了的用户,你想把它附带到当前的会话中 - 这可以通过login()函数完成。

login(requestuserbackend=None)[source]

从视图中登入一个用户,请使用login()。 它接受一个HttpRequest对象和一个User对象。 login()使用Django的session框架来将用户的ID保存在session中。

请注意,匿名会话期间的任何数据集在用户登录后都会保留在会话中。

下面的示例向你演示如何使用authenticate() 和login()

from django.contrib.auth import authenticate, login

def my_view(request):
    username = request.POST['username']
    password = request.POST['password']
    user = authenticate(request, username=username, password=password)
    if user is not None:
        login(request, user)
        # Redirect to a success page.
        ...
    else:
        # Return an 'invalid login' error message.
        ...

在旧版本中,当你手工登陆一个用户时,在调用login()之前必须authenticate()成功认证这个用户。 现在你可以使用新的backend参数设置后端。

选择验证后端

用户登录时,用户的ID和用于身份验证的后端保存在用户的会话中。 这允许相同的身份验证后端在将来的请求中获取用户的详细信息。 要保存在会话中的认证后端选择如下:

  1. 使用可选的backend参数的值(如果提供)。
  2. 使用user.backend属性的值(如果存在)。 这允许配对authenticate()login()authenticate()设置user.backend属性用户对象返回。
  3. 如果只有一个,请使用AUTHENTICATION_BACKENDS中的backend
  4. 否则,引发异常。

在情况1和2中,backend参数或user.backend属性的值应为点号导入路径字符串(如AUTHENTICATION_BACKENDS的字符串),而不是实际的类。

如何登出用户

logout(request)[source]

若要登出一个已经通过django.contrib.auth.login()登入的用户,可以在你的视图中使用django.contrib.auth.logout()。 它接收一个HttpRequest对象且没有返回值。 例如:

from django.contrib.auth import logout

def logout_view(request):
    logout(request)
    # Redirect to a success page.

注意,即使用户没有登入,logout()也不会抛出任何错误。

当您调用logout()时,当前请求的会话数据将被彻底清除。 所有存在的数据都将清除。 这是为了防止另外一个人使用相同的Web浏览器登入并访问前一个用户的会话数据。 如果你想在用户登出之后可以立即访问放入会话中的数据,请在调用django.contrib.auth.logout()之后放入。

限制对登录用户的访问

原始方式

限制访问页面的简单原始方法是检查request.user.is_authenticated,并重定向到登录页面:

from django.conf import settings
from django.shortcuts import redirect

def my_view(request):
    if not request.user.is_authenticated:
        return redirect('%s?next=%s' % (settings.LOGIN_URL, request.path))
    # ...

...或显示错误信息:

from django.shortcuts import render

def my_view(request):
    if not request.user.is_authenticated:
        return render(request, 'myapp/login_error.html')
    # ...

login_required装饰器

login_required(redirect_field_name='next'login_url=None)[source]

作为一个快捷方式,你可以使用便捷的login_required()装饰器:

from django.contrib.auth.decorators import login_required

@login_required
def my_view(request):
    ...

login_required()完成下面的事情:

  • 如果用户没有登录,则重定向到settings.LOGIN_URL,传递查询字符串中的当前绝对路径。 例如:/accounts/login/?next=/polls/3/
  • 如果用户已经登入,则正常执行视图。 视图的代码可以安全地假设用户已经登入。

默认情况下,在成功认证后用户应该被重定向的路径存储在查询字符串的一个叫做"next"的参数中。 如果对该参数你倾向使用一个不同的名字,login_required()带有一个可选的redirect_field_name参数:

from django.contrib.auth.decorators import login_required

@login_required(redirect_field_name='my_redirect_field')
def my_view(request):
    ...

注意,如果你提供一个值给redirect_field_name,你非常可能同时需要自定义你的登录模板,因为存储重定向路径的模板上下文变量将使用"next"值作为它的键,而不是默认的redirect_field_name

login_required()还带有一个可选的login_url参数。 例如:

from django.contrib.auth.decorators import login_required

@login_required(login_url='/accounts/login/')
def my_view(request):
    ...

注意,如果你没有指定login_url参数,你需要确保settings.LOGIN_URL与你的登录视图正确关联。 例如,使用默认值,可以添加下面几行到你的URLconf中:

from django.contrib.auth import views as auth_views

url(r'^accounts/login/$', auth_views.LoginView.as_view()),

settings.LOGIN_URL同时还接收视图函数名和named URL patterns。 这允许你自由地重新映射你的URLconf中的登录视图而不用更新设置。

LoginRequired mixin

使用class-based views时,可以使用LoginRequiredMixin实现与login_required相同的行为。 此mixin应位于继承列表中最左侧的位置。

class LoginRequiredMixin

如果视图正在使用此mixin,那么根据raise_exception参数,未经身份验证的用户的所有请求将被重定向到登录页面或显示HTTP 403 Forbidden错误。

您可以设置AccessMixin的任何参数来自定义未授权用户的处理:

from django.contrib.auth.mixins import LoginRequiredMixin

class MyView(LoginRequiredMixin, View):
    login_url = '/login/'
    redirect_field_name = 'redirect_to'

限制对通过测试的登录用户的访问

要根据某些权限或某些其他测试来限制访问权限,您可以执行与上一节中所述基本相同的操作。

简单的方法就是在视图中直接运行你对request.user的测试。 例如,视图检查用户的邮件属于特定的地址(例如@example.com),若不是,则重定向到登录页面。

from django.shortcuts import redirect

def my_view(request):
    if not request.user.email.endswith('@example.com'):
        return redirect('/login/?next=%s' % request.path)
    # ...

user_passes_test(test_funclogin_url=Noneredirect_field_name='next')[source]

你可以用方便的 False 装饰器,当回调函数返回 user_passes_test 时会执行一个重定向操作:

from django.contrib.auth.decorators import user_passes_test

def email_check(user):
    return user.email.endswith('@example.com')

@user_passes_test(email_check)
def my_view(request):
    ...

user_passes_test() 要求一个以User 对象为参数的回调函数,若用户允许访问此视图,返回 True。 注意,user_passes_test()不会自动检查 User 是否为匿名对象。

user_passes_test()接收两个额外的参数:

LOGIN_URL
让你指定那些没有通过检查的用户要重定向至哪里。 若不指定其值,它可能是默认的 settings.LOGIN_URL
redirect_field_name
login_required()的参数相同。 把它设置为 None 来把它从 URL 中移除,当你想把通不过检查的用户重定向到没有next page 的非登录页面时。

像这样:

@user_passes_test(email_check, login_url='/login/')
def my_view(request):
    ...

class UserPassesTestMixin

当使用class-based views时,可以使用UserPassesTestMixin来执行此操作。

test_func()

您必须覆盖类的test_func()方法来提供执行的测试。 此外,您可以设置AccessMixin的任何参数来自定义未授权用户的处理:

from django.contrib.auth.mixins import UserPassesTestMixin

class MyView(UserPassesTestMixin, View):

    def test_func(self):
        return self.request.user.email.endswith('@example.com')

get_test_func()

您也可以覆盖get_test_func()方法以使mixin对其检查使用不同命名的函数(而不是test_func())。

permission_required装饰器

permission_required(permlogin_url=Noneraise_exception=False)[source]

检查一个用户是否有指定的权限是相对常见的需求。 为此,Django为这种情况提供了一个快捷方式:permission_required()装饰器。:

from django.contrib.auth.decorators import permission_required

@permission_required('polls.can_vote')
def my_view(request):
    ...

就像has_perm()方法一样,权限名称采用"<app label>.<permission codename>"的形式,(例如polls.can_vote表示polls应用中一个模型的权限)。

装饰器也可以采取可迭代的权限,在这种情况下,用户必须具有所有权限才能访问视图。

请注意,permission_required()还需要一个可选的login_url参数:

from django.contrib.auth.decorators import permission_required

@permission_required('polls.can_vote', login_url='/loginpage/')
def my_view(request):
    ...

login_required()装饰器一样,login_url默认为settings.LOGIN_URL

如果提供了 raise_exception 参数,装饰器抛出PermissionDenied异常,使用 the 403 (HTTP Forbidden) view而不是重定向到登录页面。

如果你想使用raise_exception,还可以让你的用户有机会先登录,你可以添加login_required()装饰器:

from django.contrib.auth.decorators import login_required, permission_required

@login_required
@permission_required('polls.can_vote', raise_exception=True)
def my_view(request):
    ...

PermissionRequiredMixin mixin 

要对class-based views应用权限检查,可以使用PermissionRequiredMixin

class PermissionRequiredMixin

这个mixin,就像permission_required装饰器一样,检查访问视图的用户是否具有所有给定的权限。 您应该使用permission_required参数指定权限(或许可的迭代):

from django.contrib.auth.mixins import PermissionRequiredMixin

class MyView(PermissionRequiredMixin, View):
    permission_required = 'polls.can_vote'
    # Or multiple of permissions:
    permission_required = ('polls.can_open', 'polls.can_edit')

您可以设置AccessMixin的任何参数来自定义未授权用户的处理。

您也可以覆盖这些方法:

get_permission_required()

返回由mixin使用的许可名称的可迭代。 默认为permission_required属性,如有必要,转换为元组。

has_permission()

返回一个布尔值,表示当前用户是否具有执行装饰视图的权限。 默认情况下,返回使用get_permission_required()返回的权限列表调用has_perms()的结果。

3.自定义认证

Django自带的认证系统足够应付大多数情况,但你可能有特殊的需求,现成的默认认证系统不能满足。 自定义自己的项目的权限系统需要了解Django中哪些部分是能够扩展或替换的。 这个文档提供了如何定制权限系统的细节。

认证后端系统是可扩展的,可用于User模型存储的用户名和密码与Django的默认不同的服务进行认证。

你可为你的模型提供自定义权限,它们可以通过Django认证系统进行检查。

你可以扩展默认的User模型,或用完全自定义的模型替换

指定认证后端

在底层,Django维护一个“认证后端”的列表。 当调用django.contrib.auth.authenticate()时 — 如何登入一个用户中所描述的 — Django 会尝试所有的认证后端进行认证。 如果第一个认证方法失败,Django 将尝试第二个,以此类推,直至试完所有的认证后台。

使用的认证后台通过AUTHENTICATION_BACKENDS 设置指定。 这应该是指向Python类的Python路径名的列表,它们知道如何进行身份验证。 这些类可以位于Python 路径上任何地方。

默认情况下,AUTHENTICATION_BACKENDS 设置为:

['django.contrib.auth.backends.ModelBackend']

这个基本的认证后台会检查Django 的用户数据库并查询内建的权限。 它不会通过任何的速率限制机制防护暴力破解。 你可以在自定义的认证后端中实现自己的速率控制机制,或者使用大部分Web 服务器提供的机制。

AUTHENTICATION_BACKENDS 的顺序很重要,所以如果用户名和密码在多个后台中都是合法的,Django 将在第一个匹配成功后停止处理。

如果后台引发PermissionDenied 异常,认证将立即失败。 Django 不会检查后面的认证后台。

一旦用户被认证过,Django会在用户的session中存储他使用的认证后端,然后在session有效期中一直会为该用户提供此后端认证。 这种高效意味着验证源被缓存基于per-session基础, 所以如果你改变 AUTHENTICATION_BACKENDS, 如果你需要迫使用户重新认证,需要清除掉 session 数据. 一个简单的方式是使用这个方法:Session.objects.all().delete().

编写认证后端

认证后端是一个类,它实现两个必需方法:get_user(user_id)authenticate(request, **credentials),以及一组可选的与权限相关的认证方法

get_user方法使用一个user_id,可以是用户名,数据库ID等等,但必须是用户对象的主键,并返回一个用户对象。

authenticate方法需要一个request参数和一些凭据作为关键字参数。 大多数情况下,代码如下︰

class MyBackend(object):
    def authenticate(self, request, username=None, password=None):
        # Check the username/password and return a user.
        ...

当然,它也可以接收token的方式作为参数,例如:

class MyBackend(object):
    def authenticate(self, request, token=None):
        # Check the token and return a user.
        ...

无论哪种方式,authenticate()应该检查它获得的凭据,如果凭据有效,则返回与这些凭据匹配的用户对象。 如果不合法,则返回 None.

request is an HttpRequest and may be None if it wasn’t provided to authenticate() (例如密码在后端).

Django管理员与Django User object紧密耦合。 处理这种情况的最好方法是为您的后端存在的每个用户创建一个Django User对象(例如,在LDAP目录,外部SQL数据库等中) 你可以先写一个脚本来做这件事, 或者用你的 authenticate 方法在用户登陆的时候完成这件事。

这里有一个例子,后台对你定义在 settings.py 文件里的用户和密码进行验证,并且在用第一次验证的时候创建一个 User 对象:

from django.conf import settings
from django.contrib.auth.hashers import check_password
from django.contrib.auth.models import User

class SettingsBackend(object):
    """
    Authenticate against the settings ADMIN_LOGIN and ADMIN_PASSWORD.

    Use the login name and a hash of the password. 像这样:

    ADMIN_LOGIN = 'admin'
    ADMIN_PASSWORD = 'pbkdf2_sha256$30000$Vo0VlMnkR4Bk$qEvtdyZRWTcOsCnI/oQ7fVOu1XAURIZYoOZ3iq8Dr4M='
    """

    def authenticate(self, request, username=None, password=None):
        login_valid = (settings.ADMIN_LOGIN == username)
        pwd_valid = check_password(password, settings.ADMIN_PASSWORD)
        if login_valid and pwd_valid:
            try:
                user = User.objects.get(username=username)
            except User.DoesNotExist:
                # Create a new user. There's no need to set a password
                # because only the password from settings.py is checked.
                user = User(username=username)
                user.is_staff = True
                user.is_superuser = True
                user.save()
            return user
        return 没有

    def get_user(self, user_id):
        try:
            return User.objects.get(pk=user_id)
        except User.DoesNotExist:
            return 没有

在自定义后端处理授权

自定义验证后端能提供自己的权限。

当认证后端完成了这些功能 (get_group_permissions()get_all_permissions()has_perm(), and has_module_perms()) 那么user model就会给它授予相对应的许可。

提供给用户的权限将是所有后端返回的所有权限的超集。 也就是说,只要任意一个backend授予了一个user权限,django就给这个user这个权限。

如果后端在has_perm()has_module_perms()中引发PermissionDenied异常,授权将立即失败,Django不会检查接下来的后端认证。

上述的简单backend可以相当容易的完成授予admin权限。

class SettingsBackend(object):
    ...
    def has_perm(self, user_obj, perm, obj=None):
        return user_obj.username == settings.ADMIN_LOGIN

在上例中,授予了用户所有访问权限。 注意, 由于django.contrib.auth.models.User 同名函数将接收同样的参数,认证后台接收到的 user_obj,有可能是匿名用户 anonymous

一个完整的认证过程,可以参考 auth_permission类,它位于django/contrib/auth/backends.py,ModelBackend是默认的认证后台,并且大多数情况下会对ModelBackend表进行查询。 如果你想对后台API提供自定义行为,你可以利用Python继承的优势,继承ModelBackend并自定义后台API

授权匿名用户

匿名用户是指不经过身份验证即他们有没有提供有效的身份验证细节。 然而,这并不一定意味着他们不被授权做任何事情。 在最基本的层面上,大多数网站授权匿名用户浏览大部分网站,许多网站允许匿名发表评论等。

Django 的权限框架没有一个地方来存储匿名用户的权限。 然而,传递给身份验证后端的用户对象可能是django.contrib.auth.models.AnonymousUser 对象,该对象允许后端指定匿名用户自定义的授权行为。 这对可重用应用的作者是很有用的, 因为他可以委托所有的请求, 例如控制匿名用户访问,给这个认证后端, 而不需要设置它

授权非活动用户

非活动用户是将is_active字段设置为False的用户。 ModelBackendRemoteUserBackend身份验证后端禁止这些用户进行身份验证。如果自定义用户模型没有is_active字段,则所有用户都将被允许进行身份验证。

如果要让非活动用户进行身份验证,可以使用AllowAllUsersModelBackendAllowAllUsersRemoteUserBackend

对权限系统中的匿名用户的支持允许匿名用户具有执行某些操作的权限的情况,而未被认证的用户不具有。

不要忘记在自己的后端权限方法中测试用户的is_active属性。

在Django更改1.10:

在旧版本中,ModelBackend允许非活动用户进行身份验证。

处理对象权限

django的权限框架对对象权限有基础的支持, 尽管在它的核心没有实现它. 这意味着对象权限检查将始终返回 False 或空列表 (取决于检查的行为)。 一个认证后端将传递关键字参数obj 和 user_obj 给每一个对象相关的认证方法, 并且能够返回适当的对象级别的权限.

自定义权限

要为给定模型对象创建自定义权限,请使用permissions model Meta attribute

此示例任务模型创建三个自定义权限,即用户是否可以对您的应用程序任务实例执行操作:

class Task(models.Model):
    ...
    class Meta:
        permissions = (
            ("view_task", "Can see available tasks"),
            ("change_task_status", "Can change the status of tasks"),
            ("close_task", "Can remove a task by setting its status as closed"),
        )

唯一的做法是在运行manage.py migrate时创建这些额外的权限(创建权限的功能连接到post_migrate信号)。 当用户尝试访问应用程序提供的功能(查看任务,更改任务状态,关闭任务)时,您的代码负责检查这些权限的值。 继续上面的示例,以下检查用户是否可以查看任务:

user.has_perm('app.view_task')

扩展现有的User模型

有两种方法来扩展默认的User模型,而不用替换你自己的模型。 如果你需要的只是行为上的改变,而不需要对数据库中存储的内容做任何改变,你可以创建基于User 的proxy model。 代理模型提供的功能包括默认的排序、自定义管理器以及自定义模型方法。

如果您希望存储与User相关的信息,则可以使用OneToOneField到包含其他信息字段的模型。 这种 one-to-one 模型一般被称为资料模型(profile model),它通常被用来存储一些有关网站用户的非验证性( non-auth )资料。 例如,你可以创建一个员工模型 (Employee model):

from django.contrib.auth.models import User

class Employee(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    department = models.CharField(max_length=100)

假设一个员工Fred Smith 既有User 模型又有Employee 模型,你可以使用Django 标准的关联模型访问相关联的信息:

>>> u = User.objects.get(username='fsmith')
>>> freds_department = u.employee.department

要将个人资料模型的字段添加到管理后台的用户页面中,请在应用程序的UserAdmin定义一个InlineModelAdmin(对于本示例,我们将使用StackedInline )并将其添加到admin.py类并向User类注册的:

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import User

from my_user_profile_app.models import Employee

# Define an inline admin descriptor for Employee model
# which acts a bit like a singleton
class EmployeeInline(admin.StackedInline):
    model = Employee
    can_delete = False
    verbose_name_plural = 'employee'

# Define a new User admin
class UserAdmin(BaseUserAdmin):
    inlines = (EmployeeInline, )

# Re-register UserAdmin
admin.site.unregister(User)
admin.site.register(User, UserAdmin)

这些配置文件模型在任何情况下都不是特别的 - 它们只是Django模型,与用户模型发生一对一的链接。 因此,当创建用户时,它们不会自动创建,但可以使用django.db.models.signals.post_save来适当地创建或更新相关模型。

使用相关模型会产生其他查询或联接来检索相关数据。 根据您的需求,包含相关字段的自定义用户模型可能是您更好的选择,但是,与项目应用程序中的默认用户模型的现有关系可能有助于额外的数据库加载。

替换User模型

某些类型的项目可能有特殊的认证需求,Django内建的User模型不可能总是适用。 例如,在某些网站上使用邮件地址而不是用户名作为身份的标识可能更合理。

通过提供一个值给AUTH_USER_MODEL设置,指向自定义的模型,Django允许你覆盖默认的User模型:

AUTH_USER_MODEL = 'myapp.MyUser'

这个点式路径包含Django应用的名称(必须位于你的INSTALLED_APPS中),和要用作User模型的Django模型的名称。

在项目开始时使用自定义User模型

如果你正在开始一个新项目,强烈建议你设置一个自定义用户模型,即使默认的User模型对你已经足够可用。 下面的模型的行为与默认的用户模型相同,但是将来如果需要你可以自定义它:

from django.contrib.auth.models import AbstractUser

class User(AbstractUser):
    pass

不要忘记将AUTH_USER_MODEL指向它。 在创建任何迁移或首次运行manage.py migrate之前执行此操作。

另外,在应用程序的admin.py中注册该模型:

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import User

admin.site.register(User, UserAdmin)

在项目中期修改为自定义的User模型

创建数据库表之后,更改AUTH_USER_MODEL是非常困难的,因为它会影响外键和多对多关系。

此更改无法自动完成,需要手动修复模式、从旧用户表移动数据、并可能需要手动重新应用某些迁移。 有关步骤的概述,请参见#25313

由于Django对于可交换模型的动态依赖特性的限制,必须在其应用的第一次迁移(通常称为0001_initial)中创建AUTH_USER_MODEL引用的模型;否则你会有依赖问题。

另外,当运行迁移时,你可能遇到一个CircularDependencyError,因为Django将无法自动中断由于动态依赖关系的依赖关系循环。如果看到此错误,应该通过将你的用户模型所依赖的模型移动到第二次迁移中来打破循环。 (You can try making two normal models that have a ForeignKey to each other and seeing how makemigrations resolves that circular dependency if you want to see how it’s usually done.)

可重用的应用和AUTH_USER_MODEL

可重用的应用不应实现自定义用户模型。 一个项目可能使用多个应用,实现自定义用户模型的两个可重用应用不能一起使用。 如果需要在应用中存储用户的信息,请使用ForeignKeyOneToOneFieldsettings.AUTH_USER_MODEL,如下所述。

引用User模型

如果直接引用User(例如,通过外键引用),在AUTH_USER_MODEL设置已更改为不同用户模型的项目中,代码将不能工作。

get_user_model()[source]

你应该使用django.contrib.auth.get_user_model() 来引用用户模型,而不要直接引用User。 此方法将返回当前活动的用户模型 — 如果指定了自定义用户模型,否则返回User

在定义到用户模型的外键或多对多关系时,应使用AUTH_USER_MODEL设置指定自定义模型。 像这样:

from django.conf import settings
from django.db import models

class Article(models.Model):
    author = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
    )

当连接到用户模型发送的信号时,应该使用AUTH_USER_MODEL设置指定自定义模型。 像这样:

from django.conf import settings
from django.db.models.signals import post_save

def post_save_receiver(sender, instance, created, **kwargs):
    pass

post_save.connect(post_save_receiver, sender=settings.AUTH_USER_MODEL)

一般来说,在导入时执行的代码中,使用AUTH_USER_MODEL设置来引用用户模型是最简单的,但Django也可以调用get_user_model()导入模型,所以你可以使用models.ForeignKey(get_user_model(), ...)

如果你的应用使用多个用户模型进行测试,例如使用@override_settings(AUTH_USER_MODEL=...),并将get_user_model()的结果缓存在模块级别变量中,你可能需要监听setting_changed信号以清除缓存。 像这样:

from django.apps import apps
from django.contrib.auth import get_user_model
from django.core.signals import setting_changed
from django.dispatch import receiver

@receiver(setting_changed)
def user_model_swapped(**kwargs):
    if kwargs['setting'] == 'AUTH_USER_MODEL':
        apps.clear_cache()
        from myapp import some_module
        some_module.UserModel = get_user_model()

4.API参考

User模型

字段

class models.User
User 对象具有如下字段:
username     

         必选。 150个字符以内。 用户名可能包含字母数字,_@+ . -个字符。

         对于许多用例,max_length应该是足够的。 如果您需要较长的长度,请使用custom user model。 如果您使用具有utf8mb4编码(推荐用于正确的Unicode支持)的MySQL,请           至少指定max_length=191,因为MySQL只能创建具有191个字符的唯一索引,默认。

first_name

可选(blank=True)。 少于等于30个字符。

last_name

可选(blank=True)。 少于等于30个字符。

email

可选(blank=True)。 邮箱地址。

password

必选。 密码的哈希及元数据。 (Django 不保存原始密码)。 原始密码可以无限长而且可以包含任意字符。 参见password documentation

groups

Group 之间的多对多关系。

user_permissions

Permission 之间的多对多关系。

is_staff

布尔值。 指示用户是否可以访问Admin 站点。

is_active

布尔值。 指示用户的账号是否激活。 我们建议您将此标志设置为False而不是删除帐户;这样,如果您的应用程序对用户有任何外键,则外键不会中断。

它不是用来控制用户是否能够登录。 不需要验证后端来检查is_active标志,而是默认后端(ModelBackend)和RemoteUserBackend。 如果要允许非活动用户登录,您可以使用AllowAllUsersModelBackendAllowAllUsersRemoteUserBackend。 在这种情况下,您还需要自定义LoginView使用的AuthenticationForm,因为它拒绝了非活动用户。 请注意,诸如has_perm()等权限检查方法,Django管理员中的身份验证全部返回为非活动用户的False

在Django更改1.10:

在旧版本中,ModelBackendRemoteUserBackend允许非活动用户进行身份验证。

is_superuser

布尔值。 指定这个用户拥有所有的权限而不需要给他们分配明确的权限。

last_login

用户最后一次登录的时间。

date_joined

账户创建的时间。 当账号创建时,默认设置为当前的date/time。

属性

class models.User
is_authenticated

始终为True(与AnonymousUser.is_authenticated相对,始终为False)的只读属性。 这是区分用户是否已经认证的一种方法。这并不表示任何权限,也不会检查用户是否处于活动状态或是否具有有效的会话。 即使正常情况下,您将在request.user上检查此属性,以了解它是否已由AuthenticationMiddleware填充(表示当前登录的用户),您应该知道对于任何User实例,此属性为True

is_anonymous

始终为False的只读属性。 这是区别User 和AnonymousUser 对象的一种方法。 一般来说,您应该优先使用is_authenticated到此属性。

在Django更改1.10:

在旧版本中,这是一种方法。 使用它作为方法的向后兼容性支持将在Django 2.0中被删除。

username_validator
Django中的新功能1.10。

指向用于验证用户名的验证器实例。 Python 3上的默认值为validators.UnicodeUsernameValidator和Python 3上的validators.ASCIIUsernameValidator

要更改默认用户名验证器,可以将User模型子类化,并将此属性设置为不同的验证器实例。 例如,要在Python 3上使用ASCII用户名:

from django.contrib.auth.models import 用户
from django.contrib.auth.validators import ASCIIUsernameValidator

class CustomUser(User):
    username_validator = ASCIIUsernameValidator()

    class Meta:
        proxy = True  # If no new field is added.

方法

class models.User
get_username()

返回这个User 的username。 由于可以将User模型交换出来,您应该使用此方法,而不是直接引用用户名属性。

get_full_name()

返回first_name 和last_name,之间带有一个空格。

get_short_name()

返回first_name

set_password(raw_password)

设置用户的密码为给定的原始字符串,并负责密码的哈希。 不会保存User 对象。

None 为raw_password 时,密码将设置为一个不可用的密码,和使用set_unusable_password() 的效果一样。

check_password(raw_password)

Returns True if the given raw string is the correct password for the user. (它负责在比较时密码的哈希)。

set_unusable_password()

标记用户为没有设置密码。 它与密码为空的字符串不一样。 check_password() 对这种用户永远不会返回True。 不会保存User对象。

如果你的认证发生在外部例如LDAP 目录时,可能需要这个函数。

has_usable_password()

如果对这个用户调用过set_unusable_password(),则返回False

get_group_permissions(obj=None)

返回一个用户当前拥有的权限的set,通过用户组

如果传入obj,则仅返回此特定对象的组权限。http://python.usyiyi.cn/translate/django_182/ref/contrib/auth.html#

get_all_permissions(obj=None)

通过组和用户权限返回用户拥有的一组权限字符串。

如果传入obj,则仅返回此特定对象的权限。

has_perm(permobj=None)

如果用户具有指定的权限,则返回True,其中perm的格式为"<app label>.<permission codename>"。 (请参阅有关permissions)。 如果用户没有激活,这个方法将永远返回 False

如果传入obj,此方法将不会检查模型的权限,而是检查此特定对象。

has_perms(perm_listobj=None)

Returns True if the user has each of the specified permissions, where each perm is in the format"<app label>.<permission codename>"如果用户没有激活,这个方法将永远返回 False

如果传入obj,此方法将不会检查模型的权限,而是检查特定对象。

has_module_perms(package_name)

如果用户具有给出的package_name(Django应用的标签)中的任何一个权限,则返回True。 如果用户没有激活,这个方法将永远返回False

email_user(subjectmessagefrom_email=None**kwargs)

发生邮件给这个用户。 如果None 为from_email,Django 将使用DEFAULT_FROM_EMAIL。 任何**kwargs 都将传递给底层的send_mail() 调用。

管理器方法

class models.UserManager

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

create_user(usernameemail=Nonepassword=None**extra_fields)

创建、保存并返回一个User

username 和password 设置为给出的值。 email 的域名部分将自动转换成小写,返回的User 对象将设置is_active 为True

如果没有提供password,将调用 set_unusable_password()

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

参见Creating users 中的示例用法。

create_superuser(usernameemailpassword**extra_fields)

create_user() 相同,但是设置is_staff 和is_superuser 为True

AnonymousUser对象

class models.AnonymousUser

django.contrib.auth.models.AnonymousUser 类实现了django.contrib.auth.models.User 接口,但具有下面几个不同点:

在实际应用中,你自己可能不需要使用AnonymousUser 对象,它们用于Web 请求,在下节会讲述。

Permission模型

class models.Permission

字段

Permission 对象有以下字段:

class models.Permission
name

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

content_type

必选。 django_content_type数据库表的引用,其中包含每个已安装模型的记录。

codename

必选。 小于等于100个字符. 例如: 'can_vote'.

方法

Permission对象具有类似任何其他Django model的标准数据访问方法。

Group模型

class models.Group

字段

Group 对象有以下字段:

class models.Group
name

必选。 80个字符以内。 允许任何字符. 例如: 'Awesome Users'.

permissions

多对多字段到Permission

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

验证

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

仅允许使用ASCII字母和数字的字段验证器,除@之外, .+-_。 Python 2上的User.username的默认验证器。

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

允许Unicode字符的字段验证器,除@之外, .+-_。 Python 3上的User.username的默认验证器。

登入和登出信号

auth框架使用以下signals,可用于在用户登录或注销时通知。

user_logged_in()

当用户成功登录时发送。

与此信号一起发送的参数:

sender
刚刚登录的用户的类。
request
当前的HttpRequest实例。
user
刚刚登录的用户实例。
user_logged_out()

在调用logout方法时发送。

sender
如上所述:刚刚注销的用户的类或None,如果用户未通过身份验证。
request
当前的HttpRequest实例。
user
如果用户未通过身份验证,刚刚注销的用户实例或None
user_login_failed()

当用户登录失败时发送

sender
用于认证的模块的名称。
credentials
包含传递给authenticate()或您自己的自定义身份验证后端的用户凭据的关键字参数的字典。 匹配一组“敏感”模式(包括密码)的凭证不会作为信号的一部分发送到清除中。
request
HttpRequest对象,如果提供给authenticate()
在Django更改1.11:

添加了request参数。

认证后端

这一节详细讲述Django自带的认证后端。 关于如何使用它们以及如何编写你自己的认证后端,参见用户认证指南中的其它认证源一节

可用的认证后端

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

class ModelBackend

这是Django使用的默认认证后台。 它使用由用户标识和密码组成的凭据进行认证。 对于Django的默认用户模型,用户的标识是用户名,对于自定义的用户模型,它通过USERNAME_FIELD 字段表示(参见Customizing Users and authentication)。

它还处理 User 和PermissionsMixin 定义的权限模型。

has_perm()get_all_permissions()get_user_permissions(), 和get_group_permissions() 允许一个对象作为特定权限参数来传递, 如果条件是 if obj is not None. 后端除了返回一个空的permissions 外,并不会去完成他们。

authenticate(requestusername=Nonepassword=None**kwargs)

通过调用User.check_password 验证password 和username。 如果kwargs 没有提供,它会使用CustomUser.USERNAME_FIELD 关键字从username 中获取username。 返回一个认证过的User 或None

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

在Django更改1.11:

添加了request参数。

get_user_permissions(user_objobj=None)

返回user_obj具有的自己用户权限的权限字符串集合。 如果is_anonymousis_activeFalse,则返回空集。

get_group_permissions(user_objobj=None)

返回user_obj从其所属组的权限中获取的权限字符集。 如果is_anonymousis_activeFalse,则返回空集。

get_all_permissions(user_objobj=None)

返回user_obj的权限字符串集,包括用户权限和组权限。 如果is_anonymousis_activeFalse,则返回空集。

has_perm(user_objpermobj=None)

使用get_all_permissions()检查user_obj是否具有权限字符串perm。 如果用户不是is_active,则返回False

has_module_perms(user_objapp_label)

返回user_obj是否对应用app_label有任何权限。

user_can_authenticate()
Django中的新功能1.10。

返回是否允许用户进行身份验证。 To match the behavior of AuthenticationForm which prohibits inactive users from logging in, this method returns False for users with is_active=False不允许使用is_active字段的自定义用户模型。

class AllowAllUsersModelBackend
Django 1.10中新增。

ModelBackend相同,但是不会拒绝非激活的用户,因为user_can_authenticate()始终返回True

使用此后端时,你可能会需要覆盖confirm_login_allowed()方法来自定义LoginView使用的AuthenticationForm,因为它拒绝了非激活的用户。

class RemoteUserBackend

使用这个后端来处理Django的外部认证。 它使用在request.META['REMOTE_USER']中传递的用户名进行身份验证。 请参阅REMOTE_USER的认证文档。

如果你需要更多的控制,你可以创建你自己的验证后端,继承这个类,并重写这些属性或方法:

RemoteUserBackend.create_unknown_user

TrueFalse。 确定是否创建用户对象(如果尚未在数据库中)默认为True

RemoteUserBackend.authenticate(requestremote_user)

作为remote_user传递的用户名被认为是可信的。 此方法只需返回具有给定用户名的用户对象,如果create_unknown_userTrue则创建新的用户对象。

如果create_unknown_userUser,并且在数据库中找不到具有给定用户名的None对象,则返回False

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

RemoteUserBackend.clean_username(username)

在使用它获取或创建用户对象之前,请对username执行任何清除(例如剥离LDAP DN信息)。 返回已清除的用户名。

RemoteUserBackend.configure_user(user)

配置新创建的用户。 此方法在创建新用户后立即调用,并可用于执行自定义设置操作,例如根据LDAP目录中的属性设置用户的组。 返回用户对象。

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

返回是否允许用户进行身份验证。 对于is_active=False的用户,此方法返回False。 不允许使用is_active字段的自定义用户模型。

class AllowAllUsersRemoteUserBackend
Django 1.10中新增。

ModelBackend相同,但是不会拒绝非激活的用户,因为user_can_authenticate()始终返回True

实用功能

get_user(request)[source]

返回与给定的request会话关联的用户模型实例。

它检查存储在会话中的身份验证后端是否存在于AUTHENTICATION_BACKENDS中。 如果是这样,它使用后端的get_user()方法来检索用户模型实例,然后通过调用用户模型的get_session_auth_hash()方法验证会话。

如果存储在会话中的身份验证后端不再在AUTHENTICATION_BACKENDS中返回AnonymousUser的实例,如果后端的get_user()

管理后台

 管理后台站点

 Django最强大的部分之一是自动生成的管理后台界面。 它从你的模型中读取元数据,以提供一个快速的、以模型为中心的界面,信任的用户可以在这里管理你网站上的内容。 建议管理后台仅作为组织的一个内部管理工具使用。 它不是为了建立你的整个前端。

管理站点有许多hook用于定制,但要注意试图专门使用这些hook。 如果你需要提供一个更加以流程为中心的界面,它抽象出数据库表和字段的实现细节,那么可能需要编写自己的视图。

在本文中,我们将讨论如何激活、使用和定制Django的管理后台界面。

概述

通过使用startproject创建的默认项目模版中,管理后台已启用。

下面的一些要求作为参考:

  1. 添加 'django.contrib.admin'INSTALLED_APPS 设置中.
  2. admin有四个依赖 — django.contrib.authdjango.contrib.contenttypesdjango.contrib.messagesdjango.contrib.sessions。 如果这些应用没有在INSTALLED_APPS列表中, 那你要添加它们。
  3. 添加django.contrib.auth.context_processors.authdjango.contrib.messages.context_processors.messagesTEMPLATES中定义的DjangoTemplates后端的'context_processors'选项中,并添加django.contrib.auth.middleware.AuthenticationMiddlewaredjango.contrib.messages.middleware.MessageMiddlewareMIDDLEWARE中。 默认情况下它们都已经添加,除非你手动调整过设置,否则不需要自己添加。
  4. 确定你的应用中的哪些模型在管理后台界面中应该可以编辑。
  5. 给上面的每个模型创建一个ModelAdmin类,封装模型自定义的管理后台功能和选项。
  6. 实例化AdminSite并且告诉它你的每一个模型和ModelAdmin类。
  7. AdminSite实例hook到URLconf。

做完这些步骤之后,通过访问你hook进的URL(默认是/admin/),将能够使用你的Django管理后台站点。 如果你需要创建一个登录用户,可以使用createsuperuser命令。

1.ModelAdmin对象

class ModelAdmin[source]

ModelAdmin类是模型在管理后台界面中的表示形式。 通常,它们保存在你的应用中的名为admin.py的文件里。 让我们来看一个关于ModelAdmin类非常简单的例子:

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

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

register装饰器

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

还可以用一个装饰器来注册您的ModelAdmin类(这里有关装饰器的详细信息,请参考python中的相关说明)

from django.contrib import admin
from .models import Author

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

如果你使用的不是默认的AdminSite,那么这个装饰器可以接收一些ModelAdmin作为参数,以及一个可选的关键字参数 site :(这里使用装饰器来注册需要注册的类和模块的,请特别留意紧跟装饰器后面关于ModelAdmin的声明,前面是Author,后面是PersonAdmin,我的理解是后一种情况 下注册的类都可以用PersonAdmin来作为接口):

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

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

在python2中,如果您在类的__init__())方法中引用了模型的admin类,则不能使用此装饰器,例如,super(PersonAdmin, self).__ init __(* args, ** kwargs)。 但是,在Python3中,通过使用super().__ init __(* args, ** kwargs) t2 >可以避免这个问题; 在python2中,你必须使用admin.site.register()而不能使用装饰器方式。

发现admin文件

当你将 'django.contrib.admin'加入到INSTALLED_APPS 设置中, Django就会自动搜索每个应用的admin模块并将其导入。

class apps.AdminConfig

这是 admin的默认AppConfig 类. 它在 Django 启动时调用autodiscover() .

class apps.SimpleAdminConfig

这个类和 AdminConfig的作用一样,除了它不调用autodiscover().

autodiscover()[source]

这个函数尝试导入每个安装的应用中的admin 模块。 这些模块用于注册模型到Admin 中。

通常,当Django启动时,您将不需要直接调用此函数作为AdminConfig调用该函数。

如果您正在使用自定义 AdminSite,则通常会将所有ModelAdmin子类导入到代码中,并将其注册到自定义AdminSite。 在这种情况下, 为了禁用auto-discovery,在你的INSTALLED_APPS 设置中,应该用 'django.contrib.admin'代替'django.contrib.admin.apps.SimpleAdminConfig' 。

ModelAdmin的选项

ModelAdmin 非常灵活。 它有几个选项来处理自定义界面。 所有的选项都在 ModelAdmin 子类中定义:

from django.contrib import admin

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

在修改列表页面可用的操作列表。 详细信息请查看Admin actions .

ModelAdmin.actions_on_top
ModelAdmin.actions_on_bottom

控制actions的下拉框出现在页面的位置。 默认情况下,管理员更改列表显示页面顶部的操作(actions_on_top True; actions_on_bottom t4 > False)。

ModelAdmin.actions_selection_counter

控制选择计数器是否紧挨着下拉菜单action 默认的admin 更改列表将会显示它 (actions_selection_counter True).

ModelAdmin.date_hierarchy

把 date_hierarchy 设置为在你的model 中的DateField或DateTimeField的字段名,然后更改列表页面将包含这个字段基于日期的下拉导航。

例如:

date_hierarchy = 'pub_date'

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

date_hierarchy = 'author__pub_date'

这将根据现有数据智能地填充自己,例如,如果所有的数据都是一个月里的, 它将只显示天级别的数据.

ModelAdmin.empty_value_display

此属性将覆盖空的字段(None,空字符串等)的默认显示值。 默认值为-(破折号)。 像这样:

from django.contrib import admin

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

您还可以覆盖empty_value_display的所有管理页面的AdminSite.empty_value_display,或者对于特定字段,例如:

from django.contrib import admin

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

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

    view_birth_date.empty_value_display = '???'

ModelAdmin.exclude

如果设置了这个属性,它表示应该从表单中去掉的字段列表。

例如,让我们来考虑下面的模型:

from django.db import models

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

如果你希望title 模型的表单只包含name 和Author 字段, 你应该显式说明fields 或exclude,像这样:

from django.contrib import admin

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

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

由于Author 模型只有三个字段,birth_datetitle和 name,上述声明产生的表单将包含完全相同的字段。

ModelAdmin.fields

使用fields选项可以在“添加”和“更改”页面上的表单中进行简单的布局更改,例如仅显示可用字段的一个子集,修改其顺序或将其分组为行。 例如,可以定义一个简单的管理表单的版本使用django.contrib.flatpages.models.FlatPage 模块像下面这样:

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

在上面的例子中, 只有字段contenttitle 和 url 将会在表单中顺序的显示. fields能够包含在 ModelAdmin.readonly_fields 中定义的作为只读显示的值

对于更复杂的布局需求,请参阅fieldsets选项。

不同于 list_displayfields 选项 只包含model中的字段名或者通过form指定的表单。 只有当它们列在readonly_fields中,它才能包含callables

要在同一行显示多个字段, 就把那些字段打包在一个元组里。 在此示例中,urltitle字段将显示在同一行上,content字段将在其自己的行下显示:

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

如果editable=Truefieldsets 选项都不存在, Django将会默认显示每一个不是 fields 并且 AutoField的字段, 在单一的字段集,和在模块中定义的字段有相同的顺序

ModelAdmin.fieldsets

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

fieldsets 是一个以二元元组为元素的列表, 每一个二元元组代表一个在管理表单的 <fieldset> <fieldset> 是表单的一部分.)

二元元组的格式是 (name, field_options), 其中 name 是一个字符串相当于 fieldset的标题, field_options 是一个关于 fieldset的字典信息,一个字段列表包含在里面。

一个完整的例子, 来自于django.contrib.flatpages.models.FlatPage 模块:

from django.contrib import admin

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

在管理界面的结果看起来像这样:

如果editable=Truefields 选项都不存在, Django将会默认显示每一个不是 fieldsets 并且 AutoField的字段, 在单一的字段集,和在模块中定义的字段有相同的顺序。

field_options 字典有以下关键字:

  • fields

    字段名元组将显示在该fieldset. 此键必选.

    例如:

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

就像fields 选项, 显示多个字段在同一行, 包裹这些字段在一个元组. 在这个例子中, first_name 和 last_name 字段将显示在同一行:

{
'fields': (('first_name', 'last_name'), 'address', 'city', 'state'),
}
  • fields 能够包含定义在readonly_fields 中显示的值作为只读.

    如果添加可调用的名称到fields中,相同的规则适用于fields选项: 可调用的必须在 readonly_fields列表中.

  • classes

    包含要应用于字段集的额外CSS类的列表或元组。

    例如:

{
'classes': ('wide', 'extrapretty'),
}
  • 通过默认的管理站点样式表定义的两个有用的classes 是 collapse 和 wideFieldsets 使用 collapse 样式将会在初始化时展开并且替换掉一个 “click to expand” 链接. Fieldsets 使用 wide 样式将会有额外的水平空格.

  • description

    一个可选择额外文本的字符串显示在每一个fieldset的顶部,在fieldset头部的底下. 字符串没有被TabularInline 渲染由于它的布局.

    记住这个值不是 HTML-escaped 当它显示在管理接口中时. 如果你愿意,这允许你包括HTML。 另外,你可以使用纯文本和 django.utils.html.escape() 避免任何HTML特殊字符。

ModelAdmin.filter_horizontal

默认的, ManyToManyField 会在管理站点上显示一个<select multiple>.(多选框). 但是,当选择多个时多选框非常难用. 添加一个 ManyToManyField到该列表将使用一个漂亮的低调的JavaScript中的“过滤器”界面,允许搜索选项。 选和不选选项框并排出现。 参考filter_vertical 使用垂直界面。

ModelAdmin.filter_vertical

filter_horizontal相同,但使用过滤器界面的垂直显示,其中出现在所选选项框上方的未选定选项框。

ModelAdmin.form

默认情况下, 会根据你的模型动态创建一个ModelForm。 它被用来创建呈现在添加/更改页面上的表单。 你可以很容易的提供自己的ModelForm 来重写表单默认的添加/修改行为。 或者,你可以使用ModelAdmin.get_form() 方法自定义默认的表单,而不用指定一个全新的表单。

 ModelAdmin.formfield_overrides

这个属性通过一种临时的方案来覆盖现有的模型中Field (字段)类型在admin site中的显示类型。 formfield_overrides 在类初始化的时候通过一个字典类型的变量来对应模型字段类型与实际重载类型的关系。

因为概念有点抽象,所以让我们来举一个具体的例子。 formfield_overrides 常被用于让一个已有的字段显示为自定义控件。 所以,试想一下我们写了一个 RichTextEditorWidget 然后我们想用它来代替<textarea>用于输入大段文字。 下面就是我们如何做到这样的替换。

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

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

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

注意字典的键是一个实际的字段类型,而不是一个具体的字符。 该值是另一个字典;这些参数将被传递给表单域的__init__()方法。

ModelAdmin.inlines

请参见下面的InlineModelAdmin对象以及ModelAdmin.get_formsets_with_inlines()

ModelAdmin.list_display

使用list_display 去控制哪些字段会显示在Admin 的修改列表页面中。

例如:

list_display = ('first_name', 'last_name')

如果你没有设置__unicode__(),Admin 站点将只显示一列表示每个对象的__str__() (Python 2 中是list_display)。

list_display中,你有4种赋值方式可以使用:

  • 模型的字段。 像这样: 

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

一个接受对象实例作为参数的可调用对象。 像这样:

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

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

一个表示ModelAdmin 中某个属性的字符串。 行为与可调用对象相同。 像这样:

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

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

表示模型中某个属性的字符串。 它的行为与可调用对象几乎相同,但这时的self 是模型实例。 这里是一个完整的模型示例︰

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

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

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

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

关于list_display 要注意的几个特殊情况︰

  • 如果字段是一个__unicode__(),Django 将展示相关对象的__str__() (Python 2 上是ForeignKey)。

  • 不支持ManyToManyField 字段, 因为这将意味着对表中的每一行执行单独的SQL 语句。 如果尽管如此你仍然想要这样做,请给你的模型一个自定义的方法,并将该方法名称添加到 list_display。 list_display 的更多自定义方法请参见下文)。

  • 如果该字段为True 或NullBooleanField,Django 会显示漂亮的"on"或"off"图标而不是BooleanField 或False

  • 如果给出的字符串是模型、ModelAdmin 的一个方法或可调用对象,Django 将默认转义HTML输出。 要转义用户输入并允许自己的未转义标签,请使用format_html()

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

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

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

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

class PersonAdmin(admin.ModelAdmin):
    list_display = ('first_name', 'last_name', 'colored_name')
  • 自1.9版以来已弃用 在旧版本中,您可以在方法中添加一个allow_tags属性来防止自动转义。 因为使用format_html()format_html_join()mark_safe()更安全,因此此属性已被弃用。

  • 正如一些例子已经证明,当使用可调用,模型方法或ModelAdmin方法时,您可以通过向可调用添加short_description属性来自定义列的标题。

  • 如果一个字段的值是None,一个空字符串,或没有元素的iterable,Django将显示-(破折号)。 您可以使用AdminSite.empty_value_display重写此项:

from django.contrib import admin

admin.site.empty_value_display = '(None)'

您也可以使用ModelAdmin.empty_value_display

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

或在现场一级:

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

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

    birth_date_view.empty_value_display = 'unknown'

如果给出的字符串是模型、True 的一个方法或一个返回 True 或False 的可调用的方法,然后赋值给方法的boolean 属性一个ModelAdmin值, Django 将显示漂亮的"on"或"off"图标,。

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

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

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

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

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

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

list_display = ('__str__', 'some_other_field')

通常情况下,list_display 的元素如果不是实际的数据库字段不能用于排序(因为 Django 所有的排序都在数据库级别)。

然而,如果list_display 元素表示数据库的一个特定字段,你可以通过设置 元素的admin_order_field 属性表示这一事实。

像这样:

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

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

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

    colored_first_name.admin_order_field = 'first_name'

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

上面的示例告诉Django 在Admin 中按照按first_name 排序时依据colored_first_name 字段。

要表示按照admin_order_field 降序排序,你可以在该字段名称前面使用一个连字符前缀。 使用上面的示例,这会看起来像︰

colored_first_name.admin_order_field = '-first_name'

admin_order_field支持查询查询,以按相关模型的值进行排序。 此示例包括列表显示中的“作者名字”列,并允许以名字排序:

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

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

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

    author_first_name.admin_order_field = 'author__first_name'

list_display 的元素也可以是属性。 不过请注意,由于方式属性在Python 中的工作方式,在属性上设置property() 只能使用 short_description 函数, 能使用@property 装饰器。

像这样:

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

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

    full_name = property(my_property)

class PersonAdmin(admin.ModelAdmin):
    list_display = ('full_name',)
  • <th> 中的字段名称还将作为HTML 输出的CSS 类, 形式为每个column-<field_name> 元素上具有list_display。 例如这可以用于在CSS 文件中设置列的宽度。

  • Django 会尝试以下面的顺序解释list_display 的每个元素︰

    • 模型的字段。
    • 可调用对象。
    • 表示ModelAdmin 属性的字符串。
    • 表示模型属性的字符串。

    例如,如果first_name 既是模型的一个字段又是ModelAdmin 的一个属性,使用的将是模型字段。

ModelAdmin.list_display_links

使用list_display_links可以控制list_display中的字段是否应该链接到对象的“更改”页面。

默认情况下,更改列表页将链接第一列 - list_display中指定的第一个字段 - 到每个项目的更改页面。 但是list_display_links可让您更改此设置:

  • 将其设置为None,根本不会获得任何链接。

  • 将其设置为要将其列转换为链接的字段列表或元组(格式与list_display相同)。

    您可以指定一个或多个字段。 只要这些字段出现在list_display中,Django不会关心多少(或多少)字段被链接。 唯一的要求是,如果要以这种方式使用list_display_links,则必须定义list_display

在此示例中,first_namelast_name字段将链接到更改列表页面上:

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

在此示例中,更改列表页面网格将没有链接:

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

ModelAdmin.list_editable

list_editable设置为模型上的字段名称列表,这将允许在更改列表页面上进行编辑。 也就是说,list_editable中列出的字段将在更改列表页面上显示为表单小部件,允许用户一次编辑和保存多行。

ModelAdmin.list_filter

list_filter 设置激活激活Admin 修改列表页面右侧栏中的过滤器,如下面的屏幕快照所示︰

list_filter 应该是一个列表或元组,其每个元素应该是下面类型中的一种:

  • 字段名称,其指定的字段应该是ManyToManyFieldIntegerFieldForeignKeyDateFieldCharFieldBooleanFieldDateTimeField,例如︰

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

 list_filter 中的字段名称也可以使用__ 查找跨关联关系,例如︰

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

一个继承自django.contrib.admin.SimpleListFilter 的类,你需要给它提供title 和 parameter_name 属性并重写lookupsqueryset 方法,例如︰

 

from datetime import date

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

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

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

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

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

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

一个元组,第一个元素是字段名称,第二个元素是从继承自django.contrib.admin.FieldListFilter 的一个类,例如︰

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

您可以使用RelatedOnlyFieldListFilter将相关模型的选择限制在该关系中涉及的对象中:

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

假设author 是User 模型的一个ForeignKey,这将限制list_filter 的选项为编写过书籍的用户,而不是所有用户。

 列表过滤器通常仅在过滤器有多个选择时才会出现。 过滤器的has_output()方法控制是否显示。

也可以指定自定义模板用于渲染列表筛选器︰

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

设置list_max_show_all以控制在“显示所有”管理更改列表页面上可以显示的项目数。 只有当总结果计数小于或等于此设置时,管理员才会在更改列表上显示“显示全部”链接。 默认情况下,设置为200

ModelAdmin.list_per_page

list_per_page 设置控制Admin 修改列表页面每页中显示多少项。 默认设置为100

设置list_select_related以告诉Django在检索管理更改列表页面上的对象列表时使用select_related()。 这可以节省大量的数据库查询。

该值应该是布尔值,列表或元组。 默认值为False

当值为True时,将始终调用select_related()。 当值设置为False时,如果存在任何ForeignKey,Django将查看list_display并调用select_related() 。

如果您需要更细粒度的控制,请使用元组(或列表)作为list_select_related的值。 空元组将阻止Django调用select_related。 任何其他元组将直接传递到select_related作为参数。 像这样:

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

将会调用select_related('author', 'category').

如果需要根据请求指定动态值,则可以实现get_list_select_related()方法。

ModelAdmin.ordering

设置ordering以指定如何在Django管理视图中对对象列表进行排序。 这应该是与模型的ordering参数格式相同的列表或元组。

如果没有提供,Django管理员将使用模型的默认排序。

如果您需要指定动态顺序(例如,根据用户或语言),您可以实施get_ordering()方法。

ModelAdmin.paginator

paginator类用于分页。 默认情况下,使用django.core.paginator.Paginator。 如果自定义paginator类没有与django.core.paginator.Paginator相同的构造函数接口,则还需要为ModelAdmin.get_paginator() 。

ModelAdmin.prepopulated_fields

prepopulated_fields设置为将字段名称映射到其应预先填充的字段的字典:

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

设置时,给定字段将使用一些JavaScript来从分配的字段填充。 此功能的主要用途是自动从一个或多个其他字段生成SlugField字段的值。 生成的值是通过连接源字段的值,然后将该结果转换为有效的字节(例如用空格替换破折号)来生成的。

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

ModelAdmin. preserve_filters T0> 

管理员现在在创建,编辑或删除对象后保留列表视图中的过滤器。 您可以将此属性设置为False,以恢复之前清除过滤器的行为。

ModelAdmin.radio_fields

默认情况下,Django的管理员为ForeignKey或者有choices集合的字段使用一个下拉菜单(<select>). 如果radio_fields中存在字段,Django将使用单选按钮接口。 假设groupPerson模型上的 ForeignKey

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

您可以选择使用django.contrib.admin模块中的VERTICALHORIZONTAL

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

ModelAdmin.raw_id_fields

默认情况下,Django的管理员为ForeignKey的字段使用一个下拉菜单(<select>). 有时候你不想在下拉菜单中显示所有相关实例产生的开销。

raw_id_fields是一个字段列表,你希望将ForeignKey 或ManyToManyField 转换成Input 窗口部件:

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

如果该字段是一个ForeignKeyInput raw_id_fields Widget 应该包含一个外键,或者如果字段是一个ManyToManyField 则应该是一个逗号分隔的值的列表。 raw_id_fields Widget 在字段旁边显示一个放大镜按钮,允许用户搜索并选择一个值︰

ModelAdmin.readonly_fields

默认情况下,管理后台将所有字段显示为可编辑。 此选项中的任何字段(应为listtuple)将按原样显示其数据,不可编辑;它们也被排除在用于创建和编辑的ModelForm之外。 请注意,指定ModelAdmin.fieldsModelAdmin.fieldsets时,只读字段必须包含进去才能显示(否则将被忽略)。

如果在未通过ModelAdmin.fieldsModelAdmin.fieldsets定义显式排序的情况下使用readonly_fields,则它们将在所有可编辑字段之后添加。

只读字段不仅可以显示模型字段中的数据,还可以显示模型方法的输出或ModelAdmin类本身的方法。 这与ModelAdmin.list_display的行为非常相似。 这提供了一种使用管理界面提供对正在编辑的对象的状态的反馈的简单方法,例如:

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

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

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

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

设置save_as以在管理员更改表单上启用“另存为”功能。

通常,对象有三个保存选项:“保存”,“保存并继续编辑”和“保存并添加其他”。 如果save_asTrue,“保存并添加另一个”将被替换为创建新对象(使用新ID)而不是更新的“另存为”按钮现有的对象。

默认情况下,save_as 设置为False

ModelAdmin.save_as_continue
Django中的新功能1.10。

save_as=True时,保存新对象后的默认重定向是该对象的更改视图。 如果设置save_as_continue=False,则重定向将是更改列表视图。

默认情况下,save_as_continue设置为True

ModelAdmin.save_on_top

设置save_on_top可在表单顶部添加保存按钮。

通常,保存按钮仅出现在表单的底部。 如果您设置save_on_top,则按钮将同时显示在顶部和底部。

默认情况下,save_on_top设置为False

ModelAdmin.search_fields

search_fields 设置启用Admin 更改列表页面上的搜索框。 此属性应设置为每当有人在该文本框中提交搜索查询将搜索的字段名称的列表。

这些字段应该是某种文本字段,如CharField 或TextField。 你还可以通过查询API 的"跟随"符号进行ForeignKeyManyToManyField 上的关联查找:

search_fields = ['foreign_key__related_fieldname']

例如,如果您有一个作者的博客条目,以下定义将允许通过作者的电子邮件地址搜索博客条目

search_fields = ['user__email']

如果有人在Admin 搜索框中进行搜索,Django 拆分搜索查询为单词并返回包含每个单词的所有对象,不区分大小写,其中每个单词必须在至少一个search_fields。 例如,如果search_fields 设置为['first_name', 'last_name'],用户搜索john lennon,Django 的行为将相当于下面的这个WHERE SQL 子句︰

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

若要更快和/或更严格的搜索,请在字典名称前面加上前缀︰

使用'^'运算符来匹配从字段开始的起始位置。 例如,如果search_fields 设置为['^first_name', '^last_name'],用户搜索john lennon 时,Django 的行为将等同于下面这个WHERE SQL 字句:

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

此查询比正常'%john%' 查询效率高,因为数据库只需要检查某一列数据的开始,而不用寻找整列数据。 另外,如果列上有索引,有些数据库可能能够对于此查询使用索引,即使它是LIKE 查询。

=

使用'='运算符不区分大小写的精确匹配。 例如,如果search_fields 设置为['=first_name', '=last_name'],用户搜索john lennon 时,Django 的行为将等同于下面这个WHERE SQL 字句:

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

注意,该查询输入通过空格分隔,所以根据这个示例,目前不能够搜索first_name 精确匹配'john winston'(包含空格)的所有记录。

@
使用'@'运算符执行全文匹配。 这就像默认的搜索方法,但使用索引。 目前这只适用于MySQL。

如果你需要自定义搜索,你可以使用ModelAdmin.get_search_results() 来提供附件的或另外一种搜索行为。

ModelAdmin.show_full_result_count

设置show_full_result_count以控制是否应在过滤的管理页面上显示对象的完整计数(例如99 结果 103 total))。 如果此选项设置为False,则像99 结果 (显示 

默认情况下,show_full_result_count=True生成一个查询,对表执行完全计数,如果表包含大量行,这可能很昂贵。

ModelAdmin.view_on_site

设置view_on_site以控制是否显示“在网站上查看”链接。 此链接将带您到一个URL,您可以在其中显示已保存的对象。

此值可以是布尔标志或可调用的。 如果True(默认值),对象的get_absolute_url()方法将用于生成网址。

如果您的模型有get_absolute_url()方法,但您不想显示“在网站上查看”按钮,则只需将view_on_site设置为False

from django.contrib import admin

class PersonAdmin(admin.ModelAdmin):
    view_on_site = False

如果它是可调用的,它接受模型实例作为参数。 像这样:

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

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

自定义模板选项

Overriding admin templates 一节描述如何重写或扩展默认Admin 模板。 使用以下选项来重写ModelAdmin 视图使用的默认模板︰

ModelAdmin.add_form_template

add_view() 使用的自定义模板的路径。

ModelAdmin.change_form_template

change_view() 使用的自定义模板的路径。

ModelAdmin.change_list_template

changelist_view() 使用的自定义模板的路径。

ModelAdmin.delete_confirmation_template

delete_view() 使用的自定义模板,用于删除一个或多个对象时显示一个确认页。

ModelAdmin.delete_selected_confirmation_template

delete_selected 使用的自定义模板,用于删除一个或多个对象时显示一个确认页。 参见actions documentation

ModelAdmin.object_history_template

history_view() 使用的自定义模板的路径。

ModelAdmin.popup_response_template
Django中的新功能1.11。

response_add()response_change()response_delete()使用的自定义模板的路径。

2.ModelAdmin的方法

 ModelAdmin.save_model(requestobjformchange)[source]

The save_model method is given the HttpRequest, a model instance, a ModelForm instance, and a boolean value based on whether it is adding or changing the object. 覆盖此方法允许进行前或后保存操作。 使用Model.save()调用super().save_model()来保存对象。

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

from django.contrib import admin

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

delete_model方法给出了HttpRequest和模型实例。 覆盖此方法允许进行前或后删除操作。 使用Model.delete()调用super().delete_model()来删除对象。

ModelAdmin.save_formset(requestformformsetchange)[source]

ModelForm方法是给予HttpRequest,父save_formset实例和基于是否添加或更改父对象的布尔值。

例如,要将request.user附加到每个已更改的formset模型实例:

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

ModelAdmin.get_ordering(request)

get_ordering方法将request作为参数,并且预期返回listtuple,以便类似于ordering属性。 像这样:

class PersonAdmin(admin.ModelAdmin):

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

ModelAdmin.get_search_results(requestquerysetsearch_term)[source]

get_search_results方法将显示的对象列表修改为与提供的搜索项匹配的对象列表。 它接受请求,应用当前过滤器的查询集以及用户提供的搜索项。 它返回一个包含被修改以实现搜索的查询集的元组,以及一个指示结果是否可能包含重复项的布尔值。

默认实现搜索在ModelAdmin.search_fields中命名的字段。

此方法可以用您自己的自定义搜索方法覆盖。 例如,您可能希望通过整数字段搜索,或使用外部工具(如Solr或Haystack)。您必须确定通过搜索方法实现的查询集更改是否可能在结果中引入重复项,并在返回值的第二个元素中返回True

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

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

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

这个实现比search_fields ('name', '= age') ,例如,这将导致数字字段的字符串比较... 要么 UPPER( “polls_choice”。 “票” ::文) UPPER( '4') 在PostgreSQL上。

ModelForm方法给出了HttpRequest,父save_related实例,内联表单列表和一个布尔值,添加或更改。 在这里,您可以对与父级相关的对象执行任何预保存或后保存操作。 请注意,此时父对象及其形式已保存。

ModelAdmin.get_readonly_fields(requestobj=None)

list方法在添加表单上给予tupleobj(或HttpRequest),希望返回将以只读形式显示的字段名称的get_readonly_fieldsNone,如上面在ModelAdmin.readonly_fields部分中所述。

ModelAdmin.get_prepopulated_fields(requestobj=None)

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

ModelAdmin.get_list_display(request)[source]

list方法被赋予HttpRequest,并且希望返回字段名称的get_list_displaytuple显示在如上所述的ModelAdmin.list_display部分中的changelist视图上。

The get_list_display_links method is given the HttpRequest and the list or tuple returned byModelAdmin.get_list_display()预期将返回更改列表上将链接到更改视图的字段名称的tuplelistNone,如上所述在ModelAdmin.list_display_links部分中。

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

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

ModelAdmin.get_fields(requestobj=None)[source]

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

ModelAdmin.get_fieldsets(requestobj=None)

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

ModelAdmin.get_list_filter(request)[source]

HttpRequest方法被赋予get_list_filter,并且期望返回与list_filter属性相同类型的序列类型。

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

ModelAdmin.get_search_fields(request)[source]

HttpRequest方法被赋予get_search_fields,并且期望返回与search_fields属性相同类型的序列类型。

ModelAdmin.get_inline_instances(requestobj=None)[source]

list方法在添加表单上给予tupleobj(或HttpRequest),预期会返回get_inline_instancesNoneInlineModelAdmin对象,如下面的InlineModelAdmin部分所述。 例如,以下内容将返回内联,而不进行基于添加,更改和删除权限的默认过滤:

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

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

如果覆盖此方法,请确保返回的内联是inlines中定义的类的实例,或者在添加相关对象时可能会遇到“错误请求”错误。

ModelAdmin.get_urls()[source]

get_urls 的ModelAdmin 方法返回ModelAdmin 将要用到的URLs,方式与URLconf 相同。 因此,你可以用URL dispatcher 中所述的方式扩展它们︰

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

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

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

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

但是, 上述定义的函数self.my_view 将遇到两个问题:

  •  执行任何权限检查,所以会向一般公众开放。
  • 提供任何HTTP头的详细信息以防止缓存。 这意味着,如果页面从数据库检索数据,而且缓存中间件处于活动状态,页面可能显示过时的信息。

因为这通常不是你想要的,Django 提供一个方便的封装函数来检查权限并标记视图为不可缓存的。 这个包装器在ModelAdmin实例中是AdminSite.admin_view()(即self.admin_site.admin_view);使用它像这样:

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

请注意上述第5行中的被封装的视图︰

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

此包装将保护self.my_view未经授权的访问,并将应用django.views.decorators.cache.never_cache()装饰器,以确保缓存不缓存中间件是活动的。

如果该页面是可缓存的,但你仍然想要执行权限检查,你可以传递cacheable=True 的AdminSite.admin_view() 参数︰

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

ModelAdmin视图具有model_admin属性。 其他AdminSite视图具有admin_site属性。

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

返回Admin中添加和更改视图使用的ModelForm 类,请参阅add_view() 和 change_view()

其基本的实现是使用modelform_factory() 来子类化form,修改如fields 和exclude属性。 所以,举个例子,如果你想要为超级用户提供额外的字段,你可以换成不同的基类表单,就像这样︰

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

你也可以简单地直接返回一个自定义的ModelForm 类。

ModelAdmin.get_formsets_with_inlines(requestobj=None)[source]

产量(FormSetInlineModelAdmin)对用于管理添加和更改视图。

例如,如果您只想在更改视图中显示特定的内联,则可以覆盖get_formsets_with_inlines,如下所示:

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

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

ModelAdmin.formfield_for_foreignkey(db_fieldrequest**kwargs)

formfield_for_foreignkey上的ModelAdmin方法允许覆盖外键字段的默认窗体字段。 例如,要根据用户返回此外键字段的对象子集:

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

这使用User实例过滤Car外键字段,只显示由HttpRequest实例拥有的汽车。

ModelAdmin.formfield_for_manytomany(db_fieldrequest**kwargs)

formfield_for_foreignkey方法类似,可以覆盖formfield_for_manytomany方法来更改多对多字段的默认窗体字段。 例如,如果所有者可以拥有多个汽车,并且汽车可以属于多个所有者 - 多对多关系,则您可以过滤Car外键字段,仅显示由User

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

ModelAdmin.formfield_for_choice_field(db_fieldrequest**kwargs)

formfield_for_choice_fieldformfield_for_manytomany方法类似,可以覆盖formfield_for_foreignkey方法更改已声明选择的字段的默认窗体字段。 例如,如果超级用户可用的选择应与正式工作人员可用的选项不同,则可按以下步骤操作:

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

在表单域中设置的任何choices属性将仅限于表单字段。 如果模型上的相应字段有选择集,则提供给表单的选项必须是这些选择的有效子集,否则,在保存模型本身之前验证模型本身时,表单提交将失败并显示ValidationError 。

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

返回要用于列表的Changelist类。 默认情况下,使用django.contrib.admin.views.main.ChangeList。 通过继承此类,您可以更改列表的行为。

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

返回ModelForm类以用于更改列表页面上的Formset。 要使用自定义窗体,例如:

from django import forms

class MyForm(forms.ModelForm):
    pass

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

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

如果使用list_editable,则返回ModelFormSet类以在更改列表页上使用。 要使用自定义表单集,例如:

from django.forms import BaseModelFormSet

class MyAdminFormSet(BaseModelFormSet):
    pass

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

可以从URL查询字符串中的查找过滤更改列表页面中的对象。 例如,这是list_filter的工作原理。 查询与QuerySet.filter()(例如user__email=user@example.com)中使用的查找类似。 由于查询字符串中的查询可以由用户操纵,因此必须对其进行清理,以防止未经授权的数据暴露。

给定了lookup_allowed()方法,从查询字符串(例如'user__email')和相应的值(例如'user@example.com'),并返回一个布尔值,表示是否允许使用参数过滤changelist的QuerySet。 如果lookup_allowed()返回False,则会引发DisallowedModelAdminLookupSuspiciousOperation的子类)。

默认情况下,lookup_allowed()允许访问模型的本地字段,list_filter中使用的字段路径(但不是来自get_list_filter()的路径)并且limit_choices_to所需的查找在raw_id_fields中正常运行。

覆盖此方法可自定义ModelAdmin子类允许的查找。

ModelAdmin.has_add_permission(request)

如果允许添加对象,则应返回True,否则返回False

ModelAdmin.has_change_permission(requestobj=None)

如果允许编辑obj,则应返回True,否则返回False。 如果obj为False,则应返回TrueNone以指示是否允许对此类对象进行编辑(例如,False将被解释为意味着当前用户不允许编辑此类型的任何对象)。

ModelAdmin.has_delete_permission(requestobj=None)

如果允许删除obj,则应返回True,否则返回False。 如果obj是None,应该返回TrueFalse以指示是否允许删除此类型的对象(例如,False将被解释为意味着当前用户不允许删除此类型的任何对象)。

ModelAdmin.has_module_permission(request)

如果在管理索引页上显示模块并允许访问模块的索引页,则应返回True,否则False。 默认情况下使用User.has_module_perms()。 覆盖它不会限制对添加,更改或删除视图的访问,has_add_permission()has_change_permission()has_delete_permission()用于那。

ModelAdmin.get_queryset(request)

ModelAdmin上的get_queryset方法会返回管理网站可以编辑的所有模型实例的QuerySet。 覆盖此方法的一个用例是显示由登录用户拥有的对象:

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

使用django.contrib.messages 向用户发送消息。 参见custom ModelAdmin example

关键字参数运行你修改消息的级别、添加CSS 标签,如果contrib.messages 框架没有安装则默默的失败。 关键字参数与django.contrib.messages.add_message() 的参数相匹配,更多细节请参见这个函数的文档。 有一个不同点是级别除了使用整数/常数传递之外还以使用字符串。

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

返回要用于此视图的分页器的实例。 默认情况下,实例化paginator的实例。

ModelAdmin.response_add(requestobjpost_url_continue=None)[source]

add_view()阶段确定HttpResponse

response_add在管理表单提交后,在对象和所有相关实例已创建并保存之后调用。 您可以覆盖它以在对象创建后更改默认行为。

ModelAdmin.response_change(requestobj)[source]

确定change_view() 阶段的HttpResponse

response_change 在Admin 表单提交并保存该对象和所有相关的实例之后调用。 您可以重写它来更改对象修改之后的默认行为。

ModelAdmin.response_delete(requestobj_displayobj_id)[source]

delete_view()阶段确定HttpResponse

在对象已删除后调用response_delete。 您可以覆盖它以在对象被删除后更改默认行为。

obj_display是具有已删除对象名称的字符串。

obj_id是用于检索要删除的对象的序列化标识符。

ModelAdmin.get_changeform_initial_data(request)[source]

用于管理员更改表单上的初始数据的挂钩。 默认情况下,字段从GET参数给出初始值。 例如,initial_value会将name字段的初始值设置为?name=initial_value

该方法应该返回表单中的字典 { '字段名': 'fieldval'}:

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

其他方法

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

Django视图为模型实例添加页面。 见下面的注释。

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

模型实例编辑页面的Django视图。 见下面的注释。

ModelAdmin.changelist_view(requestextra_context=None)[source]

Django视图为模型实例更改列表/操作页面。 见下面的注释。

ModelAdmin.delete_view(requestobject_idextra_context=None)[source]

模型实例删除确认页面的Django 视图。 见下面的注释。

ModelAdmin.history_view(requestobject_idextra_context=None)[source]

显示给定模型实例的修改历史的页面的Django视图。

与上一节中详述的钩型ModelAdmin方法不同,这五个方法实际上被设计为从管理应用程序URL调度处理程序调用为Django视图,以呈现处理模型实例的页面CRUD操作。 因此,完全覆盖这些方法将显着改变管理应用程序的行为。

覆盖这些方法的一个常见原因是增加提供给呈现视图的模板的上下文数据。 在以下示例中,覆盖更改视图,以便为渲染的模板提供一些额外的映射数据,否则这些数据将不可用:

class MyModelAdmin(admin.ModelAdmin):

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

    def get_osm_info(self):
        # ...
        pass

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

这些视图返回TemplateResponse实例,允许您在渲染之前轻松自定义响应数据。

ModelAdmin资产定义

有时候你想添加一些CSS和/或JavaScript到添加/更改视图。 这可以通过在Media上使用ModelAdmin内部类来实现:

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

staticfiles appSTATIC_URL(或MEDIA_URL如果STATIC_URLNone资产路径。 相同的规则适用于表单上的regular asset definitions on forms

jQuery

Django管理JavaScript使用jQuery库。

为了避免与用户提供的脚本或库冲突,Django的jQuery(版本2.2.3)命名为django.jQuery。 如果您想在自己的管理JavaScript中使用jQuery而不包含第二个副本,则可以使用更改列表上的django.jQuery对象和添加/编辑视图。

在Django更改1.10:

嵌入式jQuery从2.1.4升级到2.2.3。

默认情况下,ModelAdmin类需要jQuery,因此除非有特定需要,否则不需要向您的ModelAdmin的媒体资源列表添加jQuery。 例如,如果您需要将jQuery库放在全局命名空间中(例如使用第三方jQuery插件时)或者如果您需要更新的jQuery版本,则必须包含自己的副本。

Django提供了jQuery的未压缩和“缩小”版本,分别是jquery.jsjquery.min.js

ModelAdminInlineModelAdmin具有media属性,可返回存储到JavaScript文件的路径的Media对象列表形式和/或格式。 如果DEBUGTrue,它将返回各种JavaScript文件的未压缩版本,包括jquery.js;如果没有,它将返回“最小化”版本。

向admin 添加自定义验证

在管理员中添加数据的自定义验证是很容易的。 自动管理界面重用django.forms,并且ModelAdmin类可以定义您自己的形式:

class ArticleAdmin(admin.ModelAdmin):
    form = MyArticleAdminForm

MyArticleAdminForm可以在任何位置定义,只要在需要的地方导入即可。 现在,您可以在表单中为任何字段添加自己的自定义验证:

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

重要的是你在这里使用ModelForm否则会破坏。

3.InlineModelAdmin对象

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

此管理界面能够在一个界面编辑多个Model。 这些称为内联。 假设你有这两个模型:

from django.db import models

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

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

The first step in displaying this intermediate model in the admin is to define an inline class for the Membership model: 您可以通过在ModelAdmin.inlines中指定模型来为模型添加内联:

from django.contrib import admin

class BookInline(admin.TabularInline):
    model = Book

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

Django提供了两个InlineModelAdmin的子类如下:

这两者之间仅仅是在用于呈现他们的模板上有区别。

InlineModelAdmin options 

BaseModelAdminModelAdmin具有许多相同的功能,并添加了一些自己的功能(共享功能实际上是在InlineModelAdmin超类中定义的)。 共享功能包括:

InlineModelAdmin类添加:

InlineModelAdmin.model

内联正在使用的模型。 这是必需的。

InlineModelAdmin.fk_name

模型上的外键的名称。 在大多数情况下,这将自动处理,但如果同一父模型有多个外键,则必须显式指定fk_name

InlineModelAdmin.formset

默认为BaseInlineFormSet。 使用自己的表单可以给你很多自定义的可能性。 内联围绕model formsets构建。

InlineModelAdmin.form

form的值默认为ModelForm。 这是在为此内联创建表单集时传递到inlineformset_factory()的内容。

InlineModelAdmin.classes
Django中的新功能1.10。

包含额外CSS类的列表或元组,以应用于为内联呈现的字段集。 默认为None。 fieldsets中配置的类一样,带有collapse类的内联将最初折叠,并且它们的标题将具有一个小的“show”链接。

InlineModelAdmin.extra

这控制除初始形式外,表单集将显示的额外表单的数量。 有关详细信息,请参阅formsets documentation

对于具有启用JavaScript的浏览器的用户,提供了“添加另一个”链接,以允许除了由于extra参数提供的内容之外添加任意数量的其他内联。

如果当前显示的表单数量超过max_num,或者用户未启用JavaScript,则不会显示动态链接。

InlineModelAdmin.get_extra()还允许您自定义额外表单的数量。

InlineModelAdmin.max_num

这控制在内联中显示的表单的最大数量。 这不直接与对象的数量相关,但如果值足够小,可以。 有关详细信息,请参阅Limiting the number of editable objects

InlineModelAdmin.get_max_num()还允许您自定义最大数量的额外表单。

InlineModelAdmin。 MIN_NUM T0> 

这控制在内联中显示的表单的最小数量。 有关详细信息,请参阅modelformset_factory()

InlineModelAdmin.get_min_num()还允许您自定义显示的表单的最小数量。

InlineModelAdmin。 raw_id_fields T0> 

By default, Django’s admin uses a select-box interface (<select>) for fields that are ForeignKey有时候你不想在下拉菜单中显示所有相关实例产生的开销。

ForeignKey 是一个字段列表,你希望将Input 或raw_id_fields 转换成ManyToManyField Widget:

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

用于在页面上呈现内联的模板。

InlineModelAdmin。 verbose_name T0> 

覆盖模型的内部verbose_name类中找到的Meta

InlineModelAdmin。 verbose_name_plural T0> 

覆盖模型的内部verbose_name_plural类中的Meta

InlineModelAdmin。 can_delete T0> 

指定是否可以在内联中删除内联对象。 默认为True

指定是否可以在admin中更改的内联对象具有指向更改表单的链接。 默认为False

InlineModelAdmin。get_formset请求obj =无** kwargs

返回BaseInlineFormSet类,以在管理员添加/更改视图中使用。 请参阅ModelAdmin.get_formsets_with_inlines的示例。

InlineModelAdmin。get_extra请求obj =无** kwargs

返回要使用的其他内联表单的数量。 默认情况下,返回InlineModelAdmin.extra属性。

覆盖此方法以编程方式确定额外的内联表单的数量。 例如,这可以基于模型实例(作为关键字参数obj传递):

class BinaryTreeAdmin(admin.TabularInline):
    model = BinaryTree

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

InlineModelAdmin。get_max_num请求obj =无** kwargs

返回要使用的额外内联表单的最大数量。 默认情况下,返回InlineModelAdmin.max_num属性。

覆盖此方法以编程方式确定内联表单的最大数量。 例如,这可以基于模型实例(作为关键字参数obj传递):

class BinaryTreeAdmin(admin.TabularInline):
    model = BinaryTree

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

返回要使用的内联表单的最小数量。 默认情况下,返回InlineModelAdmin.min_num属性。

覆盖此方法以编程方式确定最小内联表单数。 例如,这可以基于模型实例(作为关键字参数obj传递)。

使用具有两个或多个外键的模型与同一个父模型

有时可能有多个外键到同一个模型。 以这个模型为例:

from django.db import models

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

如果您想在Person管理员添加/更改页面上显示内联,则需要明确定义外键,因为它无法自动执行:

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

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

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

使用多对多模型

默认情况下,多对多关系的管理窗口小部件将显示在包含ManyToManyField的实际引用的任何模型上。 根据您的ModelAdmin定义,模型中的每个多对多字段将由标准HTML &lt; select multiple> t4>,水平或垂直过滤器或raw_id_admin小部件。 但是,也可以用内联替换这些小部件。

假设我们有以下模型:

from django.db import models

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

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

如果要使用内联显示多对多关系,可以通过为关系定义InlineModelAdmin对象来实现:

from django.contrib import admin

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

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

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

在这个例子中有两个值得注意的特征。

首先 - MembershipInline类引用Group.members.through。 through属性是对管理多对多关系的模型的引用。 在定义多对多字段时,此模型由Django自动创建。

其次,GroupAdmin必须手动排除members字段。 Django在定义关系(在这种情况下,Group)的模型上显示多对多字段的管理窗口小部件。 如果要使用内联模型来表示多对多关系,则必须告知Django的管理员而不是显示此窗口小部件 - 否则您最终会在管理页面上看到两个窗口小部件,用于管理关系。

请注意,使用此技术时,不会触发m2m_changed信号。 这是因为,就管理而言,through只是一个具有两个外键字段而不是多对多关系的模型。

在所有其他方面,InlineModelAdmin与任何其他方面完全相同。 您可以使用任何正常的ModelAdmin属性自定义外观。

使用多对多中介模型

当您使用ManyToManyFieldthrough参数指定中介模型时,admin将不会默认显示窗口小部件。 这是因为该中间模型的每个实例需要比可以在单个小部件中显示的更多的信息,并且多个小部件所需的布局将根据中间模型而变化。

但是,我们仍然希望能够在内联里编辑该信息。 幸运的是,这用内联管理模型很容易做到 假设我们有以下模型:

from django.db import models

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

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

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

在admin中显示此中间模型的第一步是为Membership模型定义一个内联类:

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

此简单示例使用InlineModelAdmin模型的默认Membership值,并将额外添加表单限制为一个。 这可以使用InlineModelAdmin类可用的任何选项进行自定义。

现在为PersonGroup模型创建管理视图:

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

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

最后,向管理网站注册您的PersonGroup模型:

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

现在,您的管理网站已设置为从GroupPerson详细信息页面内联编辑Membership对象。

使用通用关系作为内联

可以使用内联与一般相关的对象。 假设您有以下模型:

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

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

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

If you want to allow editing and creating an Image instance on the Product, add/change views you can useGenericTabularInline or GenericStackedInline (both subclasses of GenericInlineModelAdmin) provided by admin它们分别为表示内联对象的表单分别执行表格和堆叠的视觉布局,就像它们的非通用对象一样。 他们的行为就像任何其他内联一样。 在此示例应用的admin.py中:

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

from myproject.myapp.models import Image, Product

class ImageInline(GenericTabularInline):
    model = Image

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

admin.site.register(Product, ProductAdmin)

3.重写Admin模板

覆盖管理模板

相对重写一个admin站点的各类页面,直接在admin站点默认templates上直接进行修改是件相对简单的事。 你甚至可以为特定的应用或一个特定的模型覆盖少量的这些模板。

设置你的项目管理模板目录

Admin模板文件位于contrib/admin/templates/admin 目录中。

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

在 admin 目录下, 以你的应用名创建子目录. 在应用名的目录下,以你模型层的名字创建子目录. 注意:admin应用会以小写名的形式在目录下查找模型, 如果你想在大小写敏感的文件系统上运行app,请确保以小写形式命名目录.

为一个特定的app重写admin模板, 需要拷贝django/contrib/admin/templates/admin 目录到你刚才创建的目录下, 并且修改它们.

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

如果我们只想为名为“Page”的特定模型添加一个工具到更改列表视图,我们将把同一个文件复制到我们项目的templates/admin/my_app/page目录。

覆盖与更换管理模板

由于管理模板的模块化设计,通常既不必要也不建议替换整个模板。 最好只覆盖模板中需要更改的部分。

要继续上述示例,我们要为History模型的Page工具旁边添加一个新链接。 查看change_form.html后,我们确定我们只需要覆盖object-tools-items块。 因此,这里是我们的新change_form.html

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

就是这样! 如果我们将此文件放在templates/admin/my_app目录中,我们的链接将出现在my_app中所有模型的更改表单上。

Templates which may be overridden per app or model

不是contrib/admin/templates/admin 中的每个模板都可以在每个应用或每个模型中覆盖。 以下可以 ︰

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

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

对于那些不能以这种方式重写的模板,你可能仍然为您的整个项目重写它们。 只需要将新版本放在你的templates/admin 目录下。这对于要创建自定义的404 和500 页面特别有用。

根和登录模板

如果你想要更改主页、 登录或登出页面的模板,你最后创建你自己的AdminSite 实例(见下文),并更改AdminSite.index_templateAdminSite.login_template 和AdminSite.logout_template 属性。

4.AdminSite对象

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

Django管理站点由django.contrib.admin.sites.AdminSite的实例表示;默认情况下,此类的实例将创建为django.contrib.admin.site,您可以使用它注册模型和ModelAdmin实例。

当构造AdminSite 的实例时,你可以使用name 参数给构造函数提供一个唯一的实例名称。 这个实例名称用于标识实例,尤其是reversing admin URLs 的时候。 如果没有提供实例的名称,将使用默认的实例名称admin。 有关自定义AdminSite 类的示例,请参见Customizing the AdminSite class

AdminSite属性

Overriding admin templates中所述,模板可以覆盖或扩展基础的Admin 模板。

AdminSite.site_header

每个Admin 页面顶部的文本,形式为<h1>(字符串)。 默认为 “Django administration”。

AdminSite.site_title

每个Admin 页面底部的文本,形式为<title>(字符串)。 默认为“Django site admin”。

AdminSite.site_url

每个Admin 页面顶部"View site" 链接的URL。 默认情况下,site_url 为/。 设置为None 可以删除这个链接。

对于在子路径上运行的站点,each_context()方法会检查当前请求是否具有request.META['SCRIPT_NAME']设置并使用该值,如果site_url未设置为/以外的其他内容。

在Django更改1.10:

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

AdminSite.index_title

Admin 主页顶部的文本(一个字符串)。 默认为 “Site administration”。

AdminSite.index_template

Admin 站点主页的视图使用的自定义模板的路径。

AdminSite.app_index_template

Admin 站点app index 的视图使用的自定义模板的路径。

AdminSite.empty_value_display

用于在管理站点更改列表中显示空值的字符串。 默认为破折号。 通过在字段上设置empty_value_display属性,也可以在每个ModelAdmin以及ModelAdmin中的自定义字段上覆盖该值。 有关示例,请参见ModelAdmin.empty_value_display

AdminSite.login_template

Admin 站点登录视图使用的自定义模板的路径。

AdminSite.login_form

Admin 站点登录视图使用的AuthenticationForm 的子类。

AdminSite.logout_template

Admin 站点登出视图使用的自定义模板的路径。

AdminSite.password_change_template

Admin 站点密码修改视图使用的自定义模板的路径。

AdminSite.password_change_done_template

Admin 站点密码修改完成视图使用的自定义模板的路径。

AdminSite方法

AdminSite.each_context(request)[source]

返回一个字典,包含将放置在Admin 站点每个页面的模板上下文中的变量。

包含以下变量和默认值:

  • site_headerAdminSite.site_header

  • site_titleAdminSite.site_title

  • site_urlAdminSite.site_url

  • has_permissionAdminSite.has_permission()

  • available_apps:从当前用户可用的application registry中的应用程序列表。 列表中的每个条目都是表示具有以下密钥的应用程序的dict:

    • app_label:应用程序标签
    • app_url:管理员中的应用程序索引的URL
    • has_module_perms:一个布尔值,表示当前用户是否允许显示和访问模块的索引页面
    • models:应用程序中可用的模型列表

    每个模型都是具有以下键的dict:

    • object_name:模型的类名
    • name:复数名称的模型
    • perms:a dict tracking addchangedelete permissions
    • admin_url:admin changelist模型的URL
    • add_url:添加新模型实例的admin URL
AdminSite.has_permission(request)[source]

对于给定的True,如果用户有权查看Admin 网站中的至少一个页面,则返回 HttpRequest。 默认要求User.is_activeUser.is_staff 都为True

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

使用给定的admin_class注册给定的模型类(或模型类组成的可迭代对象)。 admin_class默认为ModelAdmin(默认的管理后台选项)。 如果给出了关键字参数 — 例如list_display — 它们将作为选项应用于admin_class。

如果模型是抽象的,则引发ImproperlyConfigured。 如果模型已经注册则引发django.contrib.admin.sites.AlreadyRegistered

AdminSite的实例挂接到URLconf中

设置Django管理后台的最后一步是放置你的AdminSite到你的URLconf中。 将一个给定的URL指向AdminSite.urls方法就可以做到。 没有必要使用include()

在下面的示例中,我们注册默认的AdminSite实例django.contrib.admin.site到URL /admin/

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

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

定制AdminSite

如果你想要建立你自己的具有自定义行为Admin 站点,你可以自由地子类化AdminSite 并重写或添加任何你喜欢的东西。 你只需创建AdminSite 子类的实例(方式与你会实例化任何其它Python 类相同) 并注册你的模型和ModelAdmin 子类与它而不是默认的站点。 最后,更新myproject/urls.py 来引用你的AdminSite 子类。

Admin.py

from django.contrib.admin import AdminSite

from .models import MyModel

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

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

url.py

from django.conf.urls import url

from myapp.admin import admin_site

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

注意,当使用你自己的admin 实例时,你可能不希望自动发现AdminSite 模块,因为这将导入admin 模块到你的每个myproject.admin模块中 。 这时,你需要将'django.contrib.admin' 而不是'django.contrib.admin.apps.SimpleAdminConfig' 放置在你的INSTALLED_APPS 设置中。

相同的URLconf 中的多个管理站点

在同一个Django供电的网站上创建管理站点的多个实例很容易。 只需要创建AdminSite 的多个实例并将每个实例放置在不同的URL 下。

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

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

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

AdminSite 实例的构造函数中接受一个单一参数用做它们的名字,可以是任何你喜欢的东西。 此参数将成为reversing them 时URL 名称的前缀。 只有在你使用多个AdminSite 时它才是必要的。

将视图添加到管理站点

ModelAdmin一样,AdminSite提供了一个get_urls()方法,可以重写该方法以定义网站的其他视图。 要向您的管理网站添加新视图,请扩展基本get_urls()方法,为新视图添加模式。

添加密码重置功能

您可以通过在URLconf中添加几行来将密码重置功能添加到管理站点。 具体操作就是加入下面四个正则规则。

from django.contrib.auth import views as auth_views

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

(假设您已在admin/添加了管理员,并要求您在包含管理应用程序的行之前将^admin/开头的网址)。

如果存在admin_password_reset命名的URL,则会在密码框下的默认管理登录页面上显示“忘记了您的密码?”链接。

LogEntry对象

类 楷模。 LogEntry T0> 

LogEntry类跟踪通过管理界面完成的对象的添加,更改和删除。

LogEntry属性

LogEntry.action_time

行动的日期和时间。

LogEntry.user

执行该操作的用户(一个AUTH_USER_MODEL实例)。

LogEntry.content_type

修改对象的ContentType

LogEntry.object_id

修改对象的主键的文本表示。

LogEntry.object_repr

修改后的对象'repr()

LogEntry.action_flag

记录的动作类型:ADDITIONCHANGEDELETION

例如,要获取通过管理员完成的所有添加的列表:

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

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

修改的详细说明。 例如,在编辑的情况下,消息包含编辑字段的列表。 Django管理网站将此内容格式化为JSON结构,因此get_change_message()可以重构以当前用户语言翻译的消息。 自定义代码可能将此设置为纯字符串。 建议您使用get_change_message()方法检索该值,而不是直接访问该值。

在Django更改1.10:

以前,此属性始终是一个简单的字符串。 它现在是JSON结构,以便可以使用当前用户语言翻译该消息。 老消息不变。

LogEntry方法

LogEntry.get_edited_object()

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

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

change_message格式化并转换为当前用户语言。 在Django 1.10之前创建的消息将始终以其记录的语言显示。

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

 

AdminSite部署后,该站点所提供的视图都可以使用Django的URL反向解析系统访问。

 

AdminSite提供以下命名URL:

 

页面网址名称参数
指数 index  
登录 login  
登出 logout  
密码更改 password_change  
完成密码更改 password_change_done  
i18n JavaScript jsi18n  
应用的主页 app_list app_label
重定向到对象的页面 view_on_site content_type_idobject_id

 

每个ModelAdmin 实例还将提供额外的命名URL:

 

页面网址名称参数
更改列表 {{ app_label }}_{{ model_name }}_changelist  
添加 {{ app_label }}_{{ model_name }}_add  
历史 {{ app_label }}_{{ model_name }}_history OBJECT_ID
删除 {{ app_label }}_{{ model_name }}_delete OBJECT_ID
更改 {{ app_label }}_{{ model_name }}_change OBJECT_ID

 

UserAdmin提供了一个命名的URL:

 

页面网址名称参数
密码更改 auth_user_password_change 用户名

 

这些命名URL 注册的应用命名空间为admin,实例命名空间为对应的AdminSite 实例的名称。

 

所以,如果你想要获取默认Admin 中,(polls 应用的) 一个特定的Choice 对象的更改视图的引用,你可以调用︰

 

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

 

这将查找Admin 应用中第一个注册的实例(无论实例名称是什么),并解析到poll.Choice 实例的更改视图。

如果你想要查找一个特定的Admin 实例中URL,请提供实例的名称作为current_app 给反向解析的调用 。 例如,如果你希望得到名为custom 的Admin 实例中的视图,你将需要调用

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

为了让模板中反向解析Admin URL 更加容易,Django 提供一个admin_urlname 过滤器,它以Action 作为参数︰

 

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

 

在上面的例子中Action 将匹配上文所述的ModelAdmin 实例的URL 名称的最后部分。 model_name 变量可以是任何具有app_labelopts 属性的对象,通常由Admin 视图为当前的模型提供。

 

staff_member_required装饰器

 

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

该装饰器用于需要授权的管理员视图。 使用此功能装饰的视图将具有以下行为:

  • 如果用户登录,是工作人员(User.is_staff=True),并且处于活动状态(User.is_active=True),请正常执行该视图。
  • 否则,该请求将被重定向到由login_url参数指定的URL,由redirect_field_name指定的查询字符串变量中的原始请求路径。 例如:/admin/login/?next=/admin/polls/question/3/

使用示例

from django.co

 

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

@staff_member_required
def my_view(request):
    ...

 6.管理员动作

简单来说,Django管理员的基本工作流程是“选择一个对象,然后进行更改”。这对大多数用例都很有效。 然而当你一次性要对多个对象做相同的改变,这个流程是非常的单调乏味的。

在这些情况下,Django Admin 可以让你编写并注册“Action” —— 仅仅只是一个以更改列表页面上选中对象的列表为参数的回调函数。

如果您查看管理员中的任何更改列表,您将看到此功能在操作中; Django附带所有型号的“删除所选对象”操作。 例如,下面是从Django 内建的django.contrib.auth 应用创建的用户模型:

写入动作

通过示例来解释Action 最为简单,让我们开始吧。

Action 的一个最为普遍的用例是模型的整体更新。 考虑带有Article模型的简单新闻应用:

from django.db import models

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

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

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

我们可能在模型上执行的一个普遍任务是,将文章状态从“草稿”更新为“已发布”。 在Admin 界面上一次处理一篇文章非常轻松,但是如果我们想要批量发布一些文章,则会非常单调乏味。 所以让我们编写一个Action,可以让我们将一篇文章的状态修改为“已发布”。

写入动作函数

首先,我们需要定义一个函数,当在Admin 界面上触发该Action 的时候调用。 Action 函数,跟普通的函数一样,需要接收三个参数:

我们用于发布这些文章的函数并不需要ModelAdmin 和请求对象,但是我们会用到查询集:

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

编写Action 的全部内容实际上就这么多了。 但是,我们要进行一个可选但是有用的步骤,在Admin 中给Action 起一个“友好”的标题。 默认情况下,Action 以“Make published” 的形式出现在Action 列表中 —— 将函数名称中的所有下划线用空格替换。 这样就很好了,但是我们可以提供一个更好、更人性化的名称,通过向make_published函数添加short_description 属性:

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

ModelAdmin 添加动作

接下来,我们需要把Action 告诉ModelAdmin。 它和其他配置项的工作方式相同。 所以,带有Action 及其注册的完整的admin.py 看起来像这样:

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

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

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

admin.site.register(Article, ArticleAdmin)

这段代码向我们提供的Admin 更改列表看起来像这样:

这就是全部内容了。 如果你想编写自己的Action ,你现在应该知道怎么开始了。 这篇文档的剩余部分会介绍更多高级技巧。

处理动作中的错误

如果你的Action 运行时发生可预见的些错误,你应该以优雅的方式向用户通知这些错误。 这意味着处理异常并使用django.contrib.admin.ModelAdmin.message_user()在响应中向用户展示友好的问题描述。

高级动作技术

对于进一步的选择,你可以使用一些额外的选项。

作为ModelAdmin方法的操作

上面的例子展示了定义为一个简单函数的make_published操作。 这真是极好的,但是以视图的代码设计角度来看,它并不完美:由于操作与Article紧密耦合,不如将操作直接绑定到ArticleAdmin对象上更有意义。

这样做十分简单:

class ArticleAdmin(admin.ModelAdmin):
    ...

    actions = ['make_published']

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

首先注意,我们将self放到一个方法中,并重命名 modeladmin 为make_published,其次,我们现在将'make_published'字符串放进了actions,而不是一个直接的函数引用。 这样会让 ModelAdmin将这个操作视为方法。

将Action 定义为方法,可以使操作以更加直接、符合语言习惯的方式来访问ModelAdmin,并可以调用Admin 提供的任何方法。

例如,我们可以使用self 向用户发送消息来告诉她Action 成功了:

class ArticleAdmin(admin.ModelAdmin):
    ...

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

这会使动作与后台在成功执行动作后做的事情相匹配:

 

提供中间页面的操作

默认情况下,在执行Actions 之后,用户会简单地通过重定向返回到之前的更改列表页面中。 然而,一些Action,尤其是更加复杂的操作,需要返回一个中间页面。 例如,内建的删除操作,在删除选中对象之前需要向用户询问来确认。

要提供中间页面,只要从你的操作返回HttpResponse(或其子类)就可以了。 例如,你可以编写一个简单的导出函数,它使用Django 的serialization functions来将一些选中的对象转换为JSON:

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

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

一般情况下,上面的代码的实现方式并不是很好。 大多数情况下,最佳实践是返回 HttpResponseRedirect,并且使用户重定向到你编写的视图中,向GET查询字符串传递选中对象的列表。 这需要你在中间界面上提供复杂的交互逻辑。 例如,如果你打算提供一个更加复杂的导出函数,你会希望让用户选择一种格式,以及可能在导出中包含一个含有字段的列表。 最佳方式是编写一个小型的操作,简单重定向到你的自定义导出视图中:

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

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

正如你所看到的,行动是简单的部分;所有复杂的逻辑将属于您的导出视图。 这需要处理任何类型的对象,所以需要处理ContentType

这个视图的编写作为一个练习留给读者。

可用于整个admin站点的actions

AdminSite。add_actionactionname = None[source] 

如果一些操作对管理站点的任何对象都可用的话,是非常不错的 -- 上面所定义的导出操作是个不错的备选方案。 你可以使用AdminSite.add_action()让一个操作在全局都可以使用。 像这样:

from django.contrib import admin

admin.site.add_action(export_selected_objects)

这样,export_selected_objects 操作可以在全局使用,名称为“export_selected_objects”。 你可以显式指定Action的名称 —— 如果你想以编程的方式remove the action 是非常有用的 —— 通过向AdminSite.add_action()传递第二个参数:

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

禁用动作

有时你需要禁用特定的操作 -- 尤其是registered site-wide -- 对于特定的对象。 你可以使用一些方法来禁用操作:

禁用站点范围的动作

AdminSite。disable_action(name)[source]

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

例如,你可以使用这个方法来移除内建的“删除选中的对象”操作:

admin.site.disable_action('delete_selected')

一旦你执行了上面的代码,这个操作不再对整个站点中可用。

然而,如果你需要为特定的模型重新启动在全局禁用的对象,把它显式放在ModelAdmin.actions 列表中就可以了:

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

# 这个ModelAdmin不会有删除功能
class SomeModelAdmin(admin.ModelAdmin):
    actions = ['some_other_action']
    ...

# 这个声明还要用
class AnotherModelAdmin(admin.ModelAdmin):
    actions = ['delete_selected', 'a_third_action']
    ...

禁用特定ModelAdmin 的所有操作

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

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

这样会告诉ModelAdmin,不要展示或者允许任何操作,包括site-wide actions

有条件地启用或禁用动作

的ModelAdmin。get_actions(request)[source]

最后,你可以通过覆写ModelAdmin.get_actions(),对每个请求(每个用户)按需开启或禁用操作。

这个函数返回包含允许操作的字典。 字典的键是操作的名称,值是 (function, name, short_description)元组。

多数情况下,你会按需使用这一方法,来从超类中的列表移除操作。 例如,如果我只希望名称以'J'开头的用户可以批量删除对象,我可以执行下面的代码:

class MyModelAdmin(admin.ModelAdmin):
    ...

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

日志

logging的组成

Python的logging配置由四个部分组成:

Loggers

Logger为日志系统的入口。 每个logger是一个带有名称的bucket,你可以向这个bucket写入需要处理的消息。

每个logger都有一个日志级别。 日志级别表示该logger将要处理的消息的严重性。 Python定义以下几种日志级别:

  • DEBUG:用于调试目的的底层系统信息
  • INFO:普通的系统信息
  • WARNING:表示出现一个较小的问题。
  • ERROR:表示出现一个较大的问题。
  • CRITICAL:表示出现一个致命的问题。

写入logger的每条消息都是一个日志记录。 每个日志记录也具有一个日志级别,它表示对应的消息的严重性。 每个日志记录还可以包含描述正在打印的事件的有用元信息。 这些元信息可以包含很多细节,例如回溯栈或错误码。

当一条消息传递给logger时,消息的日志级别将与logger的日志级别进行比较。 如果消息的日志级别大于等于logger 的日志级别,该消息将会往下继续处理。 如果小于,该消息将被忽略。

Logger一旦决定消息需要处理,它将传递该消息给一个Handler

Handlers

Handler决定如何处理logger中的每条消息。 它描述一个特定的日志行为,例如将消息写到屏幕上、写到文件中或者写到网络socket。

与logger一样,handler也有一个日志级别。 如果消息的日志级别小于handler的级别,handler将忽略该消息。

Logger 可以有多个handler,而每个handler 可以有不同的日志级别。 利用这种方式,可以根据消息的重要性提供不同形式的处理。 例如,你可以用一个handler将CRITICAL和 ERROR消息发送给一个页面服务,而用另外一个hander将所有的消息(包括ERRORCRITICAL消息)记录到一个文件中用于以后进行分析。

Filters

Filter用于对从logger传递给handler的日志记录进行额外的控制。

默认情况下,满足日志级别的任何消息都将被处理。 通过安装一个filter,你可以对日志处理添加额外的条件。 例如,你可以安装一个filter,只允许处理来自特定源的ERROR 消息。

Filters 还可以用于修改将要处理的日志记录的优先级。 例如,如果日志记录满足特定的条件,你可以编写一个filter 将日志记录从ERROR 降为WARNING

Filters可以安装在loggers或handlers上;可以将多个filters链接起来执行多个过滤操作。

Formatters

最后,日志记录需要转换成文本。 Formatter描述文本的准确格式。 Formatter通常由包含日志记录的属性的Python格式化字符串组成;但是,你也可以编写自定义formatters来实现特定的格式化行为。

使用logging

配置好logger、handler、filter 和formatter 之后,你需要在代码中放入logging 调用。 使用logging 框架非常简单。 下面是个例子:

# 导入logging库
import logging

# 获取logger的一个实例
logger = logging.getLogger(__name__)

def my_view(request, arg1, arg):
    ...
    if bad_mojo:
        # 记录一条错误信息
        logger.error('Something went wrong!')

就是这样! 每次满足bad_mojo 条件,将写入一条错误日志记录。

命名loggers

logging.getLogger() 调用获取(如有必要则创建)一个logger 的实例。 Logger实例通过一个名称标识。 Logger使用名称标识的目的是用于配置。

Logger的名称习惯上通常使用__name__,即包含该logger的Python模块的名字。 这允许你基于模块filter和handle日志调用。 如果你想使用其它方式组织日志消息,可以提供点号分隔的名称来标识你的logger:

# 获取一个特定名称的logger
logger = logging.getLogger('project.interesting.stuff')

点号分隔的logger名称定义一个层级。 project.interesting logger被认为是project.interesting.stuff logger 的父级;project logger是project.interestinglogger的父级。

层级为何如此重要? 因为可以设置logger传播它们的logging调用给它们的上一级。 利用这种方式,你可以在根logger上定义一系列的handler,并捕获子logger中的所有logging调用。 project这一级定义的handler将会捕获在project.interestingproject.interesting.stuff这俩级logger上的日志消息。

这种传播行为可以基于每个logger 进行控制。 如果你不想让某个logger 传播消息给它的上一级,你可以关闭这个行为。

调用logging

Logger 实例为每个默认的日志级别提供一个入口方法:

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

还有另外两个调用:

  • logger.log():打印消息时手工指定日志级别。
  • logger.exception():创建一个ERROR 级别日志消息,它封装当前异常栈的帧。

配置logging

当然,只是将logging 调用放入你的代码中还是不够的。 你还需要配置logger、handler、filter 和formatter 来确保日志的输出是有意义的。

Python的logging库提供几种配置logging的技术,从程序接口到配置文件。 默认情况下,Django使用dictConfig格式

为了配置logging,你需要使用LOGGING来定义字典形式的logging设置。 这些设置描述你的logging设置的logger、handler、filter和formatter,以及它们的日志等级和其它属性。

默认情况下,LOGGING设置与Django默认的logging配置使用下面的模式进行合并。

如果LOGGING中的disable_existing_loggers键为True(默认值),那么默认配置中的所有logger都将禁用。 禁用loggers与删除不一样;loggers仍然存在,但会静默地丢弃任何记录到它的记录,甚至不会将条目传播到父logger。 所以你应该非常小心使用'disable_existing_loggers': True;这可能不是你想要的。 相反,你可以将disable_existing_loggers设置为False,并重新定义部分或全部默认loggers;或者你可以将LOGGING_CONFIG设置为None自己处理logging配置

Logging的配置属于Django setup()函数的一部分。 所以,你可以肯定在你的项目代码中logger是永远可用的。

示例

dictConfig格式的完整文档是logging字典配置最好的信息源。 但是为了让你尝尝,下面是几个例子。

首先,这是一个简单的配置,将所有记录从django logger写入本地文件:

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

如果你使用这个示例,请确保修改'filename' 路径为运行Django 应用的用户有权限写入的一个位置。

其次,下面这个示例演示如何让日志系统将Django 的日志打印到控制台。 它在本地开发期间可能有用。

默认情况下,此配置只将级别INFO或更高版本的消息发送到控制台(与Django的默认日志记录配置相同),但默认情况下仅在DEBUG=True时显示日志记录)。 Django 中这样的日志信息不多。 然而,使用此配置,你还可以设置环境变量DJANGO_LOG_LEVEL=DEBUG,以查看Django的所有调试日志记录,这非常详细,因为它包括所有数据库查询:

import os

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

最后,下面是相当复杂的一个logging 设置:

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

这个logging 配置完成以下事情:

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

  • 定义两个formatter:

    • simple,它只输出日志的级别(例如,DEBUG)和日志消息。

      format 字符串是一个普通的Python 格式化字符串,描述每行日志的细节。 可以在Formatter Objects中找到可以输出的详细列表。

    • verbose,它输出日志级别、日志消息,以及时间、进程、线程和生成日志消息的模块。

  • 定义两个过滤器:

    • project.logging.SpecialFilter,使用别名special。 如果此过滤器需要其他参数,则可以在过滤器配置字典中将其作为附加键提供。 在这种情况下,实例化SpecialFilter时,参数foo将被赋值为bar
    • django.utils.log.RequireDebugTrue, which passes on records when DEBUG is True.
  • 定义两个处理程序:

    • console,一个StreamHandler,它将向stderr打印任何INFO(或更高版本)的消息。 这个handler 使用simple 输出格式。
    • mail_admins,一个AdminEmailHandler,它将用邮件发送ERROR(和更高级)的消息到站点管理员。 这个handler 使用special filter。
  • 配置三个logger:

    • django,它将所有消息传递到console处理程序。
    • django.request,它将把所有的ERROR日志信息发送给mail_admins handler。 另外,标记这个logger  向它的父级传递日志消息(即propagate的值为False)。 这表示写入django.request 的日志信息将不会被django logger 处理。
    • myproject.custom,将把INFO级别(更高级别)的日志经过special过滤器过滤后,发送给console和 mail_admins这俩个handler。 这意味着所有INFO级消息(或更高级别)将被打印到控制台; ERRORCRITICAL消息也将通过电子邮件输出。

自定义logging配置

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

LOGGING_CONFIG 设置定义一个可调用对象,将它用来配置Django 的logger。 默认情况下,它指向Python 的logging.config.dictConfig() 函数。 但是,如果你想使用不同的配置过程,你可以使用其它只接受一个参数的可调用对象。 配置logging 时,将使用LOGGING 的内容作为参数的值。

禁用logging配置

如果你完全不想配置logging(或者你想使用自己的方法手工配置logging),你可以设置LOGGING_CONFIG 为None。 这将禁用Django默认的logging配置过程。 下面的示例禁用Django 的logging 配置,然后手工配置logging:

LOGGING_CONFIG = None

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

设置LOGGING_CONFIG 为None 只表示禁用自动配置过程,而不是禁用logging 本身。 如果你禁用配置过程,Django 仍然执行logging 调用,只是调用的是默认定义的logging 行为。

分页

 Django提供了一些类来帮助你管理分页的数据 — 也就是说,数据被分在不同页面中,并带有“上一页/下一页”链接。 这些类位于django/core/paginator.py中。

示例

Paginator提供对象的一个列表,以及你想为每一页分配的元素数量,它就会为你提供访问每一页上对象的方法:

 

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

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

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

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

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

在视图中使用Paginator

下面是一个有点复杂的例子,它们在视图中使用Paginator来为查询集分页。 我们提供视图以及相关的模板来展示如何展示这些结果。 这个例子假设你拥有一个已经导入的Contacts模型。

视图函数看起来像是这样:

 

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

def listing(request):
    contact_list = Contacts.objects.all()
    paginator = Paginator(contact_list, 25) # 每页显示25个联系人

    page = request.GET.get('page')
    try:
        contacts = paginator.page(page)
    except PageNotAnInteger:
        # 如果page不是一个整数,则展示第一页。
        contacts = paginator.page(1)
    except EmptyPage:
        # 如果page不在范围内(例如,9999),则展示结果的最后一页。
        contacts = paginator.page(paginator.num_pages)

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

list.html模板中,你会想要包含页面之间的导航,以及来自对象本身的有用信息:

 

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

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

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

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

Paginator对象

Paginator类拥有以下构造函数:

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

必选参数

object_list

列表、元组、QuerySet或具有count()__len__()方法的其它可切片的对象。 为了得到一致的分页,QuerySet应该排好序,例如使用order_by()子句或模型上的默认ordering

 

per_page
每个页面上包含的元素的最大数目,不包含孤儿(参见下文的orphans参数)。

可选参数

orphans
当你不想要最后一个页面的记录很少时,请使用此选项。 如果最后一页通常会有一些小于或等于orphans的项目,那么这些项目将被添加到上一页(成为最后一页),而不是让它们自己单独留在一个页面。 例如有23个记录,per_page=10orphans=3时,将有两个页面;第一页有10个记录,第二页(最后一页)有13个记录。 orphans默认为零,这意味着页面从不组合,最后一页可能有一个项目。
allow_empty_first_page
第一页是否允许为空。 如果为Falseobject_list为空,则将引发一个EmptyPage错误。

方法

Paginator.page(number)[source]

返回在提供的下标处的Page对象,下标以1开始。 如果提供的页码不存在,抛出InvalidPage异常。

属性

Paginator.count

所有页面中包含的对象总数。

 

Paginator.num_pages

页面总数。

Paginator.page_range

页数的基于1的范围迭代器,例如产生[1, 2, 3, 4]

InvalidPage异常

exception InvalidPage[source]

异常的基类,当paginator传入一个无效的页码时抛出。

Paginator.page()放回在所请求的页面无效(比如不是一个整数)时,或者不包含任何对象时抛出异常。 通常,捕获InvalidPage异常就够了,但是如果你想更加精细一些,可以捕获以下两个异常之一:

exception PageNotAnInteger[source]

当向page()提供一个不是整数的值时抛出。

exception EmptyPage[source]

当向page()提供一个有效值,但是那个页面上没有任何对象时抛出。

这两个异常都是InvalidPage的子类,所以你可以通过简单的except InvalidPage来处理它们。

Page对象

你通常不需要手动构建 Page对象 -- 你可以从Paginator.page()来获得它们。

class Page(object_listnumberpaginator)[source]

当调用len()或者直接迭代一个页面的时候,它的行为类似于 Page.object_list 的序列。

方法

Page.has_next()[source]

如果有下一页,则返回True

Page.has_previous()[source]

如果有上一页,返回 True

Page.has_other_pages()[source]

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

Page.next_page_number()[source]

返回下一页的页码。 如果下一页不存在,抛出InvalidPage异常。

Page.previous_page_number()[source]

返回上一页的页码。 如果上一页不存在,抛出InvalidPage异常。

Page.start_index()[source]

返回当前页上的第一个对象,相对于分页列表的所有对象的序号,从1开始。 比如,将五个对象的列表分为每页两个对象,第二页的start_index()会返回3

Page.end_index()[source]

返回当前页上的最后一个对象,相对于分页列表的所有对象的序号,从1开始。 比如,将五个对象的列表分为每页两个对象,第二页的end_index() 会返回 4

属性

Page.object_list

当前页上所有对象的列表。

Page.number

当前页的序号,从1开始。

Page.paginator

相关的Paginator对象。

会话

 Django 提供对匿名会话的完全支持。 这个会话框架让你可以存储和取回每个站点访客任意数据。 它在服务器端存储数据, 并以cookies的形式进行发送和接受数据. Cookie 包含会话的ID —— 而不是数据本身(除非你使用cookie based backend)。

启用会话

会话是通过一个middleware实现的。

为了启用会话功能,需要这样做:

  • 编辑MIDDLEWARE设置,并确保它包含'django.contrib.sessions.middleware.SessionMiddleware'。 使用django-admin startproject创建的默认的settings.py已经启用SessionMiddleware

如果你不想使用会话,你也可以从MIDDLEWARE'django.contrib.sessions'SessionMiddleware您的INSTALLED_APPS。 它将节省一些性能消耗。

配置会话引擎

默认情况下,Django 存储会话到你的数据库中(使用django.contrib.sessions.models.Session模型)。 虽然这很方便,但是在某些架构中存储会话在其它地方会更快,所以可以配置Django 来存储会话到你的文件系统上或缓存中。

使用数据库支持的会话

如果你想使用数据库支持的会话,你需要添加'django.contrib.sessions' 到你的INSTALLED_APPS设置中。

在配置完成之后,请运行manage.py migrate来安装保存会话数据的一张数据库表。

使用缓存的会话

为了更好的性能,你可能想使用一个基于缓存的会话后端。

要使用Django的缓存系统存储会话数据,您首先需要确保已配置缓存

 

如果你在CACHES中定义多个缓存,Django 将使用默认的缓存。 若要使用另外一种缓存,请设置SESSION_CACHE_ALIAS为该缓存的名字。

配置好缓存之后,对于如何在缓存中存储数据你有两个选择:

  • 对于简单的缓存会话存储,可以设置SESSION_ENGINE 为"django.contrib.sessions.backends.cache" 。 此时会话数据将直接存储在你的缓存中。 然而,缓存数据将可能不会持久:如果缓存填满或者缓存服务器重启,缓存数据可能会被清理掉。
  • 若要持久的缓存数据,可以设置SESSION_ENGINE"django.contrib.sessions.backends.cached_db"。 这使用直写缓存 - 每次写入高速缓存也将写入数据库。 会话读取仅在数据不在缓存中时才使用数据库。

两种会话的存储都非常快,但是简单的缓存更快,因为它放弃了持久性。 大部分情况下,cached_db后端已经足够快,但是如果你需要榨干最后一点的性能,并且接受会话数据丢失的风险,那么你可使用cache后端。

如果你使用cached_db 会话后端,你还需要遵循使用数据库支持的会话中的配置说明。

使用基于文件的会话

要使用基于文件的会话,请设置SESSION_ENGINE"django.contrib.sessions.backends.file"

你可能还想设置SESSION_FILE_PATH(它的默认值来自tempfile.gettempdir()的输出,大部分情况是/tmp)来控制Django在哪里存储会话文件。 请保证你的Web 服务器具有读取和写入这个位置的权限。

在视图中使用会话

SessionMiddleware 激活时,每个HttpRequest 对象 —— 传递给Django 视图函数的第一个参数 —— 将具有一个session 属性,它是一个类字典对象。

你可以在你的视图中任何地方读取并写入 request.session。 你可以多次编辑它。

class backends.base.SessionBase

这是所有会话对象的基类。 它具有以下标准的字典方法:

__getitem__(key)

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

__setitem__(keyvalue)

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

__delitem__(key)

例如:del request.session['fav_color']。 如果给出的KeyError 在会话中不存在,将抛出 key

__contains__(key)

例如:'fav_color' in request.session

get(keydefault=None)

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

pop(keydefault=__not_given)

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

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

它还具有这些方法:

flush()

从会话中删除当前会话数据,并删除会话cookie。 这用于确保前面的会话数据不可以再次被用户的浏览器访问(例如,django.contrib.auth.logout() 函数中就会调用它)。

设置一个测试的Cookie 来验证用户的浏览器是否支持Cookie。 因为Cookie 的工作方式,只有到用户的下一个页面才能验证。 更多信息参见下文的设置测试的Cookie

返回True 或False,取决于用户的浏览器是否接受测试的Cookie。 因为Cookie的工作方式,你必须在前面一个单独的页面请求中调用set_test_cookie()。 更多信息参见下文的设置测试的Cookie

删除测试的Cookie。 使用这个函数来自己清理。

set_expiry(value)

设置会话的超时时间。 你可以传递一系列不同的值:

  • 如果value是一个整数,会话将在这么多秒没有活动后过期。 例如,调用request.session.set_expiry(300)将使得会话在5分钟后过期。
  • 若果value是一个datetimetimedelta 对象,会话将在这个指定的日期/时间过期。 注意datetimetimedelta值只有在你使用PickleSerializer时才可序列化。
  • 如果value0,那么会话的Cookie将在用户的浏览器关闭时过期。
  • 如果valueNone,那么会话转向使用全局的会话过期策略。

过期的计算不考虑读取会话的操作。 会话的过期从会话上次修改的时间开始计算。

get_expiry_age()

返回会话离过期的秒数。 对于没有自定义过期的会话(或者设置为浏览器关闭时过期的会话),它将等于SESSION_COOKIE_AGE

该函数接收两个可选的关键字参数:

  • modification:会话的最后一次修改时间,类型为一个datetime 对象。 默认为当前的时间。
  • None:会话的过期信息,类型为一个datetime 对象、一个int(以秒为单位)或expiry。 默认为通过set_expiry()保存在会话中的值,如果没有则为None
get_expiry_date()

返回过期的日期。 对于没有自定义过期的会话(或者设置为浏览器关闭时过期的会话),它将等于从现在开始SESSION_COOKIE_AGE秒后的日期。

这个函数接受与get_expiry_age()一样的关键字参数。

get_expire_at_browser_close()

返回True 或False,取决于用户的会话Cookie在用户浏览器关闭时会不会过期。

clear_expired()

从会话的存储中清除过期的会话。 这个类方法被clearsessions调用。

cycle_key()

创建一个新的会话,同时保留当前的会话数据。 django.contrib.auth.login() 调用这个方法来减缓会话的固定。

会话序列化

默认情况下,Django使用JSON序列化会话数据。 您可以使用SESSION_SERIALIZER设置自定义会话序列化格式。 即使使用Write your own serializer中描述的注意事项,我们强烈建议您使用JSON序列化,特别是在使用cookie后端时。

例如,如果您使用pickle序列化会话数据,则会出现攻击情形。 如果你使用的是signed cookie session backend 并且SECRET_KEY被攻击者知道(Django 本身没有漏洞会导致它被泄漏),攻击者就可以在会话中插入一个字符串,在unpickle 之后可以在服务器上执行任何代码。 在因特网上这个攻击技术很简单并很容易查到。 尽管Cookie 会话的存储对Cookie 保存的数据进行了签名以防止篡改,SECRET_KEY 的泄漏会立即使得可以执行远端的代码。

捆绑序列化器

class serializers.JSONSerializer

对 django.core.signing中的JSON 序列化方法的一个包装。 只可以序列基本的数据类型。

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

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

类似地,无法在JSON中编码的数据,如非UTF8字节,如'\xd9'(引发UnicodeDecodeError)不能被存储。

有关JSON序列化的限制的更多详细信息,请参阅Write your own serializer部分。

class serializers.PickleSerializer

支持任意Python 对象,但是正如上面描述的,可能导致远端执行代码的漏洞,如果攻击者知道了SECRET_KEY

编写自己的串行器

注意,与PickleSerializer不同,JSONSerializer 不可以处理任意的Python 数据类型。 这是常见的情况,需要在便利性和安全性之间权衡。 如果你希望在JSON 格式的会话中存储更高级的数据类型比如request.session 和 datetime,你需要编写一个自定义的序列化器(或者在保存它们到Decimal中之前转换这些值到一个可JSON 序列化的对象)。 虽然串行化这些值是相当简单的(DjangoJSONEncoder可能是有帮助的),编写可以可靠地获取相同内容的解码器更加脆弱。 例如,返回一个datetime 时,它可能实际上是与datetime 格式碰巧相同的一个字符串)。

你的序列化类必须实现两个方法,dumps(self, obj) 和loads(self, data) 来分别序列化和去序列化会话数据的字典。

会话对象指南

  • request.session 上使用普通的Python 字符串作为字典的键。 这主要是为了方便而不是一条必须遵守的规则。
  • 以一个下划线开始的会话字典的键被Django保留作为内部使用。
  • 不要用新的对象覆盖request.session,且不要访问或设置它的属性。 要像Python 字典一样使用它。

实例

下面这个简单的视图在一个用户提交一个评论后设置has_commented 变量为True。 它不允许一个用户多次提交评论:

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

登录站点一个“成员”的最简单的视图:

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

...根据login(),这个用户登录一个成员:

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

标准的django.contrib.auth.logout() 函数实际上所做的内容比这个要多一点以防止意外的数据泄露。 它调用的request.sessionflush()方法。 我们使用这个例子来演示如何利用会话对象来工作,而不是一个完整的logout()实现。

设置测试cookie 

为了方便,Django 提供一个简单的方法来测试用户的浏览器是否接受Cookie。 只需在一个视图中调用request.sessionset_test_cookie()方法,并在接下来的视图中调用test_cookie_worked() —— 不是在同一个视图中调用。

由于Cookie的工作方式,在set_test_cookie() 和test_cookie_worked() 之间这种笨拙的分离是必要的。 当你设置一个Cookie,直到浏览器的下一个请求你不可能真实知道一个浏览器是否接受了它。

使用delete_test_cookie() 来自己清除测试的Cookie是一个很好的实践。 请在你已经验证测试的Cookie 已经工作后做这件事。

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

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

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

使用会议视图

 在视图的外面有一个API 可以使用来操作会话的数据:

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

SessionStore.create()旨在创建一个新的会话(即,一个没有从会话存储中加载,并且使用session_key=None)。 save()旨在保存现有会话(即从会话存储中加载的会话)。 在新会话中调用save()也可以正常工作,但生成与现有事件相冲突的session_key的几率很小。 create()调用save()循环,直到生成未使用的session_key

如果你使用的是django.contrib.sessions.backends.db 后端,每个会话只是一个普通的Django 模型。 Session 模型定义在django/contrib/sessions/models.py中。 因为它是一个普通的模型,你可以使用普通的Django 数据库API 来访问会话:

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

注意,你需要调用get_decoded() 以获得会话的字典。 这是必需的,因为字典是以编码后的格式保存的:

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

会话保存时

默认情况下,Django 只有在会话被修改时才会保存会话到数据库中 —— 即它的字典中的任何值被赋值或删除时:

# Session is modified.
request.session['foo'] = 'bar'

# Session is modified.
del request.session['foo']

# Session is modified.
request.session['foo'] = {}

# Gotcha: Session is NOT modified, because this alters
# request.session['foo'] instead of request.session.
request.session['foo']['bar'] = 'baz'

上面例子的最后一种情况,我们可以通过设置会话对象的modified属性显式地告诉会话对象它已经被修改过:

request.session.modified = True

若要修改这个默认的行为,可以设置 SESSION_SAVE_EVERY_REQUEST 为True。 当设置为True时,Django 将对每个请求保存会话到数据库中。

注意会话的Cookie 只有在一个会话被创建或修改后才会发送。 如果SESSION_SAVE_EVERY_REQUEST 为True,会话的Cookie 将在每个请求中发送。

类似地,会话Cookie 的expires 部分在每次发送会话Cookie 时更新。

如果响应的状态码时500,则会话不会被保存。

浏览器长度会话与持久会话

你可以通过SESSION_EXPIRE_AT_BROWSER_CLOSE设置来控制会话框架使用浏览器时长的会话,还是持久的会话。

默认情况下,SESSION_EXPIRE_AT_BROWSER_CLOSE设置为False,表示会话的Cookie 保存在用户的浏览器中的时间为SESSION_COOKIE_AGE。 如果你不想让大家每次打开浏览器时都需要登录时可以这样使用。

如果SESSION_EXPIRE_AT_BROWSER_CLOSE 设置为True,Django 将使用浏览器时长的Cookie —— 用户关闭他们的浏览器时立即过期。 如果你想让大家在每次打开浏览器时都需要登录时可以这样使用。

这个设置是一个全局的默认值,可以通过显式地调request.session 的set_expiry() 方法来覆盖,在上面的在视图中使用会话中有描述。

清除会话存储

随着用户在你的网站上创建新的会话,会话数据可能会在你的会话存储仓库中积累。 如果你正在使用数据库作为后端,django_session 数据库表将持续增长。 如果你正在使用文件作为后端,你的临时目录包含的文件数量将持续增长。

要理解这个问题,考虑一下数据库后端发生的情况。 当一个用户登入时,Django 添加一行到django_session 数据库表中。 每次会话数据更新时,Django 将更新这行。 如果用户手工登出,Django 将删除这行。 但是如果该用户登出,该行将永远不会删除。以文件为后端的过程类似。

Django 提供自动清除过期会话的功能。 因此,定期地清除会话是你的任务。 Django 提供一个清除用的管理命令来满足这个目的:clearsessions。 建议定期调用这个命令,例如作为一个每天运行的Cron 任务。

注意,以缓存为后端不存在这个问题,因为缓存会自动删除过期的数据。 以cookie 为后端也不存在这个问题,因为会话数据通过用户的浏览器保存。

设置

一些Django settings 让你可以控制会话的行为:

会话安全性

一个站点下的子域名能够在客户端为整个域名设置Cookie。 如果子域名不受信任的用户控制且允许来自子域名的Cookie,那么可能发生会话攻击。

例如,一个攻击者可以登录good.example.com并为他的账号获取一个合法的会话。 如果该攻击者具有bad.example.com的控制权,那么他可以使用这个域名来发送他的会话ID给你,因为子域名允许在*.example.com上设置Cookie。 当你访问good.example.com时,你将以攻击者身份登录且不会察觉到并输入你的敏感的个人信息(例如,信用卡信息)到攻击者的账号中。

另外一个可能的攻击是,如果bad.example.com设置它的 SESSION_COOKIE_DOMAIN 为good.example.com ,这将导致来自该站点的会话Cookie 被发送到".example.com"

 信号

 Django 提供一个“信号分发器”,允许解耦的应用在框架的其它地方发生操作时会被通知到。 简单来说,信号允许特定的sender通知一组receiver某些操作已经发生。 这在多处代码和同一事件有关联的情况下很有用。

Django提供一组内建的信号,允许用户的代码获得Django特定操作的通知。 它们包含一些有用的通知:

监听信号

要接收信号,请使用Signal.connect()方法注册receiver函数。 receiver函数在信号发送时调用。

Signal.connect(receiversender=Noneweak=Truedispatch_uid=None)[source]

 参数:

receiver – 和这个信号连接的回调函数。 详见Receiver函数。
sender – 指定一个特定的sender,来从它那里接受信号。 详见连接到特定发送者发送的信号。
weak – Django通常以弱引用储存信号处理器。 这就是说,如果你的receiver是个局部变量,可能会被垃圾回收。 
若要防止这个行为,当你调用信号的connect()方法时,请传递weak
=False。 dispatch_uid – 一个信号接收者的唯一标识符,以防信号多次发送。 详见防止重复的信号。

通过注册一个在每次HTTP请求结束时调用的信号,让我们来看一看它是如何工作的。 我们将会连接到request_finished 信号。

Receiver函数

首先,我们需要定义receiver函数。 Receiver可以是任何Python函数或者方法:

def my_callback(sender, **kwargs):
    print("Request finished!")

请注意,该函数接受一个sender参数,以及通配符关键字参数(**kwargs);所有信号处理程序都必须接受这些参数。

我们过一会儿再关注sender,现在先看一看**kwargs参数。 所有信号都发送关键字参数,并且可以在任何时候修改这些关键字参数。 对于request_finished,它的文档描述是不发送任何参数,这意味着我们似乎可以将我们自己的信号处理器编写成my_callback(sender)

这是错误的 -- 实际上,如果你这么做了,Django 会抛出异常。 这是因为信号在任何时候都可能添加参数,你的receiver 必须能够处理这些新的参数。

连接receiver函数

有两种方法可以将一个receiver连接到信号。 你可以采用手动连接的方法:

from django.core.signals import request_finished

request_finished.connect(my_callback)

或者使用receiver() 装饰器来自动连接:

receiver(signal)[source]
参数:    signal - 函数将要连接到的信号或信号列表。

下面是使用装饰器连接的方法:

from django.core.signals import request_finished
from django.dispatch import receiver

@receiver(request_finished)
def my_callback(sender, **kwargs):
    print("Request finished!")

现在,我们的my_callback函数会在每次请求结束时调用。

这段代码应该放在哪里?

严格来说,信号处理和注册的代码应该放在你想要的任何地方,但是推荐避免放在应用的根模块和models模块中,以尽量减少产生导入代码的副作用。

实际上,信号处理函数通常定义在应用相关的signals子模块中。 信号receiver在你应用配置类中的ready()方法中连接。 如果你使用receiver()装饰器,只需要在ready()内部导入signals子模块就可以

连接由特定sender发送的信号

一些信号会发送多次,但是你只想接收这些信号的一个确定的子集。 例如,考虑 django.db.models.signals.pre_save 信号,它在模型保存之前发送。 大多数情况下,你并不需要知道所有模型何时保存 -- 只需要知道一个特定的模型何时保存。

在这些情况下,你可以通过注册来接收只由特定sender 发出的信号。 对于django.db.models.signals.pre_save的情况, sender 是被保存的模型类,所以你可以认为你只需要由某些模型发出的信号:

from django.db.models.signals import pre_save
from django.dispatch import receiver
from myapp.models import MyModel


@receiver(pre_save, sender=MyModel)
def my_handler(sender, **kwargs):
    ...

my_handler函数只在MyModel实例保存时被调用。

不同的信号使用不同的对象作为sender;有关每个特定信号的详细信息,你需要参考内建信号的文档

防止重复信号

在一些情况下,连接receiver 到信号的代码可能会执行多次。 这可能会导致receiver函数被多次注册,因此对于单个信号事件多次进行调用。

如果这样的行为会导致问题(例如在任何时候模型保存时使用信号来发送邮件),可以传递一个唯一的标识符作为dispatch_uid 参数来标识你的receiver 函数。 标识符通常是一个字符串,虽然任何可计算哈希的对象都可以。 最后的结果是,对于每个唯一的dispatch_uid值,你的receiver 函数都只绑定到信号一次:

from django.core.signals import request_finished

request_finished.connect(my_callback, dispatch_uid="my_unique_identifier")

定义和发送信号

你的应用可以利用信号功能来提供自己的信号。

定义信号

class Signal(providing_args=list)[source]

所有信号都是 django.dispatch.Signal 的实例。 providing_args是一个列表,由信号将提供给监听者的参数名称组成。 理论上是这样,但是实际上并没有任何检查来保证向监听者提供了这些参数。

像这样:

import django.dispatch

pizza_done = django.dispatch.Signal(providing_args=["toppings", "size"])

这段代码声明了pizza_done信号,它向接受者提供size和 toppings 参数。

要记住你可以在任何时候修改参数的列表,所以首次尝试的时候不需要完全确定API。

发送信号

Django中有两种方法用于发送信号。

Signal.send(sender**kwargs)[source]
Signal.send_robustsender** kwargs[source] 

要发送信号,请调用Signal.send()(所有内置信号都使用它)或Signal.send_robust()。 你必须提供sender参数(大部分时候是一个类),并且可以提供任意数量的其他关键字参数。

例如,这样来发送我们的pizza_done信号:

class PizzaStore(object):
    ...

    def send_pizza(self, toppings, size):
        pizza_done.send(sender=self.__class__, toppings=toppings, size=size)
        ...

send()send_robust()返回一个元组对的列表 [(receiver, response), ... ],表示被调用的receiver函数及其响应值的列表。

send_robust() 与 send()在处理receiver 函数时产生的异常有所不同。 send()不会捕获receiver产生的异常;它只是让错误向上传播。 所以在错误产生的情况,不是所有receiver都会获得通知。

send_robust捕获所有继承自Python ()Exception类的异常,并且确保所有receiver都能得到信号的通知。 如果发生错误,错误实例会在产生错误的receiver 的二元组中返回。

调用send_robust()的时候,所返回的错误的__traceback__属性上会带有 traceback。

断开信号

Signal.disconnect(receiver=Nonesender=Nonedispatch_uid=None)[source]

调用Signal.disconnect()来断开信号的接收器。 Signal.connect()中描述的参数相同。 如果接收器成功断开,返回 True ,否则返回False

receiver 参数表示要断开的已注册receiver。 如果使用dispatch_uid 标识receiver,它可能为None

自1.9版以来已弃用: weak参数已被弃用,因为它不起作用。 它将在Django 2.0中被删除。

 

 

posted on 2018-03-12 12:10  zhang_derek  阅读(1399)  评论(0编辑  收藏  举报

导航