返回顶部
扩大
缩小

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