diff --git a/13_1_2_Using the authentication system.md b/13_1_2_Using the authentication system_.md similarity index 100% rename from 13_1_2_Using the authentication system.md rename to 13_1_2_Using the authentication system_.md diff --git a/13_7_Messages framework_.md b/13_7_Messages framework_.md new file mode 100644 index 0000000000000000000000000000000000000000..16eed49bd49fbd2c40cb7da320e3491bbd70ab1b --- /dev/null +++ b/13_7_Messages framework_.md @@ -0,0 +1,616 @@ + + +# 消息框架 + +在网页应用中,你经常需要在处理完表单或其它类型的用户输入后,显示一个通知消息(也叫做“flash message”)给用户。 + +对于这个功能,Django 提供基于Cookie 和会话的消息,无论是匿名用户还是认证的用户。其消息框架允许你临时将消息存储在请求中,并在接下来的请求(通常就是下一个请求)中提取它们并显示。每个消息都带有一个特定`level` 标签,表示其优先级(例如`info`、`warning` 或`error`)。 + + + +## 启用消息框架 + +消息框架的实现通过一个[_中间件_](../middleware.html) 类和对应的[_context processor_](../templates/api.html)。 + +`django-admin startproject` 创建的默认`settings.py` ?已经包含启用消息框架功能需要的所有的设置: + +* [`INSTALLED_APPS`](../settings.html#std:setting-INSTALLED_APPS) 中的`'django.contrib.messages'`。 + +* [`MIDDLEWARE_CLASSES`](../settings.html#std:setting-MIDDLEWARE_CLASSES) 中的`'django.contrib.sessions.middleware.SessionMiddleware'` 和 `'django.contrib.messages.middleware.MessageMiddleware'`。 + + 默认的[_后端存储_](#message-storage-backends) 依赖[_sessions_](../../topics/http/sessions.html)。所以[`MIDDLEWARE_CLASSES`](../settings.html#std:setting-MIDDLEWARE_CLASSES) 中必须启用`SessionMiddleware` 并出现在`MessageMiddleware` 之前。 + +* [`TEMPLATES`](../settings.html#std:setting-TEMPLATES) 设置中定义的`DjangoTemplates` 的`'context_processors'` 选项包含 `'django.contrib.messages.context_processors.messages'`。 + +如果你不想使用消息框架,你可以删除[`INSTALLED_APPS`](../settings.html#std:setting-INSTALLED_APPS) 中的 `'django.contrib.messages'`、[`MIDDLEWARE_CLASSES`](../settings.html#std:setting-MIDDLEWARE_CLASSES) 中的`MessageMiddleware` 和[`TEMPLATES`](../settings.html#std:setting-TEMPLATES) 中的`messages` context processor。 + + + + + +## 配置消息框架引擎 + + + +### 后台存储 + +消息框架可以使用不同的后台存储临时消息。 + +Django 在[`django.contrib.messages`](#module-django.contrib.messages "django.contrib.messages: Provides cookie- and session-based temporary message storage.") 中提供三个内建的存储类: + + + +_class_ `storage.session.SessionStorage` + + + +这个类存储所有的消息于请求的会话中。因此,它要求启用Django 的`contrib.sessions` 应用。 + + + + + + + +_class_ `storage.cookie.CookieStorage` + + + +这个类存储消息数据于与Cookie 中(已经用一个安全的哈希进行签名以防止篡改)以在请求之间传递消息。如果Cookie 数据的大小将超过2048 字节,将丢弃旧的消息。 + + + + + + + +_class_ `storage.fallback.FallbackStorage` + + + +这个类首先使用`CookieStorage`,如果消息塞不进一个Cookie 中则使用`SessionStorage`。 它同样要求启用Django 的`contrib.sessions` 应用。 + +这个行为避免每次都写会话。在通常情况下,它提供的性能应该是最好的。 + + + + + +[`FallbackStorage`](#django.contrib.messages.storage.fallback.FallbackStorage "django.contrib.messages.storage.fallback.FallbackStorage") 是默认的存储类。如果它不适合你的需要,你可以通过设置 [`MESSAGE_STORAGE`](../settings.html#std:setting-MESSAGE_STORAGE) 为它的完整导入路径选择另外一个存储类,例如: + + + + + +``` +MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage' + +``` + + + + + + + +_class_ `storage.base.BaseStorage` + + + +如果想编写你自己的存储类,子类化`django.contrib.messages.storage.base` 中的`BaseStorage` 类并实现`_get` 和 `_store` 方法。 + + + + + +### 消息级别 + +消息框架的级别是可配置的,与Python logging 模块类似。消息的级别可以让你根据类型进行分组,这样它们能够在不同的视图和模板中过滤或显示出来。 + +可以直接从`django.contrib.messages` 导入的内建级别有: + + +| Constant | Purpose | +| --- | --- | +| `DEBUG` | Development-related messages that will be ignored (or removed) in a production deployment | +| `INFO` | Informational messages for the user | +| `SUCCESS` | An action was successful, e.g. “Your profile was updated successfully” | +| `WARNING` | A failure did not occur but may be imminent | +| `ERROR` | An action was **not** successful or some other failure occurred | + +[`MESSAGE_LEVEL`](../settings.html#std:setting-MESSAGE_LEVEL) 设置可以用来改变记录的最小级别(它还可以[在每个请求中修改](#changing-the-minimum-recorded-level-per-request))。小于这个级别的消息将被忽略。 + + + + + +### 消息的标签 + +消息的标签是一个字符串,表示消息的级别以及在视图中添加的其它标签(参见下文[添加额外的消息标签](#adding-extra-message-tags))。标签存储在字符串中并通过空格分隔。通常情况下,消息的标签用于作为CSS 类来根据消息的类型定制消息的风格。默认情况下,每个级别具有一个标签,为其级别的字符串常量的小写: + + +| Level Constant | Tag | +| --- | --- | +| `DEBUG` | `debug` | +| `INFO` | `info` | +| `SUCCESS` | `success` | +| `WARNING` | `warning` | +| `ERROR` | `error` | + +若要修改消息级别的默认标签,设置[`MESSAGE_TAGS`](../settings.html#std:setting-MESSAGE_TAGS)为包含你想要修改的级别的字典。As this extends the default tags, you only need to provide tags for the levels you wish to override: + + + + + +``` +from django.contrib.messages import constants as messages +MESSAGE_TAGS = { + messages.INFO: '', + 50: 'critical', +} + +``` + + + + + + + + + + + +## 在视图和模板中使用消息 + + + +`add_message`(_request_, _level_, _message_, _extra_tags=''_, _fail_silently=False_) + + + + + +### 新增一条消息 + +新增一条消息,调用: + + + + + +``` +from django.contrib import messages +messages.add_message(request, messages.INFO, 'Hello world.') + +``` + + + + + +有几个快捷方法提供标准的方式来新增消息并带有常见的标签(这些标签通常表示消息的HTML 类型): + + + + + +``` +messages.debug(request, '%s SQL statements were executed.' % count) +messages.info(request, 'Three credits remain in your account.') +messages.success(request, 'Profile details updated.') +messages.warning(request, 'Your account expires in three days.') +messages.error(request, 'Document deleted.') + +``` + + + + + + + + + +### 显示消息 + + + +`get_messages`(_request_) + + + +**在你的模板中**,像下面这样使用: + + + + + +``` +{% if messages %} + +{% endif %} + +``` + + + + + +如果你正在使用context processor,你的模板应该通过 `RequestContext` 渲染。否则,需要确保`messages` 在模板的Context 中可以访问。 + +即使你知道只有一条消息,你也应该仍然迭代`messages` 序列,否则下个请求中的消息不会被清除。 + +New in Django 1.7. + +Context processor 还提供一个`DEFAULT_MESSAGE_LEVELS` 变量,它映射消息级别的名称到它们的数值: + + + + + +``` +{% if messages %} + +{% endif %} + +``` + + + + + +**在模板的外面**,你可以使用[`get_messages()`](#django.contrib.messages.get_messages "django.contrib.messages.get_messages"): + + + + + +``` +from django.contrib.messages import get_messages + +storage = get_messages(request) +for message in storage: + do_something_with_the_message(message) + +``` + + + + + +例如,你可以获取所有的消息并在[_JSONResponseMixin_](../../topics/class-based-views/mixins.html#jsonresponsemixin-example) 而不是[`TemplateResponseMixin`](../class-based-views/mixins-simple.html#django.views.generic.base.TemplateResponseMixin "django.views.generic.base.TemplateResponseMixin") 中返回它们。 + +[`get_messages()`](#django.contrib.messages.get_messages "django.contrib.messages.get_messages") 将返回配置的存储后台的一个实例。 + + + + + +### The `Message` + + + +_class_ `storage.base.Message` + + + +当浏览某个模板的消息列表时,你得到的其实是`Message` 类的实例。它只是一个非常简单、只带很少属性的对象: + +* `message`: 消息的实际内容文本。 +* `level`: 一个整数,它描述了消息的类型 (请参阅上面 [message levels](#message-levels)一节). +* `tags`: 一个字符串,它由该消息的所有标签 (`extra_tags` 和`level_tag`)组合而成,组合时用空格分割开这些标签。 +* `extra_tags`: 一个字符串,它由该消息的定制标签组合而成,并用空格分割。缺省为空。 + +New in Django 1.7. + +* `level_tag`: 代表该消息级别的字符串。该属性缺省由小写的关联常数名组成, 但当设置[`MESSAGE_TAGS`](../settings.html#std:setting-MESSAGE_TAGS)参数时,可改变该规则。 + + + + + + + + + +### Creating custom message levels + +Messages levels are nothing more than integers, so you can define your own level constants and use them to create more customized user feedback, e.g.: + + + + + +``` +CRITICAL = 50 + +def my_view(request): + messages.add_message(request, CRITICAL, 'A serious error occurred.') + +``` + + + + + +When creating custom message levels you should be careful to avoid overloading existing levels. The values for the built-in levels are: + + +| Level Constant | Value | +| --- | --- | +| `DEBUG` | 10 | +| `INFO` | 20 | +| `SUCCESS` | 25 | +| `WARNING` | 30 | +| `ERROR` | 40 | + +If you need to identify the custom levels in your HTML or CSS, you need to provide a mapping via the [`MESSAGE_TAGS`](../settings.html#std:setting-MESSAGE_TAGS) setting. + + + +Note + +If you are creating a reusable application, it is recommended to use only the built-in [message levels](#message-levels) and not rely on any custom levels. + + + + + + + +### 在每个请求中修改最小的记录级别 + +每个请求都可以通过 `set_level`方法设置最小记录级别: + + + + + +``` +from django.contrib import messages + +# Change the messages level to ensure the debug message is added. +messages.set_level(request, messages.DEBUG) +messages.debug(request, 'Test message...') + +# In another request, record only messages with a level of WARNING and higher +messages.set_level(request, messages.WARNING) +messages.success(request, 'Your profile was updated.') # ignored +messages.warning(request, 'Your account is about to expire.') # recorded + +# Set the messages level back to default. +messages.set_level(request, None) + +``` + + + + + +与此相似,当前有效的记录级别可以用`get_level`方法获取: + + + + + +``` +from django.contrib import messages +current_level = messages.get_level(request) + +``` + + + + + +有关最小记录级别相关的函数信息,请参阅上面 [Message levels](#message-levels) 一节. + + + + + +### Adding extra message tags + +For more direct control over message tags, you can optionally provide a string containing extra tags to any of the add methods: + + + + + +``` +messages.add_message(request, messages.INFO, 'Over 9000!', + extra_tags='dragonball') +messages.error(request, 'Email box full', extra_tags='email') + +``` + + + + + +Extra tags are added before the default tag for that level and are space separated. + + + + + +### 当消息框架被禁止时,失败静悄悄 + +If you’re writing a reusable app (or other piece of code) and want to include messaging functionality, but don’t want to require your users to enable it if they don’t want to, you may pass an additional keyword argument `fail_silently=True` to any of the `add_message` family of methods. 举个例子 + + + + + +``` +messages.add_message(request, messages.SUCCESS, 'Profile details updated.', + fail_silently=True) +messages.info(request, 'Hello world.', fail_silently=True) + +``` + + + + + + + +Note + +Setting `fail_silently=True` only hides the `MessageFailure` that would otherwise occur when the messages framework disabled and one attempts to use one of the `add_message` family of methods. It does not hide failures that may occur for other reasons. + + + + + + + +### 在基于类的视图中添加消息 + + + +_class_ `views.SuccessMessageMixin` + + + +向基于[`FormView`](../class-based-views/generic-editing.html#django.views.generic.edit.FormView "django.views.generic.edit.FormView") 的类添加一条成功的消息 + + + +`get_success_message`(_cleaned_data_) + + + +`cleaned_data` 是表单中的清洁数据,用于字符串格式化 + + + + + + + + + +**示例 views.py**: + + + + + +``` +from django.contrib.messages.views import SuccessMessageMixin +from django.views.generic.edit import CreateView +from myapp.models import Author + +class AuthorCreate(SuccessMessageMixin, CreateView): + model = Author + success_url = '/success/' + success_message = "%(name)s was created successfully" + +``` + + + + + +字符串插值可以使用`%(field_name)s` 语法访问`form` 中的清洁数据。对于ModelForms,如果你需要访问保存的`object` 中的字段,可以覆盖[`get_success_message()`](#django.contrib.messages.views.SuccessMessageMixin.get_success_message "django.contrib.messages.views.SuccessMessageMixin.get_success_message") 方法。 + +**ModelForms 的示例views.py**: + + + + + +``` +from django.contrib.messages.views import SuccessMessageMixin +from django.views.generic.edit import CreateView +from myapp.models import ComplicatedModel + +class ComplicatedCreate(SuccessMessageMixin, CreateView): + model = ComplicatedModel + success_url = '/success/' + success_message = "%(calculated_field)s was created successfully" + + def get_success_message(self, cleaned_data): + return self.success_message % dict(cleaned_data, + calculated_field=self.object.calculated_field) + +``` + + + + + + + + + + + +## Expiration of messages + +The messages are marked to be cleared when the storage instance is iterated (and cleared when the response is processed). + +To avoid the messages being cleared, you can set the messages storage to `False` after iterating: + + + + + +``` +storage = messages.get_messages(request) +for message in storage: + do_something_with(message) +storage.used = False + +``` + + + + + + + + + +## Behavior of parallel requests + +Due to the way cookies (and hence sessions) work, **the behavior of any backends that make use of cookies or sessions is undefined when the same client makes multiple requests that set or get messages in parallel**. For example, if a client initiates a request that creates a message in one window (or tab) and then another that fetches any uniterated messages in another window, before the first window redirects, the message may appear in the second window instead of the first window where it may be expected. + +In short, when multiple simultaneous requests from the same client are involved, messages are not guaranteed to be delivered to the same window that created them nor, in some cases, at all. Note that this is typically not a problem in most applications and will become a non-issue in HTML5, where each window/tab will have its own browsing context. + + + + + +## 设置 + +A few [_settings_](../settings.html#settings-messages) give you control over message behavior: + +* [MESSAGE_LEVEL](../settings.html#std:setting-MESSAGE_LEVEL) +* [MESSAGE_STORAGE](../settings.html#std:setting-MESSAGE_STORAGE) +* [MESSAGE_TAGS](../settings.html#std:setting-MESSAGE_TAGS) + +New in Django 1.7. + +For backends that use cookies, the settings for the cookie are taken from the session cookie settings: + +* [SESSION_COOKIE_DOMAIN](../settings.html#std:setting-SESSION_COOKIE_DOMAIN) +* [SESSION_COOKIE_SECURE](../settings.html#std:setting-SESSION_COOKIE_SECURE) +* [SESSION_COOKIE_HTTPONLY](../settings.html#std:setting-SESSION_COOKIE_HTTPONLY) + + + diff --git a/14_7_The sites framework_.md b/14_7_The sites framework_.md new file mode 100644 index 0000000000000000000000000000000000000000..c74cea68e9cd520d77e38fdffee3f082cb13095b --- /dev/null +++ b/14_7_The sites framework_.md @@ -0,0 +1,601 @@ + + +# The “sites” framework + +Django 原生带有一个可选的“sites”框架。它是一个钩子,用于将对象和功能与特定的站点关联,它同时还是域名和你的Django 站点名称之间的对应关系所保存的地方。 + +如果你的Django 不只为一个站点提供支持,而且你需要区分这些不同的站点,你就可以使用它。 + +Sites 框架主要依据一个简单的模型: + + + +_class_ `models.Site` + + + +用来存储Web站点的`domain` ?和`name` 属性的模型 + + + +`domain` + + + +与Web站点关联的域名。 + + + + + + + +`name` + + + +Web 站点的名称。 + + + + + + + + + +[`SITE_ID`](../settings.html#std:setting-SITE_ID) 设置指定与特定的设置文件关联的[`Site`](#django.contrib.sites.models.Site "django.contrib.sites.models.Site") 对象在数据库中ID。如果省略该设置,[`get_current_site()`](#django.contrib.sites.shortcuts.get_current_site "django.contrib.sites.shortcuts.get_current_site") 函数将会通过比较[`domain`](#django.contrib.sites.models.Site.domain "django.contrib.sites.models.Site.domain") 与[`request.get_host()`](../request-response.html#django.http.HttpRequest.get_host "django.http.HttpRequest.get_host") 方法中得到的主机名,来得到当前的Site。 + +怎样使用取决于你,但是django自动的在几个方面通过一些简单的约定使用它。 + + + +## 示例 + +为什么要使用Sites 框架?通过例子能最好的解释。 + + + +### 关联内容到多个站点 + +通过Django开发的站点[LJWorld.com](http://www.ljworld.com/) 和[Lawrence.com](http://www.lawrence.com/) 是位于Lawrence, Kansas 的同一家机构Lawrence Journal-World newspaper 运营的。LJWorld.com 关注新闻,而Lawrence.com 关注当地的环境问题。但是有时编辑需要发布同一篇文章到_两个_站点。 + +无脑的解决方法是要求站点发布者发布同一内容两次:一次到LJWorld.com,一次到 Lawrence.com。但这是很低效的行为,而且在数据库中必须存储同一内容很多次(多副本存储,浪费资源)。 + +最好的解决方法很简单:两个站点用相同的文章数据库,一篇文章可以关联一个或者多个站点。用Django 模型的术语,它通过`Article` 模型的一个[`多对多字段`](../models/fields.html#django.db.models.ManyToManyField "django.db.models.ManyToManyField")表示: + + + + + +``` +from django.db import models +from django.contrib.sites.models import Site + +class Article(models.Model): + headline = models.CharField(max_length=200) + # ... + sites = models.ManyToManyField(Site) + +``` + + + + + +这很快很好的完成了几件事: + +* 它使得站点编辑者利用一个接口(Django admin)编辑多站点上的所有内容。 + +* 它意味着同一个内容不用往数据库存入两次;在数据库中仅仅只有一条记录。 + +* 对于两个站点,开发者可以使用相同的Django 视图代码。显示内容的视图代码需要检查,以确保请求的内容属于当前的站点。就像下面一样: + + + + + + ``` +from django.contrib.sites.shortcuts import get_current_site + + def article_detail(request, article_id): + try: + a = Article.objects.get(id=article_id, sites__id=get_current_site(request).id) + except Article.DoesNotExist: + raise Http404("Article does not exist on this site") + # ... + +``` + + + + + + + + + +### 关联内容到单独的站点 + +类似地,你可以用[`ForeignKey`](../models/fields.html#django.db.models.ForeignKey "django.db.models.ForeignKey") 关联一个模型到[`Site`](#django.contrib.sites.models.Site "django.contrib.sites.models.Site") 模型实现多对一关系。 + +例如,一篇文章只允许在一个单独的站点,你应该像这样用模型: + + + + + +``` +from django.db import models +from django.contrib.sites.models import Site + +class Article(models.Model): + headline = models.CharField(max_length=200) + # ... + site = models.ForeignKey(Site) + +``` + + + + + +这个好处和上节描述的好处是相同的。 + + + + + +### 在视图中获得当前的Site + +你可以在Django 视图中使用Sites 框架基于正在调用的视图所在的Site 实现特定的功能。例如: + + + + + +``` +from django.conf import settings + +def my_view(request): + if settings.SITE_ID == 3: + # Do something. + pass + else: + # Do something else. + pass + +``` + + + + + +当然,这样硬编码Site ID 比较丑陋。这种硬编码是你最需要尽快修复的。完成这件事情的更清洁的方法是检查当前站点的域名: + + + + + +``` +from django.contrib.sites.shortcuts import get_current_site + +def my_view(request): + current_site = get_current_site(request) + if current_site.domain == 'foo.com': + # Do something + pass + else: + # Do something else. + pass + +``` + + + + + +它还有一个优点是检查Sites 框架是否安装,如果没有安装将返回一个 [`RequestSite`](#django.contrib.sites.requests.RequestSite "django.contrib.sites.requests.RequestSite") 实例。 + +如果你不能访问request 对象,你可以使用[`Site`](#django.contrib.sites.models.Site "django.contrib.sites.models.Site") 模型管理器的`get_current()` 方法。此时,你需要确保你的设置文件包含[`SITE_ID`](../settings.html#std:setting-SITE_ID) 设置。下面的示例与前面的示例等同: + + + + + +``` +from django.contrib.sites.models import Site + +def my_function_without_request(): + current_site = Site.objects.get_current() + if current_site.domain == 'foo.com': + # Do something + pass + else: + # Do something else. + pass + +``` + + + + + + + + + +### 显示当前的域名 + +LJWorld.com 和Lawrence.com 都具有邮件通知功能,它让读者注册以在新闻发生时获得通知。这很简单:读者通过网页表单注册,然后立即收到一封邮件说 “感谢您的订阅”。 + +将这个注册过程的代码实现两次是低效而冗余的,所以这两个站点在后台使用相同的代码。但是每个Site 的“感谢您的订阅”的通知需要不同。通过使用[`Site`](#django.contrib.sites.models.Site "django.contrib.sites.models.Site") 对象,我们可以抽象这个通知并利用当前Site 的[`name`](#django.contrib.sites.models.Site.name "django.contrib.sites.models.Site.name") 和[`domain`](#django.contrib.sites.models.Site.domain "django.contrib.sites.models.Site.domain") 的值。 + +下面是该表单处理视图的一个例子: + + + + + +``` +from django.contrib.sites.shortcuts import get_current_site +from django.core.mail import send_mail + +def register_for_newsletter(request): + # Check form values, etc., and subscribe the user. + # ... + + current_site = get_current_site(request) + send_mail('Thanks for subscribing to %s alerts' % current_site.name, + 'Thanks for your subscription. We appreciate it.\n\n-The %s team.' % current_site.name, + 'editor@%s' % current_site.domain, + [user.email]) + + # ... + +``` + + + + + +在Lawrence.com 网站上,这封邮件的标题为“Thanks for subscribing to lawrence.com alerts.”。在LJWorld.com 网站上,这封邮件的标题为“Thanks for subscribing to LJWorld.com alerts.”。邮件体的行为相同。 + +注意,更加灵活(但是更沉重)的方法是使用Django 的模板系统。假设Lawrence.com 和LJWorld.com 具有不同的模板目录([`DIRS`](../settings.html#std:setting-TEMPLATES-DIRS)),你可以很容易地根据模板系统写出: + + + + + +``` +from django.core.mail import send_mail +from django.template import loader, Context + +def register_for_newsletter(request): + # Check form values, etc., and subscribe the user. + # ... + + subject = loader.get_template('alerts/subject.txt').render(Context({})) + message = loader.get_template('alerts/message.txt').render(Context({})) + send_mail(subject, message, 'editor@ljworld.com', [user.email]) + + # ... + +``` + + + + + +在这种情况下,你必须为LJWorld.com 和Lawrence.com 模板目录都创建`subject.txt` 和`message.txt` 模板文件。它更灵活,但是也更复杂。 + +尽可能地发掘[`Site`](#django.contrib.sites.models.Site "django.contrib.sites.models.Site") 对象的用法以删除不需要的复杂性和冗余是个不错的主意。 + + + + + +### 获取当前域名的url全路径 + +Django 的`get_absolute_url()` 可以很方便地获得对象不带域名的URL,但是某些情况下,你可能想显示完整的URL,带有`http://`和域名以及其它部分。要实现这点,你可以使用Sites 框架。一个简单的示例: + + + + + +``` +>>> from django.contrib.sites.models import Site +>>> obj = MyModel.objects.get(id=3) +>>> obj.get_absolute_url() +'/mymodel/objects/3/' +>>> Site.objects.get_current().domain +'example.com' +>>> 'http://%s%s' % (Site.objects.get_current().domain, obj.get_absolute_url()) +'http://example.com/mymodel/objects/3/' + +``` + + + + + + + + + + + +## 启用Sites 框架 + +按照以下步骤启用Sites 框架: + +1. 添加`'django.contrib.sites'` 到你的[`INSTALLED_APPS`](../settings.html#std:setting-INSTALLED_APPS) 设置中。 + +2. 定义[`SITE_ID`](../settings.html#std:setting-SITE_ID) 设置: + + + + + + ``` +SITE_ID = 1 + +``` + + + + + +3. 运行[`migrate`](../django-admin.html#django-admin-migrate)。 + +`django.contrib.sites` 注册一个[`post_migrate`](../signals.html#django.db.models.signals.post_migrate "django.db.models.signals.post_migrate") 信号处理器,它创建一个默认的Site`example.com`,其域名为`example.com`。在Django 创建测试数据库之后,也会创建该Site。你可以使用[_数据迁移_](../../topics/migrations.html#data-migrations)来为你的项目设置正确的name 和domain。 + +为了在线上环境中启用多个Site,你应该为每个`SITE_ID` 创建一个单独的设置文件(可以从一个共同的设置文件导入,以避免重复共享的配置),然后为每个Site 指定合适的[`DJANGO_SETTINGS_MODULE`](../../topics/settings.html#envvar-DJANGO_SETTINGS_MODULE)。 + + + + + +## Caching the current `Site` + +因为当前站点储存在数据库,每一次调用 `Site.objects.get_current()`都会导致数据库查询。但是Django还是比这个聪明滴, 当前站点被放在缓存当中了, 所以后续的调用返回的都是缓存的数据而不是直接查询数据库。 + +如果出于一些原因你想要强制用数据库查询, 你可以告诉Django清除缓存,用下面这个方法 `Site.objects.clear_cache()`: + + + + + +``` +# First call; current site fetched from database. +current_site = Site.objects.get_current() +# ... + +# Second call; current site fetched from cache. +current_site = Site.objects.get_current() +# ... + +# Force a database query for the third call. +Site.objects.clear_cache() +current_site = Site.objects.get_current() + +``` + + + + + + + + + +## The `CurrentSiteManager` + + + +_class_ `managers.CurrentSiteManager` + + + +如果 [`Site`](#django.contrib.sites.models.Site "django.contrib.sites.models.Site") 在你的应用中非常的关键, 你可以考虑用 [`CurrentSiteManager`](#django.contrib.sites.managers.CurrentSiteManager "django.contrib.sites.managers.CurrentSiteManager") 在你的模型中(s). 它是一个 model[_manager_](../../topics/db/managers.html)用来自动过滤,留下只与当前站点有关的数据查询 [`Site`](#django.contrib.sites.models.Site "django.contrib.sites.models.Site"). + + + +Mandatory [`SITE_ID`](../settings.html#std:setting-SITE_ID) + +`CurrentSiteManager` 只有在你定义了[`SITE_ID`](../settings.html#std:setting-SITE_ID) 在setting 中才起作用。 + + + +使用 [`CurrentSiteManager`](#django.contrib.sites.managers.CurrentSiteManager "django.contrib.sites.managers.CurrentSiteManager") ,你只要直接把他添加到你的model 中。For example: + + + + + +``` +from django.db import models +from django.contrib.sites.models import Site +from django.contrib.sites.managers import CurrentSiteManager + +class Photo(models.Model): + photo = models.FileField(upload_to='/home/photos') + photographer_name = models.CharField(max_length=100) + pub_date = models.DateField() + site = models.ForeignKey(Site) + objects = models.Manager() + on_site = CurrentSiteManager() + +``` + + + + + +通过这个model, `Photo.objects.all()` 将会返回所有在数据库中的 `Photo`对象,但是 `Photo.on_site.all()`只会返回 与当前site相关的`Photo`对象, 这是根据 [`SITE_ID`](../settings.html#std:setting-SITE_ID) 在setting的设置。 + +换句话说,这两种表达方式是等价的: + + + + + +``` +Photo.objects.filter(site=settings.SITE_ID) +Photo.on_site.all() + +``` + + + + + +[`CurrentSiteManager`](#django.contrib.sites.managers.CurrentSiteManager "django.contrib.sites.managers.CurrentSiteManager")是如何知道哪个`Photo`字段是 [`Site`](#django.contrib.sites.models.Site "django.contrib.sites.models.Site")的? 通常来说, [`CurrentSiteManager`](#django.contrib.sites.managers.CurrentSiteManager "django.contrib.sites.managers.CurrentSiteManager")查找一个 [`ForeignKey`](../models/fields.html#django.db.models.ForeignKey "django.db.models.ForeignKey") 它的名字叫`site` 或者是一个 [`ManyToManyField`](../models/fields.html#django.db.models.ManyToManyField "django.db.models.ManyToManyField")字段 ,叫做 `sites`来筛选出. 如果你用名字不叫`site` or `sites`的字段来表示一个与[`Site`](#django.contrib.sites.models.Site "django.contrib.sites.models.Site")对象相关联,,那么你就需要在你的model中显示得传递自定义的字段名给[`CurrentSiteManager`](#django.contrib.sites.managers.CurrentSiteManager "django.contrib.sites.managers.CurrentSiteManager")。下面的model, 它有一个字段叫做 `publish_on`, 说明了这个问题: + + + + + +``` +from django.db import models +from django.contrib.sites.models import Site +from django.contrib.sites.managers import CurrentSiteManager + +class Photo(models.Model): + photo = models.FileField(upload_to='/home/photos') + photographer_name = models.CharField(max_length=100) + pub_date = models.DateField() + publish_on = models.ForeignKey(Site) + objects = models.Manager() + on_site = CurrentSiteManager('publish_on') + +``` + + + + + +如果你尝试使用[`CurrentSiteManager`](#django.contrib.sites.managers.CurrentSiteManager "django.contrib.sites.managers.CurrentSiteManager") 并且传递了一个并不存在的字段名称给他, Django 就会引发一个 `ValueError`. + +最后, 注意你可能会想要保持一个正常的 (non-site-specific) `Manager` 在你的model, 虽然你使用了 [`CurrentSiteManager`](#django.contrib.sites.managers.CurrentSiteManager "django.contrib.sites.managers.CurrentSiteManager"). 就像 [_manager documentation_](../../topics/db/managers.html)当中的解释那样,如果你手动定义了一个manager,Django是不会为你自动创建 `objects = models.`Manager() manager。也请注意某些 Django组件 –即, Django admin site 和通用视图– 使用的是 _first_定义 在你model中的manager,所以如果你希望你的admin site可以连接到所有对象 (不仅仅是特定的站点对象), 那就设置 `objects = models.`Manager() 在你的 model中, 并且在你定义[`CurrentSiteManager`](#django.contrib.sites.managers.CurrentSiteManager "django.contrib.sites.managers.CurrentSiteManager")之前。 + + + + + +## Site middleware + +New in Django 1.7. + +如果你经常使用这个模式: + + + + + +``` +from django.contrib.sites.models import Site + +def my_view(request): + site = Site.objects.get_current() + ... + +``` + + + + + +这里有些方法可以防止这种重复调用。添加 [`django.contrib.sites.middleware.CurrentSiteMiddleware`](../middleware.html#django.contrib.sites.middleware.CurrentSiteMiddleware "django.contrib.sites.middleware.CurrentSiteMiddleware") 到[`MIDDLEWARE_CLASSES`](../settings.html#std:setting-MIDDLEWARE_CLASSES). 中间件设置 `site` 属性给每一次request对象, 所以你可以用 `request.site` 来获取当前site。 + + + + + +## Django是如何使用的站点框架 + +虽然不强制要求你的网站使用site框架,但是我们鼓励你使用它,因为在一些地方Django利用它。 即使你的Django只在支持单个站点, 你也应该花两秒时间来给你的站点对象创建`domain` 和`name`,并且设置它的ID在你的 [`SITE_ID`](../settings.html#std:setting-SITE_ID) setting中。 + +下面是Django 如何使用sites framework: + +* 在 [`redirects framework`](redirects.html#module-django.contrib.redirects "django.contrib.redirects: A framework for managing redirects."),每一个redirect都和特定的站点相关联。当Django查找一个 redirect, 它就考虑在当前的站点中查找。 +* 在 [`flatpages 框架`](flatpages.html#module-django.contrib.flatpages "django.contrib.flatpages: A framework for managing simple ?flat? HTML content in a database."), 每一个flatpage 都被关联到特定的站点。当一个 flatpage 被创建, 你指定它的 [`Site`](#django.contrib.sites.models.Site "django.contrib.sites.models.Site"),并且[`FlatpageFallbackMiddleware`](flatpages.html#django.contrib.flatpages.middleware.FlatpageFallbackMiddleware "django.contrib.flatpages.middleware.FlatpageFallbackMiddleware") 在返回flatpages 中检查当前站以显示。 +* 在 [`syndication framework`](syndication.html#module-django.contrib.syndication "django.contrib.syndication: A framework for generating syndication feeds, in RSS and Atom, quite easily.")中, 模板的 `title` and `description` 自动访问变量`{{ site }}`, 这个 [`Site`](#django.contrib.sites.models.Site "django.contrib.sites.models.Site") 代表当前站点的站点对象。. 此外,挂钩提供项URL将使用当前 [`Site`](#django.contrib.sites.models.Site "django.contrib.sites.models.Site")对象的`domain`,如果你不指定一个完全合格的域名。 +* 在[`authentication framework`](../../topics/auth/index.html#module-django.contrib.auth "django.contrib.auth: Django's authentication framework.")中, [`django.contrib.auth.views.login()`](../../topics/auth/default.html#django.contrib.auth.views.login "django.contrib.auth.views.login") 视图传递当前 [`Site`](#django.contrib.sites.models.Site "django.contrib.sites.models.Site") 名称的模板`{{ site_name }}`. +* 快捷视图 (`django.contrib.contenttypes.views.shortcut`) 使用当前[`Site`](#django.contrib.sites.models.Site "django.contrib.sites.models.Site")对象的的域 计算对象的URL。 +* 在管理框架, “view on site” 链接使用当前 [`Site`](#django.contrib.sites.models.Site "django.contrib.sites.models.Site") 算出将重定向的域名. + + + + + +## RequestSite objects + +一些 [_django.contrib_](index.html)应用有利用到 sites framework 但是它们的架构不会_require_ sites framework必须安装在你的数据库中。有些人不想, 或者不能安装site ?framework所要求的_able_在他们的数据库中。) 出于这种情况,framework 提供了一个 [`django.contrib.sites.requests.RequestSite`](#django.contrib.sites.requests.RequestSite "django.contrib.sites.requests.RequestSite")类,当你数据支持的站点框架不可用的时候做一个回退 + + + +_class_ `requests.RequestSite` + + + +A class that shares the primary interface of [`Site`](#django.contrib.sites.models.Site "django.contrib.sites.models.Site") (i.e., it has `domain` and `name` attributes) but gets its data from a Django [`HttpRequest`](../request-response.html#django.http.HttpRequest "django.http.HttpRequest") object rather than from a database. + + + +`__init__`(_request_) + + + +Sets the `name` and `domain` attributes to the value of [`get_host()`](../request-response.html#django.http.HttpRequest.get_host "django.http.HttpRequest.get_host"). + + + + + + + +Deprecated since version 1.7: This class used to be defined in `django.contrib.sites.models`. The old import location will work until Django 1.9. + + + + + + + +A [`RequestSite`](#django.contrib.sites.requests.RequestSite "django.contrib.sites.requests.RequestSite") object has a similar interface to a normal [`Site`](#django.contrib.sites.models.Site "django.contrib.sites.models.Site") object, except its [`__init__()`](#django.contrib.sites.requests.RequestSite.__init__ "django.contrib.sites.requests.RequestSite.__init__") method takes an [`HttpRequest`](../request-response.html#django.http.HttpRequest "django.http.HttpRequest") object. It’s able to deduce the `domain` and `name` by looking at the request’s domain. It has `save()` and `delete()` methods to match the interface of [`Site`](#django.contrib.sites.models.Site "django.contrib.sites.models.Site"), but the methods raise [`NotImplementedError`](https://docs.python.org/3/library/exceptions.html#NotImplementedError "(in Python v3.4)"). + + + + + +## get_current_site shortcut + +最后,为了避免重复的回退代码,site framework 提供了一个 [`django.contrib.sites.shortcuts.get_current_site()`](#django.contrib.sites.shortcuts.get_current_site "django.contrib.sites.shortcuts.get_current_site") 功能。 + + + +`shortcuts.get_current_site`(_request_) + + + +这是函数是用来检查`django.contrib.sites` 是否安装并且返回一个基于request的[`Site`](#django.contrib.sites.models.Site "django.contrib.sites.models.Site") 对象或者一个[`RequestSite`](#django.contrib.sites.requests.RequestSite "django.contrib.sites.requests.RequestSite") 对象。 + + + +Deprecated since version 1.7: This function used to be defined in `django.contrib.sites.models`. The old import location will work until Django 1.9. + + + +Changed in Django 1.8: + +This function will now lookup the current site based on [`request.get_host()`](../request-response.html#django.http.HttpRequest.get_host "django.http.HttpRequest.get_host") if the [`SITE_ID`](../settings.html#std:setting-SITE_ID) setting is not defined. + + + + + + + + + diff --git a/1_3_2_Writing your first patch for Django.md b/1_3_2_Writing your first patch for Django.md new file mode 100644 index 0000000000000000000000000000000000000000..17af606f99c5daaf9cc97c2aa80e6f7bd04f9d04 --- /dev/null +++ b/1_3_2_Writing your first patch for Django.md @@ -0,0 +1,636 @@ + + +# 为Django编写首个补丁 + + + +## 介绍 + +有兴趣为社区做出点贡献吗?也许你会在Django中发现你想要修复的漏洞,或者你希望为它添加一个小功能。 + +为Django作贡献这件事本身就是使你的顾虑得到解决的最好方式。一开始这可能会使你怯步,但事实上是很简单的。整个过程中我们会一步一步为你解说,所以你可以通过例子学习。 + + + +### 本页教程面向的读者 + +使用教程前,我们希望你至少对于Django的运行方式有基础的了解。这意味着你可以自如地在[_写你自己的Django app_](tutorial01.html)时使用教程。 除此之外,你应该对于Python本身有很好的了解。如果您并不太了解, 我们为您推荐[Dive Into Python](http://www.diveintopython3.net/),对于初次使用Python的程序员来说这是一本很棒(而且免费)的在线电子书。 + +对于版本控制系统及Trac不熟悉的人来说,这份教程及其中的链接所包含的信息足以满足你们开始学习的需求。然而,如果你希望定期为Django贡献,你可能会希望阅读更多关于这些不同工具的信息。 + +当然对于其中的大部分内容,Django会尽可能做出解释以帮助广大的读者。 + + + +何处获得帮助: + +如果你在使用本教程时遇到困难,你可以发送信息给[_django开发者_](../internals/mailing-lists.html#django-developers-mailing-list) 或者登陆 [#django-dev on irc.freenode.net](irc://irc.freenode.net/django-dev) 向其他Django使用者需求帮助。 + + + + + + + +### 教程包含的内容 + +一开始我们会帮助你为Django编写补丁,在教程结束时,你将具备对于工具和所包含过程的基本了解。准确来说,我们的教程将包含以下几点: + +* 安装Git。 +* 如何下载Django的开发副本 +* 运行Django的测试组件 +* 为你的补丁编写一个测试 +* 为你的补丁编码。 +* 测试你的补丁。 +* 为你所做的改变写一个补丁文件。 +* 去哪里寻找更多的信息。 + +一旦你完成了这份教程,你可以浏览剩下的[_Django’s documentation on contributing_](../internals/contributing/index.html). 它包含了大量信息。任何想成为Django的正式贡献者必须去阅读它。如果你有问题,它也许会给你答案 + + + + + + + +## 安装Git + +使用教程前,你需要安装好Git,下载Django的最新开发版本并且为你作出的改变生成补丁文件 + +为了确认你是否已经安装了Git, 输入 `git` 进入命令行。如果信息提示命令无法找到, 你就需要下载并安装Git, 详情阅读 [Git’s download page](http://git-scm.com/download). + +如果你还不熟悉 Git, 你可以在命令行下输入 `git help` 了解更多关于它的命令(确认已安装)。 + + + + + +## 获取Django 开发版的副本 + +为Django贡献的第一步就是获取源代码复本。在命令行里, 使用 `cd` 命令进入你想要保存Django的目录 + +使用下面的命令来下载Django的源码库 + + + + + +``` +git clone https://github.com/django/django.git + +``` + + + + + + + +注意 + +对那些希望使用 [virtualenv](http://www.virtualenv.org)的人,你可以用: + + + + + +``` +pip install -e /path/to/your/local/clone/django/ + +``` + + + + + +(你clone的`django` 目录包含 `setup.py`) ,它可以链接到你的cloned确认一个虚拟环境。这是一个伟大的选择,你开发的 Django 副本从您的系统的其余部分隔离,避免了潜在冲突的包。 + + + + + + + +## 回滚到更早的Django版本 + +这个教程中,我们使用 [#17549](https://code.djangoproject.com/ticket/17549)问题来作为学习用例,所以我们要把git中Django的版本回滚到这个问题的补丁没有提交之前。这样的话我们就可以参与到从草稿到补丁的所有过程,包括运行Django的测试套件。 + +**请记住,我们将用Django的老版本来到达学习的目的,通常情况下你应当使用当前最新的开发版本来提交补丁。** + + + +注意 + +这个补丁由 Ulrich Petri 开发, Git  提交到 Django 源码 [提交id为ac2052ebc84c45709ab5f0f25e685bf656ce79bc](https://github.com/django/django/commit/ac2052ebc84c45709ab5f0f25e685bf656ce79bc). 因此,我们要回到补丁提交之前的版本号 [提交ID: 39f5bc7fc3a4bb43ed8a1358b17fe0521a1a63ac](https://github.com/django/django/commit/39f5bc7fc3a4bb43ed8a1358b17fe0521a1a63ac). + + + +首先打开Django源码的根目录(这个目录包含了  `django`, `docs`, `tests`, `AUTHORS`, 等) 然后你你可以根据下面的教程check out老版本的Django: + + + + + +``` +git checkout 39f5bc7fc3a4bb43ed8a1358b17fe0521a1a63ac + +``` + + + + + + + + + +## 首先运行Django 的测试套件 + +当你贡献代码给Django的时候,一个非常重要的问题就是你修改的代码不要给其他部分引入新的bug。 有个办法可以在你更改代码之后检查Django是否能正常工作,就是运行Django的测试套件。如果所有的测试用例都通过,你就有理由相信你的改动完全没有破坏Django。如果你从来没有运行过Django的测试套件,那么比较好的做法是事先运行一遍,熟悉下正常情况下应该输出什么结果。 + +你可以简单的通过`cd`到Django  `tests/` 目录下执行测试,如果你是用GNU/Linux, Mac OS X或者其你喜欢的其他Unix系统,执行: + + + + + +``` +PYTHONPATH=.. python runtests.py --settings=test_sqlite + +``` + + + + + +如果你使用 Windows,安装 Git 后默认生成 Git 命令行环境: “Git Bash” ,在命令行中环境中执行上面的测试命令。GitHub提供了一个[很好的教程](https://help.github.com/articles/set-up-git#platform-windows)。 + + + +Note + +如果你使用了 `virtualenv`,你可以在执行测试时省略 `PYTHONPATH=..`。表示在 `测试`.目录的上一层目录寻找 Django 。`virtualenv` 自动把 Django 放在 `PYTHONPATH` 目录下。 + + + +现在坐下来放松一下。Django的整个测试套件有超过4800种不同的测试,所以它运行时间需要5到15分钟,这取决于你的电脑的速度。 + +Django的测试套件运行时,您将看到一个字符流代表每个测试的运行的状态。 `E` 表示测试中出现异常 和 `F` 表示断言失败。这两种情况都被认为测试失败。同时,`X` 和 `S` 分别表示与期望结果不同和跳过测试。 点表示测试通过。 + +跳过测试主要由缺少测试所需的外部库引起;查看 [_Running all the tests_](../internals/contributing/writing-code/unit-tests.html#running-unit-tests-dependencies) 获取测所需依赖包,并确保安装由于代码修改造成的新依赖包(这篇教程不需要额外安装依赖包)。 + +当测试执行完毕后,得到反馈信息显示测试已通过,或者测试失败。因为还没有对 Django 的源码做任何修改,所有的测试用例**应该**测试通过。如果测试失败或出现错误,回头确认以上执行操作是否正确。查看 [_Running the unit tests_](../internals/contributing/writing-code/unit-tests.html#running-unit-tests) 获取更多信息。 + +注意最新版本 Django 分支不总稳定。当在分支上开发时,你可以查看代码持续集成构建页面的信息 [Django’s continuous integration builds](http://djangoci.com) 来判断测试错误只在你指定的电脑上发生,还是官方版本中也存在该错误。如果点击某个构建信息,可以通过配置列表信息查看错误发生时 Python 以及后端数据库的信息。 + + + +Note + +在本教程以及所用分支中,测试使用数据库 SQLite 即可, 然而在某些情况下需要 [_测试更多不同的数据库_](../internals/contributing/writing-code/unit-tests.html#running-unit-tests-settings)。 + + + + + + + +## 为你的ticket写一些测试用例 + +大多数情况下,Django 的补丁必需包含测试。Bug 修复补丁的测试是一个回归测试,确保该 Bug 不会再次在 Django 中出现。该测试应该在 Bug 存在时测试失败,在 Bug 已经修复后通过测试。新功能补丁的测试必须验证新功能是否正常运行。新功能的测试将在功能正常时通过测试,功能未执行时测试失败。 + +最好的方式是在修改代码之前写测试单元代码。这种开发风格叫做 [测试驱动开发](http://en.wikipedia.org/wiki/Test-driven_development) 被应用在项目开发和单一补丁开发过程中。测试单元编写完毕后,执行测试单元,此时测试失败(因为目前还没有修复 BuG 或 添加新功能),如果测试成功通过,你需要重新修改测试单元保证测试失败。然而测试单元并没有阻止 BUG 发生的作用。 + +现在我们的操作示例。 + + + +### 为分支 #17549 写测试 + +分支 [#17549](https://code.djangoproject.com/ticket/17549) 描述了以下的额外功能。 + +> It’s useful for URLField to give you a way to open the URL; otherwise you might as well use a CharField. + +为了解决这个问题,我们将添加一个 `render` 方法到 `AdminURLFieldWidget` ,通过表单显示一个可点击的链接。在更改代码之前,我们需要一组测试来验证将添加的功能现在以及未来都能正常工作。 + +进入 Django 下 `tests/regressiontests/admin_widgets/` 目录打开文件  `tests.py` 。在第 269行类`AdminFileWidgetTest` 之前添加以下内容: + + + + + +``` +class AdminURLWidgetTest(DjangoTestCase): + def test_render(self): + w = widgets.AdminURLFieldWidget() + self.assertHTMLEqual( + conditional_escape(w.render('test', '')), + '' + ) + self.assertHTMLEqual( + conditional_escape(w.render('test', 'http://example.com')), + '

Currently:http://example.com
Change:

' + ) + + def test_render_idn(self): + w = widgets.AdminURLFieldWidget() + self.assertHTMLEqual( + conditional_escape(w.render('test', 'http://example-äüö.com')), + '

Currently:http://example-äüö.com
Change:

' + ) + + def test_render_quoting(self): + w = widgets.AdminURLFieldWidget() + self.assertHTMLEqual( + conditional_escape(w.render('test', 'http://example.com/some text')), + '

Currently:http://example.com/<sometag>some text</sometag>
Change:

' + ) + self.assertHTMLEqual( + conditional_escape(w.render('test', 'http://example-äüö.com/some text')), + '

Currently:http://example-äüö.com/<sometag>some text</sometag>
Change:

' + ) + +``` + + + + + +该测试会验证我们新添加的方法 `render` 在不同情况下工作正常。 + + + +但是这个测试内容看起来比较难... + +如果你没有写过测试,第一眼看上去测试代码会有点难。幸运的是测试在编程里是一个 _非常_ 重要的部分, 因此下面有更多的相关信息: + +* 为 Django 添加测试代码浏览官网文档: [_编写和运行测试单元_](../topics/testing/overview.html). +* 深入 Python (一个在线免费的 Python 初学者教程) 包含了非常棒的 [测试单元介绍](http://www.diveintopython.net/unit_testing/index.html). +* 阅读以上文档后,如果想更深入了解测试内容,参考:  [Python 测试单元文档](https://docs.python.org/library/unittest.html). + + + + + + + +### 编写新的测试 + +因为我们还没有对`AdminURLFieldWidget`做任何修改,所以我们的测试会失败。 我们在`model_forms_regress` 目录中运行所有测试,确保测试会失败。 在命令行中 `进入` Django 的 `tests/` 目录并执行: + + + + + +``` +PYTHONPATH=.. python runtests.py --settings=test_sqlite admin_widgets + +``` + + + + + +如果测试方法运行正常,会出现三个测试失败信息,每个信息对应一个我们刚刚添加的新测试方法。如果所有测试方法都正常通过,请检查上面的测试方法是否添加到了正确的文件位置。 + + + + + + + +## 修改 Django 源码 + +我们在 Django 仓储的标签[#17549](https://code.djangoproject.com/ticket/17549)中添加新功能描述。 + + + +### 为标签#17549编写代码 + +进入目录 `django/django/contrib/admin/` 并打开文件 `widgets.py`。 在第302行找到类 `AdminURLFieldWidget`,在`__init__`方法后面添加新方法 `render` 内容如下: + + + + + +``` +def render(self, name, value, attrs=None): + html = super(AdminURLFieldWidget, self).render(name, value, attrs) + if value: + value = force_text(self._format_value(value)) + final_attrs = {'href': mark_safe(smart_urlquote(value))} + html = format_html( + '

{} {}
{} {}

', + _('Currently:'), flatatt(final_attrs), value, + _('Change:'), html + ) + return html + +``` + + + + + + + + + +### 确保测试通过 + +修改 Django 源码后,我们通过之前编写的测试方法来验证源码修改是否工作正常。运行 `admin_widgets` 目录下所有的测试方法, `进入`  Django 的 `tests/` 目录然后运行: + + + + + +``` +PYTHONPATH=.. python runtests.py --settings=test_sqlite admin_widgets + +``` + + + + + +哦,好事是我们写了这些测试! 但仍然收到三个测试异常: + + + + + +``` +NameError: global name 'smart_urlquote' is not defined + +``` + + + + + +我们忘记导入这些方法。在 `smart_urlquote` 第 13 行的末尾添加`django/contrib/admin/widgets.py`,结果如下: + + + + + +``` +from django.utils.html import escape, format_html, format_html_join, smart_urlquote + +``` + + + + + +重新运行测试方法正常会通过测试。如果没有通过测试,请重新确认上面提到的类 `AdminURLFieldWidget` 以及新添加的测试方法是否被正确复制到指定位置。 + + + + + + + +## 再次运行Django 的测试套件 + +如果已经确认补丁以及测试结果都正常,现在是时候运行 Django 完整的测试用例,验证你的修改是否对 Django 的其他部分造成新的 Bug。 虽然测试用例帮助识别容易被人忽略的错误,但测试通过并不能保证完全没有 Bug 存在。 + +运行 Django 完整的测试用例, `进入` Django 下  `tests/` 目录并执行: + + + + + +``` +PYTHONPATH=.. python runtests.py --settings=test_sqlite + +``` + + + + + +只要没有看到测试异常,你可以继续下一步骤。注意这个修复会产生一个[小的 CSS 变动](https://github.com/django/django/commit/ac2052ebc84c45709ab5f0f25e685bf656ce79bc#diff-0) 来格式化新的组件。如果你喜欢,你可以修改它,但是目前我们不做任何修改。 + + + + + +## 编写文档 + +这个新功能信息应该被记录到文档。找到文件 `django/docs/ref/models/fields.txt` 第 925 行,在已存在的 `URLField` 文档条目下添加以下内容: + + + + + +``` +.. versionadded:: 1.5 + + The current value of the field will be displayed as a clickable link above the + input widget. + +``` + + + + + +关于 `versionadded` 的解释以及文档编写的更多信息,请参考 [_文档编写_](../internals/contributing/writing-documentation.html)。 这个页面还介绍了怎么在本地重新生成一份文档,你可以查看新生成的 HTML 文档页面. + + + + + +## 为你的修改生成补丁 + +现在是时候生成一个补丁文件,这个补丁文件可以上传到 Trac 或者更新到其他 Django。 运行下面这个命令来查看你的补丁内容: + + + + + +``` +git diff + +``` + + + + + +这里显示的内容为当前代码与 check out 时候的代码变化,即之前对代码所做修改前后的变化。 + +在浏览补丁内容后按 `q` 键退出命令行。如果你的补丁内容看起来正常,运行下面这个命令,在当前目录生成补丁文件: + + + + + +``` +git diff > 17549.diff + +``` + + + + + +在 Django 的根目录生成补丁文件 `17549.diff`。这个补丁文件包含所有的代码变动信息,看起来如下: + + + + + +``` +diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py +index 1e0bc2d..9e43a10 100644 +--- a/django/contrib/admin/widgets.py ++++ b/django/contrib/admin/widgets.py +@@ -10,7 +10,7 @@ from django.contrib.admin.templatetags.admin_static import static + from django.core.urlresolvers import reverse + from django.forms.widgets import RadioFieldRenderer + from django.forms.util import flatatt +-from django.utils.html import escape, format_html, format_html_join ++from django.utils.html import escape, format_html, format_html_join, smart_urlquote + from django.utils.text import Truncator + from django.utils.translation import ugettext as _ + from django.utils.safestring import mark_safe +@@ -306,6 +306,18 @@ class AdminURLFieldWidget(forms.TextInput): + final_attrs.update(attrs) + super(AdminURLFieldWidget, self).__init__(attrs=final_attrs) + ++ def render(self, name, value, attrs=None): ++ html = super(AdminURLFieldWidget, self).render(name, value, attrs) ++ if value: ++ value = force_text(self._format_value(value)) ++ final_attrs = {'href': mark_safe(smart_urlquote(value))} ++ html = format_html( ++ '

{} {}
{} {}

', ++ _('Currently:'), flatatt(final_attrs), value, ++ _('Change:'), html ++ ) ++ return html ++ + class AdminIntegerFieldWidget(forms.TextInput): + class_name = 'vIntegerField' + +diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt +index 809d56e..d44f85f 100644 +--- a/docs/ref/models/fields.txt ++++ b/docs/ref/models/fields.txt +@@ -922,6 +922,10 @@ Like all :class:`CharField` subclasses, :class:`URLField` takes the optional + :attr:`~CharField.max_length`argument. If you don't specify + :attr:`~CharField.max_length`, a default of 200 is used. + ++.. versionadded:: 1.5 ++ ++The current value of the field will be displayed as a clickable link above the ++input widget. + + Relationship fields + =================== +diff --git a/tests/regressiontests/admin_widgets/tests.py b/tests/regressiontests/admin_widgets/tests.py +index 4b11543..94acc6d 100644 +--- a/tests/regressiontests/admin_widgets/tests.py ++++ b/tests/regressiontests/admin_widgets/tests.py + +@@ -265,6 +265,35 @@ class AdminSplitDateTimeWidgetTest(DjangoTestCase): + '

Datum:
Zeit:

', + ) + ++class AdminURLWidgetTest(DjangoTestCase): ++ def test_render(self): ++ w = widgets.AdminURLFieldWidget() ++ self.assertHTMLEqual( ++ conditional_escape(w.render('test', '')), ++ '' ++ ) ++ self.assertHTMLEqual( ++ conditional_escape(w.render('test', 'http://example.com')), ++ '

Currently:http://example.com
Change:

' ++ ) ++ ++ def test_render_idn(self): ++ w = widgets.AdminURLFieldWidget() ++ self.assertHTMLEqual( ++ conditional_escape(w.render('test', 'http://example-äüö.com')), ++ '

Currently:http://example-äüö.com
Change:

' ++ ) ++ ++ def test_render_quoting(self): ++ w = widgets.AdminURLFieldWidget() ++ self.assertHTMLEqual( ++ conditional_escape(w.render('test', 'http://example.com/some text')), ++ '

Currently:http://example.com/<sometag>some text</sometag>
Change:

' ++ ) ++ self.assertHTMLEqual( ++ conditional_escape(w.render('test', 'http://example-äüö.com/some text')), ++ '

Currently:http://example-äüö.com/<sometag>some text</sometag>
Change:

' ++ ) + + class AdminFileWidgetTest(DjangoTestCase): + def test_render(self): + +``` + + + + + + + + + +## 接下来做什么? + +恭喜,你已经生成了你的第一个 Django 补丁 !现在你已经明白了整个过程,你可以好好利用这些技能帮助改善Django的代码库。 生成补丁和发送到 Trac 上是有用的,然而我们推荐使用 [_面向 git 的工作流_](../internals/contributing/writing-code/working-with-git.html)。 + +目前我们没有在本地对仓储做提交操作,我们可以通过下面这个命令放弃修改并回到最原始 Django 代码状态。 + + + + + +``` +git reset --hard HEAD +git checkout master + +``` + + + + + + + +### 关于新手贡献值的注意事项 + +在你开始为 Django 编写补丁时,这里有些信息,你应该看一看: + +* 你应该阅读了 Django 的参考文档 [_claiming tickets and submitting patches_](../internals/contributing/writing-code/submitting-patches.html). 它涵盖了Trac 规则,如何声称自己的 tickets,补丁的编码风格和其他一些重要信息。 +* 第一次提交补丁额外应该阅读  [_documentation for first time contributors_](../internals/contributing/new-contributors.html). 这里有很多对新手贡献值的建议。 +* 接下来,如果你想对源码贡献有更深入了解,可以阅读接下来的 Django 文档 [_Django’s documentation on contributing_](../internals/contributing/index.html)。 它包含了大量的有用信息,这里可以解决你可能遇到的所有问题。 + + + + + +### 寻找你的第一个真正的标签 + +一旦你看过了之前那些信息,你便已经具备了走出困境,为自己编写补丁寻找门票的能力。对于那些有着“容易获得”标准的门票要尤其注意。这些门票实际上常常很简单而且对于第一次撰写补丁的人很有帮助。一旦你熟悉了给Django写补丁,你就可以进一步为更难且更复杂的门票写补丁。 + +如果你只是想要简单的了解(没人会因此责备你!), 那么你可以尝试着查看这个[需要补丁的简单标签](https://code.djangoproject.com/query?status=new&status=reopened&has_patch=0&easy=1&col=id&col=summary&col=status&col=owner&col=type&col=milestone&order=priority)列表和[已有补丁但需要提升的简单标签](https://code.djangoproject.com/query?status=new&status=reopened&needs_better_patch=1&easy=1&col=id&col=summary&col=status&col=owner&col=type&col=milestone&order=priority)列表. 如果你比较擅长写测试,那么你也可以看看这个 [需要测试的简单标签](https://code.djangoproject.com/query?status=new&status=reopened&needs_tests=1&easy=1&col=id&col=summary&col=status&col=owner&col=type&col=milestone&order=priority)列表. 一定要记得遵循在Django的文档[_声明标签和递交补丁_](../internals/contributing/writing-code/submitting-patches.html)中提到的关于声明标签的指导规则. + + + + + +### 接下来要做什么? + +一旦一个标签有了补丁,那么它就需要其他人来重审。上传了一个补丁或递交了一个pull request之后,一定记得更新标签的元数据,比如设置标签的标志状态为“has patch”,“doesn’t need tests”等。只有这样,其他人才能找到并重审这个标签。从零开始写补丁并不是做贡献的唯一方式。重审一些已经存在的补丁也是一种非常有用的做贡献方式。点击[_标签鉴别_](../internals/contributing/triaging-tickets.html) 查看更多详细信息. + + + + + diff --git a/1_3_2_Writing your first patch for Django_.md b/1_3_2_Writing your first patch for Django_.md deleted file mode 100644 index c1522802daaf5227d6808ea887851e2af9bb1ea7..0000000000000000000000000000000000000000 --- a/1_3_2_Writing your first patch for Django_.md +++ /dev/null @@ -1,40 +0,0 @@ -# 为Django编写首个补丁 # - -## 介绍 ## - -有兴趣为社区做出点贡献吗?也许你会在Django中发现你想要修复的漏洞,或者你希望为它添加一个小特征。 - -为Django作贡献这件事本身就是使你的顾虑得到解决的最好方式。一开始这可能会使你怯步,但事实上是很简单的。整个过程中我们会一步一步为你解说,所以你可以通过例子学习。 - -## Who’s this tutorial for? ## - -使用教程前,我们希望你至少对于Django的运行方式有基础的了解。这意味着你可以自如地在写你自己的Django app时使用教程。 除此之外,你应该对于Python本身有很好的了解。如果您并不太了解, 我们为您推荐Dive Into Python,对于初次使用Python的程序员来说这是一本很棒(而且免费)的在线电子书。 - -对于版本控制系统及Trac不熟悉的人来说,这份教程及其中的链接所包含的信息足以满足你们开始学习的需求。然而,如果你希望定期为Django贡献,你可能会希望阅读更多关于这些不同工具的信息。 - -当然对于其中的大部分内容,Django会尽可能做出解释以帮助广大的读者。 - -> 何处获得帮助: -> -> 如果你在使用本教程时遇到困难,你可以发送信息给django开发者 或者登陆 #django-dev on irc.freenode.net 向其他Django使用者需求帮助。 - -## 教程包含的内容 ## - -一开始我们会帮助你为Django编写补丁,在教程结束时,你将具备对于工具和所包含过程的基本了解。准确来说,我们的教程将包含以下几点: - -+ 安装Git。 -+ 如何下载Django的开发复本 -+ 运行Django的测试组件 -+ 为你的补丁编写一个测试 -+ 为你的补丁编码。 -+ 测试你的补丁。 -+ 为你所做的改变写一个补丁文件。 -+ 去哪里寻找更多的信息。 - -一旦你完成了这份教程,你可以浏览剩下的Django’s documentation on contributing. 它包含了大量信息。任何想成为Django的正式贡献者必须去阅读它。如果你有问题,它也许会给你答案 - -## 安装Git ## - -使用教程前,你需要安装好Git,下载Django的最新开发版本并且为你作出的改变生成补丁文件 - -为了确认你是否已经安装了Git, 输入 git 进入命令行。如果信息提示命令无法找到, 你就需要下载并安装Git, 详情阅读 Git’s download page. diff --git a/2_1_2_Field types.md b/2_1_2_Field types.md new file mode 100644 index 0000000000000000000000000000000000000000..2e98bbc1dbbd9e6ad9d5d69cb0278a2fa51beeab --- /dev/null +++ b/2_1_2_Field types.md @@ -0,0 +1,5225 @@ + + + + +# 模型字段参考 + +本文档包含了Django提供的全部模型[`字段`](#django.db.models.Field "django.db.models.Field")的[字段选项](#field-options) 和 [字段类型](#field-types)的API参考。 + + + +请看: + +如果内建的字段不能满足你的需要,你可以尝试包含对特定国家和文化有帮助的配套代码的 [django-localflavor](https://django-localflavor.readthedocs.org/)。当然,你也可以很容易的编写你[_自定义的字段_](../../howto/custom-model-fields.html)。 + + + + + +注意 + +严格意义上来讲, Model 是定义在[`django.db.models.fields`](#module-django.db.models.fields "django.db.models.fields: Built-in field types.")里面,但为了使用方便,它们被导入到 [`django.db.models`](../../topics/db/models.html#module-django.db.models "django.db.models")中;标准上,我们导入`from django.db import models`,然后使用?`models.Field`的形式使用字段。 + + + + + +## 字段选项(Field options) + +下列参数是全部字段类型都可用的,而且都是可选择的。 + + + +### null + + + +`Field.null` + + + +如果为`True`,Django 将空值以`NULL` 存储到数据库中。默认值是 `False`。 + +字符串字段例如[`CharField`](#django.db.models.CharField "django.db.models.CharField") 和[`TextField`](#django.db.models.TextField "django.db.models.TextField") 要避免使用[`null`](#django.db.models.Field.null "django.db.models.Field.null"),因为空字符串值将始终储存为空字符串而不是`NULL`。如果字符串字段的`null=True`,那意味着对于“无数据”有两个可能的值:`NULL` 和空字符串。在大多数情况下,对于“无数据”声明两个值是赘余的,Django 的惯例是使用空字符串而不是`NULL`。 + +无论是字符串字段还是非字符串字段,如果你希望在表单中允许空值,你将还需要设置`blank=True`,因为[`null`](#django.db.models.Field.null "django.db.models.Field.null") 仅仅影响数据库存储(参见[`blank`](#django.db.models.Field.blank "django.db.models.Field.blank"))。 + + + +注意 + +在使用Oracle 数据库时,数据库将存储`NULL` 来表示空字符串,而与这个属性无关。 + + + +如果你希望[`BooleanField`](#django.db.models.BooleanField "django.db.models.BooleanField") 接受[`null`](#django.db.models.Field.null "django.db.models.Field.null") 值,请用 [`NullBooleanField`](#django.db.models.NullBooleanField "django.db.models.NullBooleanField") 代替。 + + + + + +### blank + + + +`Field.blank` + + + +如果为`True`,则该字段允许为空白。 默认值是 `False`。 + +注意它与[`null`](#django.db.models.Field.null "django.db.models.Field.null")不同。[`null`](#django.db.models.Field.null "django.db.models.Field.null") 纯粹是数据库范畴的概念,而[`blank`](#django.db.models.Field.blank "django.db.models.Field.blank") 是数据验证范畴的。如果字段设置`blank=True`,表单验证时将允许输入空值。如果字段设置`blank=False`,则该字段为必填。 + + + + + +### choices + + + +`Field.choices` + + + +它是一个可迭代的结构(比如,列表或是元组),由可迭代的二元组组成(比如`[(A, B), (A, B) ...]`),用来给这个字段提供选择项。如果设置了 choices ,默认表格样式就会显示选择框,而不是标准的文本框,而且这个选择框的选项就是 choices 中的元组。 + +每个元组中的第一个元素,是存储在数据库中的值;第二个元素是该选项更易理解的描述。 比如: + + + + + +``` +YEAR_IN_SCHOOL_CHOICES = ( + ('FR', 'Freshman'), + ('SO', 'Sophomore'), + ('JR', 'Junior'), + ('SR', 'Senior'), +) + +``` + + + + + +一般来说,最好在模型类内部定义choices,然后再给每个值定义一个合适名字的常量。 + + + + + +``` +from django.db import models + +class Student(models.Model): + FRESHMAN = 'FR' + SOPHOMORE = 'SO' + JUNIOR = 'JR' + SENIOR = 'SR' + YEAR_IN_SCHOOL_CHOICES = ( + (FRESHMAN, 'Freshman'), + (SOPHOMORE, 'Sophomore'), + (JUNIOR, 'Junior'), + (SENIOR, 'Senior'), + ) + year_in_school = models.CharField(max_length=2, + choices=YEAR_IN_SCHOOL_CHOICES, + default=FRESHMAN) + + def is_upperclass(self): + return self.year_in_school in (self.JUNIOR, self.SENIOR) + +``` + + + + + +尽管你可以在模型类的外部定义choices然后引用它,但是在模型类中定义choices和其每个choice的name(即元组的第二个元素)可以保存所有使用choices的类的信息, 也使得choices更容易被应用(例如, `Student.SOPHOMORE` 可以在任何引入`Student` 模型的位置生效)。 + +你也可以归类可选的choices到已命名的组中用来达成组织整理的目的: + + + + + +``` +MEDIA_CHOICES = ( + ('Audio', ( + ('vinyl', 'Vinyl'), + ('cd', 'CD'), + ) + ), + ('Video', ( + ('vhs', 'VHS Tape'), + ('dvd', 'DVD'), + ) + ), + ('unknown', 'Unknown'), +) + +``` + + + + + +每个元组的第一个元素是组的名字。第二个元素是一组可迭代的二元元组,每一个二元元组包含一个值和一个给人看的名字构成一个选项。分组的选项可能会和未分组的选项合在同一个list中。 (就像例中的unknown选项)。 + +对于有[`choices`](#django.db.models.Field.choices "django.db.models.Field.choices") set的模型字段, Django 将会加入一个方法来获取当前字段值的易于理解的名称(即元组的第二个值)参见数据库API文档中的[`get_FOO_display()`](instances.html#django.db.models.Model.get_FOO_display "django.db.models.Model.get_FOO_display")。 + +请注意choices可以是任何可迭代的对象 – 不是必须是列表或者元组。这一点使你可以动态的构建choices。但是如果你发现你自己搞不定动态的[`choices`](#django.db.models.Field.choices "django.db.models.Field.choices"),你最好还是使用[`ForeignKey`](#django.db.models.ForeignKey "django.db.models.ForeignKey")来构建一个合适的数据库表。如果有数据变动的话,[`choices`](#django.db.models.Field.choices "django.db.models.Field.choices")意味着那些变动不多的静态数据。 + +New in Django 1.7. + +除非[`blank=False`](#django.db.models.Field.blank "django.db.models.Field.blank") 和[`default`](#django.db.models.Field.default "django.db.models.Field.default")一起在字段中被设置,否则,可选择菜单将会有`"---------"` 的标签。要重写这个行为, 需要加入一个包含`None`的元组到 `choices`里面; 例如 `(None, 'Your String For Display')`. 或者, 你可以在操作有意义的地方用一个空字符串代替`None` ?- 比如在一个 [`CharField`](#django.db.models.CharField "django.db.models.CharField"). + + + + + +### db_column + + + +`Field.db_column` + + + +数据库中用来表示该字段的名称。如果未指定,那么Django将会使用Field名作为字段名. + +如果你的数据库列名为SQL语句的保留字,或者是包含不能作为Python 变量名的字符,如连字符,没问题。Django 会在后台给列名和表名加上双引号。 + + + + + +### db_index + + + +`Field.db_index` + + + +若值为 `True`, 则 [`django-admin sqlindexes`](../django-admin.html#django-admin-sqlindexes) 将会为此字段输出 `CREATE INDEX` 语句。(译注:为此字段创建索引) + + + + + +### db_tablespace + + + +`Field.db_tablespace` + + + +?如果该字段有索引的话,[_database tablespace_](../../topics/db/tablespaces.html)的名称将作为该字段的索引名。 如果[`DEFAULT_INDEX_TABLESPACE`](../settings.html#std:setting-DEFAULT_INDEX_TABLESPACE) 已经设置,则默认值是由DEFAULT_INDEX_TABLESPACE指定, 如果没有设置则由 [`db_tablespace`](options.html#django.db.models.Options.db_tablespace "django.db.models.Options.db_tablespace") 指定,如果后台数据库不支持表空间,或者索引,则该选项被忽略 + + + + + +### default + + + +`Field.default` + + + +该字段的默认值. 它可以是一个值或者一个可调用对象. 如果是一个可调用对象,那么在每一次创建新对象的时候,它将会调用一次. + +这个默认值不可以是一个可变对象(如字典,列表,等等),因为对于所有模型的一个新的实例来说,它们指向同一个引用。或者,把他们包装为一个可调用的对象。例如,你有一个自定义的`JSONField`,并且想指定一个特定的字典值,可以如下使用: + + + + + +``` +def contact_default(): + return {"email": "to1@example.com"} + +contact_info = JSONField("ContactInfo", default=contact_default) + +``` + + + + + +请注意`lambda`s 函数不可作为如 `default` 这类可选参数的值.因为它们无法被 [_migrations命令序列化_](../../topics/migrations.html#migration-serializing). 请参见文档其他部分。 + +默认值会在新实例创建并且没有给该字段提供值时使用。如果字段为主键,默认值也会在设置为`None`时使用。 + +Changed in Django 1.8: + +之前的版本不会使用`None`作为主键? + + + + + + + +### editable + + + +`Field.editable` + + + +如果设为`False`, 这个字段将不会出现在 admin 或者其他 [`ModelForm`](../../topics/forms/modelforms.html#django.forms.ModelForm "django.forms.ModelForm"). 他们也会跳过 [_模型验证_](instances.html#validating-objects). 默认是`True`. + + + + + +### error_messages + + + +`Field.error_messages` + + + +`error_messages` 参数能够让你重写默认抛出的错误信息。通过指定 key 来确认你要重写的错误信息。 + +error_messages 的 key 值包括 `null`, `blank`, `invalid`, `invalid_choice`, `unique`, 和 `unique_for_date`. 其余的 error_messages 的 keys 是不一样的在不同的章节下 [Field types](#field-types) 。 + +New in Django 1.7. + +这个 `unique_for_date` 的 error_messages 的key 是在 1.7 中加的。 + + + + + +### help_text + + + +`Field.help_text` + + + +额外的 ‘help' 文本将被显示在表单控件form中。即便你的字段没有应用到一个form里面,这样的操作对文档化也很有帮助。 + +注意这 _不_ 会自动添加 HTML 标签。需要你在 [`help_text`](#django.db.models.Field.help_text "django.db.models.Field.help_text") 包含自己需要的格式。例如: + + + + + +``` +help_text="Please use the following format: YYYY-MM-DD." + +``` + + + + + +另外, 你可以使用简单文本和`django.utils.html.escape()`以避免任何HTML特定的字符.请确保你所使用的help text能够避免那些由不受信任的用户进行的跨站点脚本攻击。 + + + + + +### primary_key + + + +`Field.primary_key` + + + +若为 `True`, 则该字段会成为模型的主键字段。 + +如果你没有在模型的任何字段上指定 `primary_key=True`, Django会自动添加一个 [`AutoField`](#django.db.models.AutoField "django.db.models.AutoField") 字段来充当主键。 所以除非你想要覆盖默认的主键行为,否则不需要在任何字段上设定`primary_key=True` 。更多内容 请参考 [_Automatic primary key fields_](../../topics/db/models.html#automatic-primary-key-fields). + +`primary_key=True` 暗含着[`null=False`](#django.db.models.Field.null "django.db.models.Field.null") 和[`unique=True`](#django.db.models.Field.unique "django.db.models.Field.unique"). 一个对象上只能拥有一个主键. + +主键字段是只读的。如果你改变了一个已存在对象上的主键并且保存的话,会创建一个新的对象,而不是覆盖旧的. + + + + + +### unique + + + +`Field.unique` + + + +如果为?`True`, 这个字段在表中必须有唯一值. + +这是一个在数据库级别的强制性动作,并且通过模型来验证。如果你试图用一个重复的值来保存[`unique`](#django.db.models.Field.unique "django.db.models.Field.unique") 字段,那么一个[`django.db.IntegrityError`](../exceptions.html#django.db.IntegrityError "django.db.IntegrityError")就会通过模型的[`save()`](instances.html#django.db.models.Model.save "django.db.models.Model.save") 函数抛出来。 + +除了[`ManyToManyField`](#django.db.models.ManyToManyField "django.db.models.ManyToManyField")、[`OneToOneField`](#django.db.models.OneToOneField "django.db.models.OneToOneField")和[`FileField`](#django.db.models.FileField "django.db.models.FileField") 以外的其他字段类型都可以使用这个设置。 + +注意当设置`unique` 为`True` 时,你不需要再指定 [`db_index`](#django.db.models.Field.db_index "django.db.models.Field.db_index"),因为`unique` 本身就意味着一个索引的创建。 + + + + + +### unique_for_date + + + +`Field.unique_for_date` + + + +当设置它为[`DateField`](#django.db.models.DateField "django.db.models.DateField") 和[`DateTimeField`](#django.db.models.DateTimeField "django.db.models.DateTimeField") 字段的名称时,表示要求该字段对于相应的日期字段值是唯一的。 + +例如,你有一个`title` 字段设置`unique_for_date="pub_date"`,那么Django 将不允许两个记录具有相同的`title` 和`pub_date`。 + +注意,如果你将它设置为[`DateTimeField`](#django.db.models.DateTimeField "django.db.models.DateTimeField"),只会考虑其日期部分。此外,如果[`USE_TZ`](../settings.html#std:setting-USE_TZ) 为`True`,检查将以对象保存时的[_当前的时区_](../../topics/i18n/timezones.html#default-current-time-zone) 进行。 + +这是在模型验证期间通过[`Model.validate_unique()`](instances.html#django.db.models.Model.validate_unique "django.db.models.Model.validate_unique") 强制执行的,而不是在数据库层级进行验证。如果[`unique_for_date`](#django.db.models.Field.unique_for_date "django.db.models.Field.unique_for_date") 约束涉及的字段不是[`ModelForm`](../../topics/forms/modelforms.html#django.forms.ModelForm "django.forms.ModelForm")中的字段(例如,`exclude`中列出的字段或者设置了[`editable=False`](#django.db.models.Field.editable "django.db.models.Field.editable")),[`Model.validate_unique()`](instances.html#django.db.models.Model.validate_unique "django.db.models.Model.validate_unique") 将忽略该特殊的约束。 + + + + + +### unique_for_month + + + +`Field.unique_for_month` + + + +类似[`unique_for_date`](#django.db.models.Field.unique_for_date "django.db.models.Field.unique_for_date"),只是要求字段对于月份是唯一的。 + + + + + +### unique_for_year + + + +`Field.unique_for_year` + + + +类似[`unique_for_date`](#django.db.models.Field.unique_for_date "django.db.models.Field.unique_for_date") 和 [`unique_for_month`](#django.db.models.Field.unique_for_month "django.db.models.Field.unique_for_month")。 + + + + + +### verbose_name + + + +`Field.verbose_name` + + + +一个字段的可读性更高的名称。如果用户没有设定冗余名称字段,Django会自动将该字段属性名中的下划线转换为空格,并用它来创建冗余名称。可以参照 [_Verbose field names_](../../topics/db/models.html#verbose-field-names). + + + + + +### validators + + + +`Field.validators` + + + +该字段将要运行的一个Validator 的列表。更多信息参见[_Validators 的文档_](../validators.html)。 + + + +#### 注册和获取查询 + +`Field` 实现了[_查询注册API_](lookups.html#lookup-registration-api)。该API 可以用于自定义一个字段类型的可用的查询,以及如何从一个字段获取查询。 + + + + + + + + + +## 字段类型 (Field types) + + + +### 自增字段 + + + +_class_ `AutoField`(_**options_) + + + +一个根据实际ID自动增长的[`IntegerField`](#django.db.models.IntegerField "django.db.models.IntegerField") . 你通常不需要直接使用;如果不指定,一个主键字段将自动添加到你创建的模型中.详细查看?[_主键字段_](../../topics/db/models.html#automatic-primary-key-fields). + + + + + +### BigIntegerField + + + +_class_ `BigIntegerField`([_**options_]) + + + +一个64位整数, 类似于一个?[`IntegerField`](#django.db.models.IntegerField "django.db.models.IntegerField") ,它的值的范围是?`-9223372036854775808` 到`9223372036854775807`之间. 这个字段默认的表单组件是一个[`TextInput`](../forms/widgets.html#django.forms.TextInput "django.forms.TextInput"). + + + + + +### BinaryField + + + +_class_ `BinaryField`([_**options_]) + + + +这是一个用来存储原始二进制码的Field. 只支持`bytes` 赋值,注意这个Field只有很有限的功能。例如,不大可能在一个`BinaryField` 值的数据上进行查询 + + + +Abusing `BinaryField` + +尽管你可能想使用数据库来存储你的文件,但是99%的情况下这都是不好的设计。这个字段 _不是_替代[_static files_](../../howto/static-files/index.html) 的合理操作. + + + + + + + +### BooleanField + + + +_class_ `BooleanField`(_**options_) + + + +true/false 字段。 + +此字段的默认表单挂件是一个[`CheckboxInput`](../forms/widgets.html#django.forms.CheckboxInput "django.forms.CheckboxInput"). + +如果你需要设置[`null`](#django.db.models.Field.null "django.db.models.Field.null") 值,则使用[`NullBooleanField`](#django.db.models.NullBooleanField "django.db.models.NullBooleanField") 来代替BooleanField。 + +如果[`Field.default`](#django.db.models.Field.default "django.db.models.Field.default")没有指定的话, `BooleanField` 的默认值是 `None`。 + + + + + +### CharField + + + +_class_ `CharField`(_max_length=None_[, _**options_]) + + + +一个用来存储从小到很大各种长度的字符串的地方 + +如果是巨大的文本类型, 可以用 [`TextField`](#django.db.models.TextField "django.db.models.TextField"). + +这个字段默认的表单样式是 [`TextInput`](../forms/widgets.html#django.forms.TextInput "django.forms.TextInput"). + +[`CharField`](#django.db.models.CharField "django.db.models.CharField")必须接收一个额外的参数: + + + +`CharField.max_length` + + + +字段的最大字符长度.max_length将在数据库层和Django表单验证中起作用, 用来限定字段的长度.? + + + + + + + +Note + +如果你在写一个需要导出到多个不同数据库后端的应用,你需要注意某些后端对`max_length`有一些限制,查看[_数据库后端注意事项_](../databases.html)来获取更多的细节 + + + + + +MySQL的使用者们: + +如果你在使用该字段的同时使用MySQLdb 1.2.2以及 `utf8_bin` 校对 其一般_不是_ 默认情况), 你必须了解一些问题. 详细请参考 [_MySQL database notes_](../databases.html#mysql-collation) . + + + + + + + +### CommaSeparatedIntegerField + + + +_class_ `CommaSeparatedIntegerField`(_max_length=None_[, _**options_]) + + + +一个逗号分隔的整数字段。像 [`CharField`](#django.db.models.CharField "django.db.models.CharField")一样, 需要一个[`max_length`](#django.db.models.CharField.max_length "django.db.models.CharField.max_length") 参数, 同时数据库移植时也需要注意。 + + + + + +### DateField + + + +_class_ `DateField`([_auto_now=False_, _auto_now_add=False_, _**options_]) + + + +这是一个使用Python的`datetime.date`实例表示的日期. 有几个额外的设置参数: + + + +`DateField.auto_now` + + + +每次保存对象时,自动设置该字段为当前时间。用于"最后一次修改"的时间戳。注意,它_总是_使用当前日期;和你可以覆盖的那种默认值不一样。 + + + + + + + +`DateField.auto_now_add` + + + +当对象第一次被创建时自动设置当前时间。用于创建时间的时间戳. 它_总是_使用当前日期;和你可以覆盖的那种默认值不一样。 + + + + + +该字段默认对应的表单控件是一个[`TextInput`](../forms/widgets.html#django.forms.TextInput "django.forms.TextInput"). 在管理员站点添加了一个JavaScript写的日历控件,和一个“Today"的快捷按钮.包含了一个额外的`invalid_date`错误消息键. + +`auto_now_add`, `auto_now`, and `default` 这些设置是相互排斥的. 他们之间的任何组合将会发生错误的结果. + + + +Note + +在目前的实现中,设置`auto_now`或者`auto_now_add`为`True`将为让这个字段同时得到`editable=False`和`blank=True`这两个设置. + + + + + +Note + +`auto_now` and `auto_now_add`这两个设置会在对象创建或更新的时刻,总是使用[_default timezone_](../../topics/i18n/timezones.html#default-current-time-zone)(默认时区)的日期. 如果你不想这样,你可以考虑一下简单地使用你自己的默认调用或者重写`save()`(在save()函数里自己添加保存时间的机制.译者注)而不是使用`auto_now` or `auto_now_add`; 或者使用`DateTimeField`字段类来替换`DateField` 并且在给用户呈现时间的时候,决定如何处理从datetime到date的转换. + + + + + + + +### DateTimeField + + + +_class_ `DateTimeField`([_auto_now=False_, _auto_now_add=False_, _**options_]) + + + +它是通过Python `datetime.datetime`实例表示的日期和时间. 携带了跟[`DateField`](#django.db.models.DateField "django.db.models.DateField")一样的额外参数. + +该字段默认对应的表单控件是一个单个的[`TextInput`](../forms/widgets.html#django.forms.TextInput "django.forms.TextInput")(单文本输入框). 管理界面是使用两个带有 JavaScript控件的?[`TextInput`](../forms/widgets.html#django.forms.TextInput "django.forms.TextInput")?文本框. + + + + + +### DecimalField + + + +_class_ `DecimalField`(_max_digits=None_, _decimal_places=None_[, _**options_]) + + + +用python中 [`Decimal`](https://docs.python.org/3/library/decimal.html#decimal.Decimal "(in Python v3.4)") 的一个实例来表示十进制浮点数. 有两个 **必须的**参数: + + + +`DecimalField.max_digits` + + + +位数总数,包括小数点后的位数。该值必须大于等于`decimal_places`. + + + + + + + +`DecimalField.decimal_places` + + + +小数点后的数字数量 + + + + + +例如,要保存最大为 `999` 并有两位小数的数字,你应该使用: + + + + + +``` +models.DecimalField(..., max_digits=5, decimal_places=2) + +``` + + + + + +而要存储那些将近10亿,并且要求达到小数点后十位精度的数字: + + + + + +``` +models.DecimalField(..., max_digits=19, decimal_places=10) + +``` + + + + + +该字段默认的窗体组件是 [`TextInput`](../forms/widgets.html#django.forms.TextInput "django.forms.TextInput"). + + + +Note + +想获取更多关于 [`FloatField`](#django.db.models.FloatField "django.db.models.FloatField") 和 [`DecimalField`](#django.db.models.DecimalField "django.db.models.DecimalField") 差异, 请参照 [_FloatField vs. DecimalField_](#floatfield-vs-decimalfield). + + + + + + + +### DurationField + +New in Django 1.8. + + + +_class_ `DurationField`([_**options_]) + + + +用作存储一段时间的字段类型 - 类似Python中的[`timedelta`](https://docs.python.org/3/library/datetime.html#datetime.timedelta "(in Python v3.4)"). 当数据库使用的是PostgreSQL, 该数据类型使用的是一个 `interval` 而在Oracle上,则使用的是 `INTERVAL DAY(9) TO SECOND(6)`. Otherwise a `bigint` of microseconds is used. + + + +注意 + +`DurationField` 的算数运算在多数情况下可以很好的工作。?然而,在除了PostgreSQL之外的其他数据库中, 将 `DurationField` 与 `DateTimeField` 的实例比较则不会得到正确的结果。 + + + + + + + +### EmailField + + + +_class_ `EmailField`([_max_length=254_, _**options_]) + + + +一个 [`CharField`](#django.db.models.CharField "django.db.models.CharField") 用来检查输入的email地址是否合法。它使用 [`EmailValidator`](../validators.html#django.core.validators.EmailValidator "django.core.validators.EmailValidator") 来验证输入合法性。 + +Changed in Django 1.8: + +默认最大长度 `max_length` 从75增加到254以符合RFC3696/5321标准。 + + + + + + + +### FileField + + + +_class_ `FileField`([_upload_to=None_, _max_length=100_, _**options_]) + + + +一个上传文件的字段。 + + + +注意 + +FileField字段不支持`primary_key` 和`unique`参数,如果使用会生成?`TypeError`错误 + + + +有两个可选参数: + + + +`FileField.upload_to` + + + +Changed in Django 1.7: + +在旧版本Django中,`upload_to`?属性是必须要有的; + + + +一个本地文件系统的路径,它将附加到[`MEDIA_ROOT`](../settings.html#std:setting-MEDIA_ROOT) 设置的后面来确定[`url`](#django.db.models.fields.files.FieldFile.url "django.db.models.fields.files.FieldFile.url") 属性的值。 + +这个路径可能会包含一个 [`strftime()`](https://docs.python.org/3/library/time.html#time.strftime "(in Python v3.4)") 格式串,并且会在文件上传时被替换为 实际的date/time作为文件路径 (这样上传的文件就不会塞满你指定的文件夹了). + +它还可以是一个可调用对象如函数,将调用它来获取上传路径,包括文件名。这个可调用对象必须接受两个参数,并且返回一个Unix 风格的路径(带有前向/)给存储系统。将传递的两个参数为: + + +| Argument | Description | +| --- | --- | +| `instance` | + +`FileField` 定义所在的模型的实例。更准确地说,就是当前文件的所在的那个实例。 + +大部分情况下,这个实例将还没有保存到数据库中,若它用到默认的`AutoField` 字段,_它的主键字段还可能没有值_。 + + | +| `filename` | The filename that was originally given to the file. This may or may not be taken into account when determining the final destination path. | + + + + + + + +`FileField.storage` + + + +一个Storage 对象,用于你的文件的存取。参见[_管理文件_](../../topics/files.html) 获取如何提供这个对象的细节。 + + + + + +这个字段的默认表单Widget 是[`ClearableFileInput`](../forms/widgets.html#django.forms.ClearableFileInput "django.forms.ClearableFileInput")。 + +在模型中调用[`FileField`](#django.db.models.FileField "django.db.models.FileField") 或 [`ImageField`](#django.db.models.ImageField "django.db.models.ImageField") (见下方) 需如下几步: + +1. 在你的settings文件中, 你必须要定义 [`MEDIA_ROOT`](../settings.html#std:setting-MEDIA_ROOT) 作为Django存储上传文件的路径(从性能上考虑,这些文件不能存在数据库中。) 定义一个 [`MEDIA_URL`](../settings.html#std:setting-MEDIA_URL) 作为基础的URL或者目录。确保这个目录可以被web server使用的账户写入。 +2. 在模型中添加[`FileField`](#django.db.models.FileField "django.db.models.FileField") 或 [`ImageField`](#django.db.models.ImageField "django.db.models.ImageField") 字段, 定义 [`upload_to`](#django.db.models.FileField.upload_to "django.db.models.FileField.upload_to")参数,内容是 [`MEDIA_ROOT`](../settings.html#std:setting-MEDIA_ROOT) 的子目录,用来存放上传的文件。 +3. 数据库中存放的仅是这个文件的路径 (相对于[`MEDIA_ROOT`](../settings.html#std:setting-MEDIA_ROOT)). 你很可能会想用由Django提供的便利的[`url`](#django.db.models.fields.files.FieldFile.url "django.db.models.fields.files.FieldFile.url") 属性。比如说, 如果你的[`ImageField`](#django.db.models.ImageField "django.db.models.ImageField") 命名为 `mug_shot`, 你可以在template中用 `{{ object.mug_shot.url }}`获得你照片的绝对路径。 + +例如,如果你的 [`MEDIA_ROOT`](../settings.html#std:setting-MEDIA_ROOT)设定为 `'/home/media'`,并且 [`upload_to`](#django.db.models.FileField.upload_to "django.db.models.FileField.upload_to")设定为 `'photos/%Y/%m/%d'`。 [`upload_to`](#django.db.models.FileField.upload_to "django.db.models.FileField.upload_to")的`'%Y/%m/%d'`被[`strftime()`](https://docs.python.org/3/library/time.html#time.strftime "(in Python v3.4)")所格式化;`'%Y'` 将会被格式化为一个四位数的年份, `'%m'` 被格式化为一个两位数的月份`'%d'`是两位数日份。如果你在Jan.15.2007上传了一个文件,它将被保存在`/home/media/photos/2007/01/15`目录下. + +如果你想获得上传文件的存盘文件名,或者是文件大小,你可以分别使用 [`name`](../files/file.html#django.core.files.File.name "django.core.files.File.name") 和 [`size`](../files/file.html#django.core.files.File.size "django.core.files.File.size") 属性; 更多可用属性及方法信息,请参见 [`File`](../files/file.html#django.core.files.File "django.core.files.File") 类索引 和 [_Managing files_](../../topics/files.html) 主题指导. + + + +Note + +保存的文件作为模型存储在数据库中的一部分,所以在磁盘上使用的实际的文件名在模型保存完毕之前是不可靠的。 + + + +上传的文件对应的URL可以通过使用 [`url`](#django.db.models.fields.files.FieldFile.url "django.db.models.fields.files.FieldFile.url") 属性获得. 在内部,它会调用 ?[`Storage`](../files/storage.html#django.core.files.storage.Storage "django.core.files.storage.Storage") 类下的[`url()`](../files/storage.html#django.core.files.storage.Storage.url "django.core.files.storage.Storage.url")方法. + +值得注意的是,无论你在任何时候处理上传文件的需求,你都应该密切关注你的文件将被上传到哪里,上传的文件类型,以避免安全漏洞。_认证所有上传文件_ 以确保那些上传的文件是你所认为的文件。例如,如果你盲目的允许其他人在无需认证的情况下上传文件至你的web服务器的root目录中,那么别人可以上传一个CGI或者PHP脚本然后通过访问一个你网站的URL来执行这个脚本。所以,不要允许这种事情发生。 + +甚至是上传HTML文件也值得注意,它可以通过浏览器(虽然不是服务器)执行,也可以引发相当于是XSS或者CSRF攻击的安全威胁。 + +[`FileField`](#django.db.models.FileField "django.db.models.FileField") 实例将会在你的数据库中创建一个默认最大长度为100字符的`varchar` 列。就像其他的fields一样, 你可以用 [`max_length`](#django.db.models.CharField.max_length "django.db.models.CharField.max_length") 参数改变最大长度的值. + + + +#### FileField 和FieldFile + + + +_class_ `FieldFile`[[source]](../../_modules/django/db/models/fields/files.html#FieldFile) + + + +当你添加 [`FileField`](#django.db.models.FileField "django.db.models.FileField") 到你的模型中时, 你实际上会获得一个 [`FieldFile`](#django.db.models.fields.files.FieldFile "django.db.models.fields.files.FieldFile")的实例来替代将要访问的文件。 除了继承至 [`django.core.files.File`](../files/file.html#django.core.files.File "django.core.files.File")的功能外, 这个类还有其他属性和方法可以用于访问文件: + + + +`FieldFile.url` + + + +通过潜在[`Storage`](../files/storage.html#django.core.files.storage.Storage "django.core.files.storage.Storage") 类的[`url()`](../files/storage.html#django.core.files.storage.Storage.url "django.core.files.storage.Storage.url")方法可以只读地访问文件的URL。 + + + +`FieldFile.open`(_mode='rb'_)[[source]](../../_modules/django/db/models/fields/files.html#FieldFile.open) + + + +该方法像标准的Python `open()` 方法,并可通过?`mode`参数设置打开模式. + + + +`FieldFile.close`()[[source]](../../_modules/django/db/models/fields/files.html#FieldFile.close) + + + +该方法像标准的Python`file.close()` 方法,并关闭相关文件. + + + +`FieldFile.save`(_name_, _content_, _save=True_)[[source]](../../_modules/django/db/models/fields/files.html#FieldFile.save) + + + +这个方法会将文件名以及文件内容传递到字段的storage类中,并将模型字段与保存好的文件关联. 如果想要手动关联文件数据到你的模型中的 [`FileField`](#django.db.models.FileField "django.db.models.FileField")实例, 则`save()` 方法总是用来保存该数据. + +方法接受两个必选参数: `name` 文件名, 和 `content` 文件内容.可选参数`save` 控制模型实例在关联的文件被修改时是否保存.默认为 `True`. + +注意参数 `content` 应该是 [`django.core.files.File`](../files/file.html#django.core.files.File "django.core.files.File")的一个实例, 而不是Python内建的File对象.你可以用如下方法从一个已经存在的Python文件对象来构建 [`File`](../files/file.html#django.core.files.File "django.core.files.File") : + + + + + +``` +from django.core.files import File +# Open an existing file using Python's built-in open() +f = open('/tmp/hello.world') +myfile = File(f) + +``` + + + + + +或者,你可以像下面的一样从一个python字符串中构建 + + + + + +``` +from django.core.files.base import ContentFile +myfile = ContentFile("hello world") + +``` + + + + + +更多信息, 请参见 [_Managing files_](../../topics/files.html). + + + +`FieldFile.delete`(_save=True_)[[source]](../../_modules/django/db/models/fields/files.html#FieldFile.delete) + + + +删除与此实例关联的文件,并清除该字段的所有属性。注意︰ 如果它碰巧是开放的调用 `delete()` 方法 时,此方法将关闭该文件。 + +模型实例`save`的文件与此字段关联的可选 保存 参数控件已被删除。默认值为 `True`。 + +注意,model删除的时候,与之关联的文件并不会被删除。如果你要把文件也清理掉,你需要自己处理。 + + + + + + + +### FilePathField + + + +_class_ `FilePathField`(_path=None_[, _match=None_, _recursive=False_, _max_length=100_, _**options_]) + + + +一个?[`CharField`](#django.db.models.CharField "django.db.models.CharField") ,内容只限于文件系统内特定目录下的文件名。有三个参数, 其中第一个是 **必需的**: + + + +`FilePathField.path` + + + +必填。这个[`FilePathField`](#django.db.models.FilePathField "django.db.models.FilePathField") 应该得到其选择的目录的绝对文件系统路径。例如: `"/home/images"`. + + + + + + + +`FilePathField.match` + + + +可选的.[`FilePathField`](#django.db.models.FilePathField "django.db.models.FilePathField") 将会作为一个正则表达式来匹配文件名。但请注意正则表达式将将被作用于基本文件名,而不是完整路径。例如: `"foo.*.txt$"`, 将会匹配到一个名叫 `foo23.txt` 的文件,但不匹配到 `bar.txt` 或者 `foo23.png`. + + + + + + + +`FilePathField.recursive` + + + +可选的.`True` 或 `False`.默认是`False`.声明是否包含所有子目录的[`路径`](#django.db.models.FilePathField.path "django.db.models.FilePathField.path") + + + + + + + +`FilePathField.allow_files` + + + +可选的.`True` 或 `False`.默认是`True`.声明是否包含指定位置的文件。该参数或[`allow_folders`](#django.db.models.FilePathField.allow_folders "django.db.models.FilePathField.allow_folders") 中必须有一个为 `True`. + + + + + + + +`FilePathField.allow_folders` + + + +是可选的.输入 `True` 或者 `False`.默认值为 `False`.声明是否包含指定位置的文件夹。该参数或 [`allow_files`](#django.db.models.FilePathField.allow_files "django.db.models.FilePathField.allow_files") 中必须有一个为 `True`. + + + + + +当然,这些参数可以同时使用。 + +有一点需要提醒的是 [`match`](#django.db.models.FilePathField.match "django.db.models.FilePathField.match")只匹配基本文件名(base filename), 而不是整个文件路径(full path). 例如: + + + + + +``` +FilePathField(path="/home/images", match="foo.*", recursive=True) + +``` + + + + + +...将匹配`/home/images/foo.png`而不是`/home/images/foo/bar.png` 因为只允许[`匹配`](#django.db.models.FilePathField.match "django.db.models.FilePathField.match") 基本文件名(`foo.png` 和 `bar.png`). + +[`FilePathField`](#django.db.models.FilePathField "django.db.models.FilePathField")实例被创建在您的数据库为`varchar`列默认最大长度为 100 个字符。作为与其他字段,您可以更改使用的[`max_length`](#django.db.models.CharField.max_length "django.db.models.CharField.max_length")最大长度。 + + + + + +### FloatField + + + +_class_ `FloatField`([_**options_]) + + + +用Python的一个`float` 实例来表示一个浮点数. + +该字段的默认组件是一个 [`TextInput`](../forms/widgets.html#django.forms.TextInput "django.forms.TextInput"). + + + +`FloatField` 与`DecimalField` + +有时候[`FloatField`](#django.db.models.FloatField "django.db.models.FloatField") 类会和[`DecimalField`](#django.db.models.DecimalField "django.db.models.DecimalField") 类发生混淆. 虽然它们表示都表示实数,但是二者表示数字的方式不一样。`FloatField` 使用的是Python内部的 `float` 类型, 而`DecimalField` 使用的是Python的 `Decimal` 类型. 想要了解更多二者的差别, 可以查看Python文档中的 [`decimal`](https://docs.python.org/3/library/decimal.html#module-decimal "(in Python v3.4)") 模块. + + + + + + + +### ImageField + + + +_class_ `ImageField`([_upload_to=None_, _height_field=None_, _width_field=None_, _max_length=100_, _**options_]) + + + +继承了 [`FileField`](#django.db.models.FileField "django.db.models.FileField")的所有属性和方法, 但还对上传的对象进行校验,确保它是个有效的image. + +除了从[`FileField`](#django.db.models.FileField "django.db.models.FileField")继承来的属性外,[`ImageField`](#django.db.models.ImageField "django.db.models.ImageField") 还有`宽`和 `高`属性。 + +为了更便捷的去用那些属性值, [`ImageField`](#django.db.models.ImageField "django.db.models.ImageField") 有两个额外的可选参数 + + + +`ImageField.height_field` + + + +该属性的设定会在模型实例保存时,自动填充图片的高度. + + + + + + + +`ImageField.width_field` + + + +该属性的设定会在模型实例保存时,自动填充图片的宽度. + + + + + +ImageField字段需要调用[Pillow](http://pillow.readthedocs.org/en/latest/) 库. + +[`ImageField`](#django.db.models.ImageField "django.db.models.ImageField")会创建在你的数据库中 和 `varchar` 一样,默认最大长度为100和其他字段一样, 你可以使用[`max_length`](#django.db.models.CharField.max_length "django.db.models.CharField.max_length") 参数来设置默认文件最大值. + +此字段的默认表单工具是[`ClearableFileInput`](../forms/widgets.html#django.forms.ClearableFileInput "django.forms.ClearableFileInput"). + + + + + +### IntegerField + + + +_class_ `IntegerField`([_**options_]) + + + +一个整数。在Django所支持的所有数据库中,从 `-2147483648` 到 `2147483647` 范围内的值是合法的。默认的表单输入工具是[`TextInput`](../forms/widgets.html#django.forms.TextInput "django.forms.TextInput"). + + + + + +### IPAddressField + + + +_class_ `IPAddressField`([_**options_]) + + + + + +Deprecated since version 1.7: 该字段已废弃,从1.7开始支持[`GenericIPAddressField`](#django.db.models.GenericIPAddressField "django.db.models.GenericIPAddressField"). + + + +IP地址,会自动格式化(例如:“192.0.2.30”)。默认表单控件为 [`TextInput`](../forms/widgets.html#django.forms.TextInput "django.forms.TextInput"). + + + + + +### GenericIPAddressField + + + +_class_ `GenericIPAddressField`([_protocol=both_, _unpack_ipv4=False_, _**options_]) + + + +一个 IPv4 或 IPv6 地址, 字符串格式 (例如 `192.0.2.30` 或 `2a02:42fe::4`). 这个字段的默认表单小部件是一个[`TextInput`](../forms/widgets.html#django.forms.TextInput "django.forms.TextInput"). + +IPv6 地址会根据 [**RFC 4291**](http://tools.ietf.org/html/rfc4291.html#section-2.2) 章节 2.2所规范, 包括该章节中第三段的的IPv4格式建议, 就像 `::ffff:192.0.2.0`这样. 例如, `2001:0::0:01` 将会被规范成 `2001::1`, `::ffff:0a0a:0a0a` 被规范成 `::ffff:10.10.10.10`. 所有字符都会被转换成小写. + + + +`GenericIPAddressField.protocol` + + + +限制有效输入的协议类型. 允许的值是 `'both'` (默认值), `'IPv4'` 或 `'IPv6'`. 匹配不区分大小写. + + + + + + + +`GenericIPAddressField.unpack_ipv4` + + + +解析IPv4映射地址如 `::ffff:192.0.2.1`.如果启用该选项,则地址将被解析到`192.0.2.1`.默认为禁用。只有当`协议` 设置为`'both'`时才可以使用。 + + + + + +如果允许空白值,则必须允许null值,因为空白值存储为null。 + + + + + +### NullBooleanField + + + +_class_ `NullBooleanField`([_**options_]) + + + +类似[`BooleanField`](#django.db.models.BooleanField "django.db.models.BooleanField"), 但是允许 `NULL` 作为一个选项.使用此代替`null=True`的[`BooleanField`](#django.db.models.BooleanField "django.db.models.BooleanField")。此字段的默认表单widget为[`NullBooleanSelect`](../forms/widgets.html#django.forms.NullBooleanSelect "django.forms.NullBooleanSelect")。 + + + + + +### PositiveIntegerField(正整数字段) + + + +_class_ `PositiveIntegerField`([_**options_]) + + + +类似 [`IntegerField`](#django.db.models.IntegerField "django.db.models.IntegerField"), 但值必须是正数或者零(`0`). 从`0`到`2147483647`的值在Django支持的所有数据库中都是安全的。由于向后兼容性原因,接受值`0`。 + + + + + +### PositiveSmallIntegerField + + + +_class_ `PositiveSmallIntegerField`([_**options_]) + + + +该模型字段类似 [`PositiveIntegerField`](#django.db.models.PositiveIntegerField "django.db.models.PositiveIntegerField"), 但是只允许小于某一特定值(依据数据库类型而定)。从`0` 到 `32767` 这个区间,对于Django所支持的所有数据库而言都是安全的。 + + + + + +### SlugField + + + +_class_ `SlugField`([_max_length=50_, _**options_]) + + + +[_Slug_](../../glossary.html#term-slug) 是一个新闻术语(通常叫做短标题)。一个slug只能包含字母、数字、下划线或者是连字符,通常用来作为短标签。通常它们是用来放在URL里的。 + +像CharField一样,你可以指定[`max_length`](#django.db.models.CharField.max_length "django.db.models.CharField.max_length")(也请参阅该部分中的有关数据库可移植性的说明和[`max_length`](#django.db.models.CharField.max_length "django.db.models.CharField.max_length"))。如果没有指定 [`max_length`](#django.db.models.CharField.max_length "django.db.models.CharField.max_length"), Django将会默认长度为50。 + +Implies setting [`Field.db_index`](#django.db.models.Field.db_index "django.db.models.Field.db_index") to `True`. + +根据某些其他值的值自动预填充SlugField通常很有用。你可以在admin中使用[`prepopulated_fields`](../contrib/admin/index.html#django.contrib.admin.ModelAdmin.prepopulated_fields "django.contrib.admin.ModelAdmin.prepopulated_fields")自动执行此操作。 + + + + + +### SmallIntegerField + + + +_class_ `SmallIntegerField`([_**options_]) + + + +与 [`IntegerField`](#django.db.models.IntegerField "django.db.models.IntegerField")这个字段类型很类似,不同的是SmallInteger类型只能在一个确定的范围内(数据库依赖)。对于django来讲,该字段值在 `-32768` 至 `32767`这个范围内对所有可支持的数据库都是安全的。 + + + + + +### TextField + + + +_class_ `TextField`([_**options_]) + + + +大文本字段。该模型默认的表单组件是[`Textarea`](../forms/widgets.html#django.forms.Textarea "django.forms.Textarea")。 + +Changed in Django 1.7: + +如果你在这个字段类型中使用了`max_length`属性,它将会在渲染页面表单元素[`Textarea`](../forms/widgets.html#django.forms.Textarea "django.forms.Textarea") 时候体现出来。但是并不会在model或者数据库级别强制性的限定字段长度。请使用[`CharField`](#django.db.models.CharField "django.db.models.CharField")。 + + + + + +MySQL 用户 + +如果你将此字段用于MySQLdb 1.2.1p2和`utf8_bin`排序规则(这_不是_默认值),则需要注意一些问题。有关详细信息,请参阅[_MySQL数据库注释_](../databases.html#mysql-collation)。 + + + + + + + +### TimeField + + + +_class_ `TimeField`([_auto_now=False_, _auto_now_add=False_, _**options_]) + + + +时间字段,和Python中 `datetime.time` 一样。接受与[`DateField`](#django.db.models.DateField "django.db.models.DateField")相同的自动填充选项。 + +表单默认为 [`TextInput`](../forms/widgets.html#django.forms.TextInput "django.forms.TextInput").输入框。Admin添加一些JavaScript快捷方式。 + + + + + +### URLField + + + +_class_ `URLField`([_max_length=200_, _**options_]) + + + +一个[`CharField`](#django.db.models.CharField "django.db.models.CharField") 类型的URL + +此字段的默认表单widget为[`TextInput`](../forms/widgets.html#django.forms.TextInput "django.forms.TextInput")。 + +与所有[`CharField`](#django.db.models.CharField "django.db.models.CharField")子类一样,[`URLField`](#django.db.models.URLField "django.db.models.URLField")接受可选的[`max_length`](#django.db.models.CharField.max_length "django.db.models.CharField.max_length")参数。如果不指定[`max_length`](#django.db.models.CharField.max_length "django.db.models.CharField.max_length"),则使用默认值200。 + + + + + +### UUIDField + +New in Django 1.8. + + + +_class_ `UUIDField`([_**options_]) + + + +一个用来存储UUID的字段。使用Python的[`UUID`](https://docs.python.org/3/library/uuid.html#uuid.UUID "(in Python v3.4)")类。 当使用PostgreSQL数据库时,该字段类型对应的数据库中的数据类型是`uuid`,使用其他数据库时,数据库对应的是`char(32)`类型。 + +使用UUID类型相对于使用具有[`primary_key`](#django.db.models.Field.primary_key "django.db.models.Field.primary_key")参数的[`AutoField`](#django.db.models.AutoField "django.db.models.AutoField")类型是一个更好的解决方案。 数据库不会自动生成UUID,所以推荐使用[`default`](#django.db.models.Field.default "django.db.models.Field.default")参数: + + + + + +``` +import uuid +from django.db import models + +class MyUUIDModel(models.Model): + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + # other fields + +``` + + + + + +注意:这里传递给default是一个可调用的对象(即一个省略了括号的方法),而不是传递一个`UUID`实例给`default` + + + + + + + +## 关系字段 + +Django 同样定义了一系列的字段来描述数据库之间的关联。 + + + +### ForeignKey + + + +_class_ `ForeignKey`(_othermodel_[, _**options_]) + + + +多对一关系。需要一个位置参数:与该模型关联的类。 + +若要创建一个递归的关联 —— 对象与自己具有多对一的关系 —— 请使用`models.ForeignKey('self')`。 + +如果你需要关联到一个还没有定义的模型,你可以使用模型的名字而不用模型对象本身: + + + + + +``` +from django.db import models + +class Car(models.Model): + manufacturer = models.ForeignKey('Manufacturer') + # ... + +class Manufacturer(models.Model): + # ... + pass + +``` + + + + + +若要引用在其它应用中定义的模型,你可以用带有完整标签名的模型来显式指定。例如,如果上面提到的`Manufacturer` 模型是在一个名为`production` 的应用中定义的,你应该这样使用它: + + + + + +``` +class Car(models.Model): + manufacturer = models.ForeignKey('production.Manufacturer') + +``` + + + + + +在解析两个应用之间具有相互依赖的导入时,这种引用将会很有帮助。 + +`ForeignKey` 会自动创建数据库索引。你可以通过设置[`db_index`](#django.db.models.Field.db_index "django.db.models.Field.db_index") 为`False` 来取消。如果你创建外键是为了一致性而不是用来Join,或者如果你将创建其它索引例如部分或多列索引,你也许想要避免索引的开销。 + + + +警告 + +不建议从一个没有迁移的应用中创建一个`ForeignKey` 到一个具有迁移的应用。更多详细信息,请参阅[_依赖性文档_](../../topics/migrations.html#unmigrated-dependencies)。 + + + + + +#### 数据库中的表示 + +在幕后,Django 会在字段名上添加`"_id"` 来创建数据库中的列名。在上面的例子中,`Car` 模型的数据库表将会拥有一个`manufacturer_id` 列。(你可以通过显式指定[`db_column`](#django.db.models.Field.db_column "django.db.models.Field.db_column")?改变它)。但是,你的代码永远不应该处理数据库中的列名称,除非你需要编写自定义的SQL。你应该永远只处理你的模型对象中的字段名称。 + + + + + +#### 参数 + +[`ForeignKey`](#django.db.models.ForeignKey "django.db.models.ForeignKey") 接受额外的参数集 —— 全都是可选的 —— 它们定义关联关系如何工作的细节。 + + + +`ForeignKey.limit_choices_to` + + + +当这个字段使用`模型表单`或者Admin 渲染时(默认情况下,查询集中的所有对象都可以使用),为这个字段设置一个可用的选项。它可以是一个字典、一个[`Q`](querysets.html#django.db.models.Q "django.db.models.Q") 对象或者一个返回字典或[`Q`](querysets.html#django.db.models.Q "django.db.models.Q")对象的可调用对象。 + +例如: + + + + + +``` +staff_member = models.ForeignKey(User, limit_choices_to={'is_staff': True}) + +``` + + + + + +将使得`模型表单` 中对应的字段只列出`is_staff=True` 的`Users`。 这在Django 的Admin 中也可能有用处。 + +可调用对象的形式同样非常有用,比如与Python 的`datetime`模块一起使用来限制选择的时间范围。例如: + + + + + +``` +def limit_pub_date_choices(): + return {'pub_date__lte': datetime.date.utcnow()} + +limit_choices_to = limit_pub_date_choices + +``` + + + + + +如果`limit_choices_to` 自己本身是或者返回一个用于[_复杂查询_](../../topics/db/queries.html#complex-lookups-with-q)的[`Q 对象`](querysets.html#django.db.models.Q "django.db.models.Q"),当字段没有在模型的`ModelAdmin`中的[`raw_id_fields`](../contrib/admin/index.html#django.contrib.admin.ModelAdmin.raw_id_fields "django.contrib.admin.ModelAdmin.raw_id_fields") 列出时,它将只会影响Admin中的可用的选项。 + +Changed in Django 1.7: + +以前的Django 版本不允许传递一个可调用的对象给`limit_choices_to`。 + + + + + +注 + +如果`limit_choices_to` 使用可调用对象,这个可调用对象将在每次创建一个新表单的时候都调用。它还可能在一个模型校验的时候调用,例如被管理命令或者Admin。Admin 多次构造查询集来验证表单在各种边缘情况下的输入,所以你的可调用对象可能调用多次。 + + + + + + + + + +`ForeignKey.related_name` + + + +这个名称用于让关联的对象反查到源对象。它还是[`related_query_name`](#django.db.models.ForeignKey.related_query_name "django.db.models.ForeignKey.related_query_name") 的默认值(关联的模型进行反向过滤时使用的名称)。完整的解释和示例参见[_关联对象的文档_](../../topics/db/queries.html#backwards-related-objects)。注意,当你为[_抽象模型_](../../topics/db/models.html#abstract-base-classes)定义关联关系的时,必须设置这个参数的值;而且当你这么做的时候需要用到[_一些特殊语法_](../../topics/db/models.html#abstract-related-name)。 + +如果你不想让Django 创建一个反向关联,请设置`related_name` 为 `'+'` 或者以`'+'` 结尾。 例如,下面这行将确定`User` 模型将不会有到这个模型的返回关联: + + + + + +``` +user = models.ForeignKey(User, related_name='+') + +``` + + + + + + + + + + + +`ForeignKey.related_query_name` + + + +这个名称用于目标模型的反向过滤。如果设置了[`related_name`](#django.db.models.ForeignKey.related_name "django.db.models.ForeignKey.related_name"),则默认为它的值,否则默认值为模型的名称: + + + + + +``` +# Declare the ForeignKey with related_query_name +class Tag(models.Model): + article = models.ForeignKey(Article, related_name="tags", related_query_name="tag") + name = models.CharField(max_length=255) + +# That's now the name of the reverse filter +Article.objects.filter(tag__name="important") + +``` + + + + + + + + + + + +`ForeignKey.to_field` + + + +关联到的关联对象的字段名称。默认地,Django 使用关联对象的主键。 + + + + + + + +`ForeignKey.db_constraint` + + + +控制是否在数据库中为这个外键创建约束。默认值为`True`,而且这几乎一定是你想要的效果;设置成`False` 对数据的完整性来说是很糟糕的。即便如此,有一些场景你也许想要这么设置: + +* 你有遗留的无效数据。 +* 你正在对数据库缩容。 + +如果被设置成`False`,访问一个不存在的关联对象将抛出 `DoesNotExist` 异常。 + + + + + + + +`ForeignKey.on_delete` + + + +当一个[`ForeignKey`](#django.db.models.ForeignKey "django.db.models.ForeignKey") 引用的对象被删除时,Django 默认模拟SQL 的`ON DELETE CASCADE` 的约束行为,并且删除包含该`ForeignKey`的对象。这种行为可以通过设置[`on_delete`](#django.db.models.ForeignKey.on_delete "django.db.models.ForeignKey.on_delete") 参数来改变。例如,如果你有一个可以为空的[`ForeignKey`](#django.db.models.ForeignKey "django.db.models.ForeignKey"),在其引用的对象被删除的时你想把这个ForeignKey 设置为空: + + + + + +``` +user = models.ForeignKey(User, blank=True, null=True, on_delete=models.SET_NULL) + +``` + + + + + + + + + +[`on_delete`](#django.db.models.ForeignKey.on_delete "django.db.models.ForeignKey.on_delete") 在[`django.db.models`](../../topics/db/models.html#module-django.db.models "django.db.models")中可以找到的值有: + +* + + `CASCADE` + + + + 级联删除;默认值。 + + + + + +* + + `PROTECT` + + + + 抛出[`ProtectedError`](../exceptions.html#django.db.models.ProtectedError "django.db.models.ProtectedError") 以阻止被引用对象的删除,它是[`django.db.IntegrityError`](../exceptions.html#django.db.IntegrityError "django.db.IntegrityError") 的一个子类。 + + + + + +* + + `SET_NULL` + + + + 把[`ForeignKey`](#django.db.models.ForeignKey "django.db.models.ForeignKey") 设置为null; [`null`](#django.db.models.Field.null "django.db.models.Field.null") 参数为`True` 时才可以这样做。 + + + + + +* + + `SET_DEFAULT` + + + + [`ForeignKey`](#django.db.models.ForeignKey "django.db.models.ForeignKey") 值设置成它的默认值;此时必须设置[`ForeignKey`](#django.db.models.ForeignKey "django.db.models.ForeignKey") 的default 参数。 + + + + + +* + + `SET`() + + + + 设置[`ForeignKey`](#django.db.models.ForeignKey "django.db.models.ForeignKey") 为传递给[`SET()`](#django.db.models.SET "django.db.models.SET") 的值,如果传递的是一个可调用对象,则为调用后的结果。在大部分情形下,传递一个可调用对象用于避免models.py 在导入时执行查询: + + + + + + ``` +from django.conf import settings + from django.contrib.auth import get_user_model + from django.db import models + + def get_sentinel_user(): + return get_user_model().objects.get_or_create(username='deleted')[0] + + class MyModel(models.Model): + user = models.ForeignKey(settings.AUTH_USER_MODEL, + on_delete=models.SET(get_sentinel_user)) + +``` + + + + + + + + + +* + + `DO_NOTHING` + + + + 不采取任何动作。如果你的数据库后端强制引用完整性,它将引发一个[`IntegrityError`](../exceptions.html#django.db.IntegrityError "django.db.IntegrityError") ,除非你手动添加一个`ON DELETE` 约束给数据库自动(可能要用到[_初始化的SQL_](../../howto/initial-data.html#initial-sql))。 + + + + + + + +`ForeignKey.swappable` + + + +New in Django 1.7. + +控制迁移框架的的重复行为如果该[`ForeignKey`](#django.db.models.ForeignKey "django.db.models.ForeignKey") 指向一个可切换的模型。如果它是默认值`True`,那么如果[`ForeignKey`](#django.db.models.ForeignKey "django.db.models.ForeignKey") 指向的模型与`settings.AUTH_USER_MODEL` 匹配(或其它可切换的模型),则保存在迁移中的关联关系将使用对setting 中引用而不是直接对模型的引用。 + +只有当你确定你的模型将永远指向切换后的模型 —— 例如如果它是专门为你的自定义用户模型设计的模型时,你才会想将它设置成`False`。 + +设置为`False` 并不表示你可以引用可切换的模型即使在它被切换出去之后 —— `False` 只是表示生成的迁移中ForeignKey 将始终引用你指定的准确模型(所以,如果用户试图允许一个你不支持的User 模型时将会失败)。 + +如果有疑问,请保留它的默认值`True`。 + + + + + + + +`ForeignKey.allow_unsaved_instance_assignment` + + + +New in Django 1.8: + +添加这个标志是为了向后兼容,因为老版本的Django 始终允许赋值未保存的模型实例。 + + + +Django 阻止未保存的模型实例被分配给一个`ForeignKey` 字段以防止意味的数据丢失(当保存一个模型实例时,未保存的外键将默默忽略)。 + +如果你需要允许赋值未保存的实例且不关心数据的丢失(例如你不会保存对象到数据库),你可以通过创建这个字段的子类并设置其`allow_unsaved_instance_assignment` 属性为`True` 来关闭这个检查。例如: + + + + + +``` +class UnsavedForeignKey(models.ForeignKey): + # A ForeignKey which can point to an unsaved object + allow_unsaved_instance_assignment = True + +class Book(models.Model): + author = UnsavedForeignKey(Author) + +``` + + + + + + + + + + + + + + + +### ManyToManyField + + + +_class_ `ManyToManyField`(_othermodel_[, _**options_]) + + + +一个多对多关联。要求一个关键字参数:与该模型关联的类,与[`ForeignKey`](#django.db.models.ForeignKey "django.db.models.ForeignKey") 的工作方式完全一样,包括[_递归关系_](#recursive-relationships) 和[_惰性关系_](#lazy-relationships)。 + +关联的对象可以通过字段的[`RelatedManager`](relations.html#django.db.models.fields.related.RelatedManager "django.db.models.fields.related.RelatedManager") 添加、删除和创建。 + + + +警告 + +不建议从一个没有迁移的应用中创建一个`ManyToManyField`到一个具有迁移的应用。 更多细节参见[_依赖性文档_](../../topics/migrations.html#unmigrated-dependencies)。 + + + + + +#### 数据库中的表示 + +在幕后,Django 创建一个中间表来表示多对多关系。默认情况下,这张中间表的名称使用多对多字段的名称和包含这张表的模型的名称生成。因为某些数据库支持的表的名字的长度有限制,这些标的名称将自动截短到64 个字符并加上一个唯一性的哈希值。这意味着,你看的表的名称可能类似 `author_books_9cdf4`;这再正常不过了。你可以使用[`db_table`](#django.db.models.ManyToManyField.db_table "django.db.models.ManyToManyField.db_table") 选项手工提供中间表的名称。 + + + + + +#### 参数 + +[`ManyToManyField`](#django.db.models.ManyToManyField "django.db.models.ManyToManyField") 接收一个参数集来控制关联关系的功能 —— 它们都是可选的。 + + + +`ManyToManyField.related_name` + + + +与[`ForeignKey.related_name`](#django.db.models.ForeignKey.related_name "django.db.models.ForeignKey.related_name") 相同。 + + + + + + + +`ManyToManyField.related_query_name` + + + +与[`ForeignKey.related_query_name`](#django.db.models.ForeignKey.related_query_name "django.db.models.ForeignKey.related_query_name") 相同。 + + + + + + + +`ManyToManyField.limit_choices_to` + + + +与[`ForeignKey.limit_choices_to`](#django.db.models.ForeignKey.limit_choices_to "django.db.models.ForeignKey.limit_choices_to") 相同。 + +`limit_choices_to` 对于使用[`through`](#django.db.models.ManyToManyField.through "django.db.models.ManyToManyField.through") 参数自定义中间表的`ManyToManyField` 不生效。 + + + + + + + +`ManyToManyField.symmetrical` + + + +只用于与自身进行关联的ManyToManyField。例如下面的模型: + + + + + +``` +from django.db import models + +class Person(models.Model): + friends = models.ManyToManyField("self") + +``` + + + + + +当Django 处理这个模型的时候,它定义该模型具有一个与自身具有多对多关联的[`ManyToManyField`](#django.db.models.ManyToManyField "django.db.models.ManyToManyField"),因此它不会向`Person` 类添加`person_set` 属性。Django 将假定这个[`ManyToManyField`](#django.db.models.ManyToManyField "django.db.models.ManyToManyField") 字段是对称的 —— 如果我是你的朋友,那么你也是我的朋友。 + +如果你希望与`self` 进行多对多关联的关系不具有对称性,可以设置[`symmetrical`](#django.db.models.ManyToManyField.symmetrical "django.db.models.ManyToManyField.symmetrical") 为`False`。这会强制让Django 添加一个描述器给反向的关联关系,以使得[`ManyToManyField`](#django.db.models.ManyToManyField "django.db.models.ManyToManyField") 的关联关系不是对称的。 + + + + + + + +`ManyToManyField.through` + + + +Django 会自动创建一个表来管理多对多关系。不过,如果你希望手动指定中介表,可以使用[`through`](#django.db.models.ManyToManyField.through "django.db.models.ManyToManyField.through") 选项来指定Django 模型来表示你想要使用的中介表。 + +这个选项最常见的使用场景是当你想要关联[_额外的数据到多对多关联关系_](../../topics/db/models.html#intermediary-manytomany)的时候。 + +如果你没有显式指定`through` 的模型,仍然会有一个隐式的`through` 模型类,你可以用它来直接访问对应的表示关联关系的数据库表。它由三个字段来链接模型。 + +如果源模型和目标不同,则生成以下字段: + +* `id`:关系的主键。 +* `_id`:声明`ManyToManyField` 字段的模型的`id`。 +* `_id`:`ManyToManyField` 字段指向的模型的`id`。 + +如果`ManyToManyField` 的源模型和目标模型相同,则生成以下字段: + +* `id`:关系的主键。 +* `from__id`:源模型实例的`id`。 +* `to__id`:目标模型实例的`id`。 + +这个类可以让一个给定的模型像普通的模型那样查询与之相关联的记录。 + + + + + + + +`ManyToManyField.through_fields` + + + +New in Django 1.7. + +只能在指定了自定义中间模型的时候使用。 Django 一般情况会自动决定使用中间模型的哪些字段来建立多对多关联。但是,考虑如下模型: + + + + + +``` +from django.db import models + +class Person(models.Model): + name = models.CharField(max_length=50) + +class Group(models.Model): + name = models.CharField(max_length=128) + members = models.ManyToManyField(Person, through='Membership', through_fields=('group', 'person')) + +class Membership(models.Model): + group = models.ForeignKey(Group) + person = models.ForeignKey(Person) + inviter = models.ForeignKey(Person, related_name="membership_invites") + invite_reason = models.CharField(max_length=64) + +``` + + + + + +`Membership` 有_两个_ 外键指向`Person` (`person` 和`inviter`),这使得关联关系含混不清并让Django 不知道使用哪一个。在这种情况下,你必须使用`through_fields` 明确指定Django 应该使用哪些外键,就像上面例子一样。 + +`through_fields` 接收一个二元组`('field1', 'field2')`,其中`field1` 为指向定义[`ManyToManyField`](#django.db.models.ManyToManyField "django.db.models.ManyToManyField") 字段的模型的外键名称(本例中为`group`),`field2` 为指向目标模型的外键的名称(本例中为`person`)。 + +当中间模型具有多个外键指向多对多关联关系模型中的任何一个(或两个),你_必须_ 指定`through_fields`。这通用适用于[_递归的关联关系_](#recursive-relationships),当用到中间模型而有多个外键指向该模型时,或当你想显式指定Django 应该用到的两个字段时。 + +递归的关联关系使用的中间模型始终定义为非对称的,也就是[`symmetrical=False`](#django.db.models.ManyToManyField.symmetrical "django.db.models.ManyToManyField.symmetrical") —— 所以具有源和目标的概念。这种情况下,`'field1'` 将作为管理关系的源,而`'field2'` 作为目标。 + + + + + + + +`ManyToManyField.db_table` + + + +为存储多对多数据而创建的表的名称。如果没有提供,Django 将基于定义关联关系的模型和字段假设一个默认的名称。 + + + + + + + +`ManyToManyField.db_constraint` + + + +控制中间表中的外键是否创建约束。默认为`True`,而且这是几乎就是你想要的;设置为`False` 对数据完整性将非常糟糕。下面是你可能需要这样设置的一些场景: + +* 你具有不合法的遗留数据。 +* 你正在对数据库缩容。 + +不可以同时传递`db_constraint` 和 `through`。 + + + + + + + +`ManyToManyField.swappable` + + + +New in Django 1.7. + +控制[`ManyToManyField`](#django.db.models.ManyToManyField "django.db.models.ManyToManyField") 指向一个可切换的模型时迁移框架的行为。如果它是默认值`True`,那么如果[`ManyToManyField`](#django.db.models.ManyToManyField "django.db.models.ManyToManyField") 指向的模型与`settings.AUTH_USER_MODEL` 匹配(或其它可切换的模型),则保存在迁移中的关联关系将使用对setting 中引用而不是直接对模型的引用。 + +只有当你确定你的模型将永远指向切换后的模型 —— 例如如果它是专门为你的自定义用户模型设计的模型时,你才会想将它设置成`False`。 + +如果不确定,请将它保留为`True`。 + + + + + + + +`ManyToManyField.allow_unsaved_instance_assignment` + + + +New in Django 1.8. + +与[`ForeignKey.allow_unsaved_instance_assignment`](#django.db.models.ForeignKey.allow_unsaved_instance_assignment "django.db.models.ForeignKey.allow_unsaved_instance_assignment") 的工作方式类似。 + + + + + +[`ManyToManyField`](#django.db.models.ManyToManyField "django.db.models.ManyToManyField") 不支持[`validators`](#django.db.models.Field.validators "django.db.models.Field.validators")。 + +[`null`](#django.db.models.Field.null "django.db.models.Field.null") 不生效,因为无法在数据库层次要求关联关系。 + + + + + + + +### OneToOneField + + + +_class_ `OneToOneField`(_othermodel_[, _parent_link=False_, _**options_]) + + + +一对一关联关系。概念上讲,这个字段很像是[`ForeignKey`](#django.db.models.ForeignKey "django.db.models.ForeignKey") 设置了[`unique=True`](#django.db.models.Field.unique "django.db.models.Field.unique"),不同的是它会直接返回关系另一边的单个对象。 + +它最主要的用途是作为扩展自另外一个模型的主键;例如,[_多表继承_](../../topics/db/models.html#multi-table-inheritance)就是通过对子模型添加一个隐式的一对一关联关系到父模型实现的。 + +需要一个位置参数:与该模型关联的类。 它的工作方式与[`ForeignKey`](#django.db.models.ForeignKey "django.db.models.ForeignKey") 完全一致,包括所有与[_递归关系_](#recursive-relationships)和[_惰性关系_](#lazy-relationships)相关的选项。 + +如果你没有指定`OneToOneField` 的[`related_name`](#django.db.models.ForeignKey.related_name "django.db.models.ForeignKey.related_name") 参数,Django 将使用当前模型的小写的名称作为默认值。 + +例如下面的例子: + + + + + +``` +from django.conf import settings +from django.db import models + +class MySpecialUser(models.Model): + user = models.OneToOneField(settings.AUTH_USER_MODEL) + supervisor = models.OneToOneField(settings.AUTH_USER_MODEL, related_name='supervisor_of') + +``` + + + + + +你将使得`User` 模型具有以下属性: + + + + + +``` +>>> user = User.objects.get(pk=1) +>>> hasattr(user, 'myspecialuser') +True +>>> hasattr(user, 'supervisor_of') +True + +``` + + + + + +当反向访问关联关系时,如果关联的对象不存在对应的实例,则抛出`DoesNotExist` 异常。例如,如果一个User 没有`MySpecialUser` 指定的supervisor: + + + + + +``` +>>> user.supervisor_of +Traceback (most recent call last): + ... +DoesNotExist: User matching query does not exist. + +``` + + + + + +另外,`OneToOneField` 除了接收[`ForeignKey`](#django.db.models.ForeignKey "django.db.models.ForeignKey") 接收的所有额外的参数之外,还有另外一个参数: + + + +`OneToOneField.parent_link` + + + +当它为`True` 并在继承自另一个[_具体模型_](../../glossary.html#term-concrete-model) 的模型中使用时,表示该字段应该用于反查的父类的链接,而不是在子类化时隐式创建的`OneToOneField`。 + + + + + +`OneToOneField` 的使用示例参见[_One-to-one 关联关系_](../../topics/db/examples/one_to_one.html)。 + + + + + + + +## Field API 参考 + + + +_class_ `Field` + + + +`Field` 是一个抽象的类, 用来代表数据库中的表的一列. Django 使用这些fields 去创建表 ([`db_type()`](#django.db.models.Field.db_type "django.db.models.Field.db_type")), 去建立Python中的类型和数据库中类型的映射关系 ([`get_prep_value()`](#django.db.models.Field.get_prep_value "django.db.models.Field.get_prep_value")) 反之亦然 ([`from_db_value()`](#django.db.models.Field.from_db_value "django.db.models.Field.from_db_value")), 并且实现[_Lookup API reference_](lookups.html) ([`get_prep_lookup()`](#django.db.models.Field.get_prep_lookup "django.db.models.Field.get_prep_lookup")). + +field 是不同Django版本API中最根本的部分,尤其是[`models`](instances.html#django.db.models.Model "django.db.models.Model") and [`querysets`](querysets.html#django.db.models.query.QuerySet "django.db.models.query.QuerySet"). + +在模型中,一个字段被实例化为类的属性,并表现为一个特定的表的列,详情查看[_Models_](../../topics/db/models.html). 它具有[`null`](#django.db.models.Field.null "django.db.models.Field.null")和[`唯一`](#django.db.models.Field.unique "django.db.models.Field.unique")等属性,以及Django用于将字段值映射到数据库特定值的方法。 + +`字段`是[`RegisterLookupMixin`](lookups.html#django.db.models.lookups.RegisterLookupMixin "django.db.models.lookups.RegisterLookupMixin")的子类,因此可以在其上注册[`Transform`](lookups.html#django.db.models.Transform "django.db.models.Transform")和[`Lookup`](lookups.html#django.db.models.Lookup "django.db.models.Lookup") `QuerySet` s(例如`field_name__exact =“foo”`)。默认情况下,所有[_内置查找_](querysets.html#field-lookups)都已注册。 + +Django的所有内建字段,如[`CharField`](#django.db.models.CharField "django.db.models.CharField")都是`Field`的特定实现。如果您需要自定义字段,则可以将任何内置字段子类化,也可以从头开始写入`字段`。无论哪种情况,请参阅[_编写自定义模型字段_](../../howto/custom-model-fields.html)。 + + + +`description` + + + +字段的详细说明,例如用于[`django.contrib.admindocs`](../contrib/admin/admindocs.html#module-django.contrib.admindocs "django.contrib.admindocs: Django's admin documentation generator.")应用程序。 + +描述可以是以下形式: + + + + + +``` +description = _("String (up to %(max_length)s)") + +``` + + + + + +其中参数从字段的`__ dict __`插入。 + + + + + +To map a `Field` to a database-specific type, Django exposes two methods: + + + +`get_internal_type`() + + + +返回一个字符串,命名此字段以用于后端特定用途。默认情况下,它返回类名。 + +有关自定义字段中的用法,请参见[_模拟内置字段类型_](../../howto/custom-model-fields.html#emulating-built-in-field-types)。 + + + + + + + +`db_type`(_connection_) + + + +返回[`字段`](#django.db.models.Field "django.db.models.Field")的数据库列数据类型,同时考虑`连接`。 + +有关自定义字段中的用法,请参见[_自定义数据库类型_](../../howto/custom-model-fields.html#custom-database-types)。 + + + + + +有三种主要情况,Django需要与数据库后端和字段交互: + +* 当它查询数据库(Python值 转为 数据库后端值) +* 当它从数据库加载数据(数据库后端值 转为 Python值) +* 当它保存到数据库(Python值 转为 数据库后端值) + +查询时,使用[`get_db_prep_value()`](#django.db.models.Field.get_db_prep_value "django.db.models.Field.get_db_prep_value")和[`get_prep_value()`](#django.db.models.Field.get_prep_value "django.db.models.Field.get_prep_value"): + + + +`get_prep_value`(_value_) + + + +`value`是模型属性的当前值,方法应以已准备好用作查询中的参数的格式返回数据。 + +有关使用方式,请参阅[_将Python对象转换为查询值_](../../howto/custom-model-fields.html#converting-python-objects-to-query-values)。 + + + + + + + +`get_db_prep_value`(_value_, _connection_, _prepared=False_) + + + +将`值`转换为后端特定值。如果`prepared = True`和[`get_prep_value()`](#django.db.models.Field.get_prep_value "django.db.models.Field.get_prep_value") if为`False`,则默认返回`值`。 + +有关用法,请参见[_将查询值转换为数据库值_](../../howto/custom-model-fields.html#converting-query-values-to-database-values)。 + + + + + +加载数据时,使用[`from_db_value()`](#django.db.models.Field.from_db_value "django.db.models.Field.from_db_value"): + + + +`from_db_value`(_value_, _expression_, _connection_, _context_) + + + +New in Django 1.8. + +将数据库返回的值转换为Python对象。它与[`get_prep_value()`](#django.db.models.Field.get_prep_value "django.db.models.Field.get_prep_value")相反。 + +此方法不用于大多数内置字段,因为数据库后端已返回正确的Python类型,或后端本身执行转换。 + +有关用法,请参见[_将值转换为Python对象_](../../howto/custom-model-fields.html#converting-values-to-python-objects)。 + + + +注意 + +出于性能原因,`from_db_value`在不需要它的字段(所有Django字段)上不实现为无操作。因此,您不能在定义中调用`super`。 + + + + + + + +保存时,使用[`pre_save()`](#django.db.models.Field.pre_save "django.db.models.Field.pre_save")和[`get_db_prep_save()`](#django.db.models.Field.get_db_prep_save "django.db.models.Field.get_db_prep_save") + + + +`get_db_prep_save`(_value_, _connection_) + + + +与[`get_db_prep_value()`](#django.db.models.Field.get_db_prep_value "django.db.models.Field.get_db_prep_value")相同,但在字段值必须_保存到数据库_时调用。默认返回[`get_db_prep_value()`](#django.db.models.Field.get_db_prep_value "django.db.models.Field.get_db_prep_value")。 + + + + + + + +`pre_save`(_model_instance_, _add_) + + + +在[`get_db_prep_save()`](#django.db.models.Field.get_db_prep_save "django.db.models.Field.get_db_prep_save")之前调用方法以在保存之前准备值(例如,对于[`DateField.auto_now`](#django.db.models.DateField.auto_now "django.db.models.DateField.auto_now"))。 + +`model_instance`是此字段所属的实例,`add`是实例是否第一次保存到数据库。 + +它应该返回此字段的`model_instance`适当属性的值。属性名称位于`self.attname`(这是由[`Field`](#django.db.models.Field "django.db.models.Field")设置)。 + +有关使用情况,请参阅[_保存前预处理值_](../../howto/custom-model-fields.html#preprocessing-values-before-saving)。 + + + + + +当在字段上使用查找时,该值可能需要“准备”。Django公开了两种方法: + + + +`get_prep_lookup`(_lookup_type_, _value_) + + + +在用于查找之前,准备`values`到数据库。The `lookup_type` will be one of the valid Django filter lookups: `"exact"`, `"iexact"`, `"contains"`, `"icontains"`, `"gt"`, `"gte"`, `"lt"`, `"lte"`, `"in"`, `"startswith"`, `"istartswith"`, `"endswith"`, `"iendswith"`, `"range"`, `"year"`, `"month"`, `"day"`, `"isnull"`, `"search"`, `"regex"`, and `"iregex"`. + +New in Django 1.7. + +如果你使用[_自定义查找_](lookups.html),则`lookup_type`可以是在字段中注册的任何`lookup_name`。 + +有关用法,请参见[_准备在数据库查找中使用的值_](../../howto/custom-model-fields.html#preparing-values-for-use-in-database-lookups)。 + + + + + + + +`get_db_prep_lookup`(_lookup_type_, _value_, _connection_, _prepared=False_) + + + +类似于[`get_db_prep_value()`](#django.db.models.Field.get_db_prep_value "django.db.models.Field.get_db_prep_value"),但用于执行查找。 + +与[`get_db_prep_value()`](#django.db.models.Field.get_db_prep_value "django.db.models.Field.get_db_prep_value")一样,将用于查询的特定连接作为`connection`传递。此外,`prepared`描述该值是否已经用[`get_prep_lookup()`](#django.db.models.Field.get_prep_lookup "django.db.models.Field.get_prep_lookup")准备好。 + + + + + +字段经常从不同的类型接收它们的值,从序列化或从表单。 + + + +`to_python`(_value_) + + + +改变这个值为正确的python对象。它作为[`value_to_string()`](#django.db.models.Field.value_to_string "django.db.models.Field.value_to_string")的反向操作,也在[`clean()`](instances.html#django.db.models.Model.clean "django.db.models.Model.clean")中调用。 + +有关用法,请参见[_将值转换为Python对象_](../../howto/custom-model-fields.html#converting-values-to-python-objects)。 + + + + + +除了保存到数据库,该字段还需要知道如何序列化其值: + + + +`value_to_string`(_obj_) + + + +将`obj`转换为字符串。用于序列化字段的值。 + +有关用法,请参见[_转换字段数据以进行序列化_](../../howto/custom-model-fields.html#converting-model-field-to-serialization)。 + + + + + +当使用[`模型 表单`](../../topics/forms/modelforms.html#django.forms.ModelForm "django.forms.ModelForm")时,`Field`需要知道应由哪个表单字段表示: + + + +`formfield`(_form_class=None_, _choices_form_class=None_, _**kwargs_) + + + +返回[`ModelForm`](../../topics/forms/modelforms.html#django.forms.ModelForm "django.forms.ModelForm")的此字段的默认[`django.forms.Field`](../forms/fields.html#django.forms.Field "django.forms.Field")。 + +By default, if both `form_class` and `choices_form_class` are `None`, it uses [`CharField`](../forms/fields.html#django.forms.CharField "django.forms.CharField"); if `choices_form_class` is given, it returns [`TypedChoiceField`](../forms/fields.html#django.forms.TypedChoiceField "django.forms.TypedChoiceField"). + +有关用法,请参见[_为模型字段指定表单字段_](../../howto/custom-model-fields.html#specifying-form-field-for-model-field)。 + + + + + + + +`deconstruct`() + + + +New in Django 1.7. + +返回具有足够信息的4元组,以重新创建字段: + +1. 模型上字段的名称。 +2. 字段的导入路径(例如`“django.db.models.IntegerField”`)。这应该是兼容的版本,所以不要太具体可能会更好。 +3. 位置参数列表。 +4. 关键字参数的字典。 + +必须将此方法添加到1.7之前的字段,才能使用[_迁移_](../../topics/migrations.html)迁移其数据。 + + + + + + + + + + + + + + + +# Field属性参考 + +New in Django 1.8. + +每个`字段`实例包含几个允许内省其行为的属性。Use these attributes instead of `isinstance` checks when you need to write code that depends on a field’s functionality. 这些属性可与[_Model._meta API_](meta.html#model-meta-field-api)一起使用,以缩小特定字段类型的搜索范围。自定义模型字段应实现这些标志。 + + + +## Attributes for fields + + + +`Field.auto_created` + + + +布尔标志,指示是否自动创建字段,例如模型继承使用的`OneToOneField`。 + + + + + + + +`Field.concrete` + + + +布尔标志,指示字段是否具有与其相关联的数据库列。 + + + + + + + +`Field.hidden` + + + +Boolean flag that indicates if a field is used to back another non-hidden field’s functionality (e.g. the `content_type` and `object_id` fields that make up a `GenericForeignKey`). The `hidden` flag is used to distinguish what constitutes the public subset of fields on the model from all the fields on the model. + + + +Note + +[`Options.get_fields()`](meta.html#django.db.models.options.Options.get_fields "django.db.models.options.Options.get_fields")默认排除隐藏字段。传入`include_hidden = True`可返回结果中的隐藏字段。 + + + + + + + + + +`Field.is_relation` + + + +布尔标志,指示字段是否包含对一个或多个其他模型的功能的引用(例如`ForeignKey`,`ManyToManyField`,`OneToOneField`等) 。 + + + + + + + +`Field.model` + + + +返回定义字段的模型。如果在模型的超类上定义了字段,则`模型`将引用超类,而不是实例的类。 + + + + + + + + + +## 具有关系的字段的属性 + +这些属性用于查询关系的基数和其他详细信息。这些属性存在于所有字段上;但是,如果字段是关系类型([`Field.is_relation=True`](#django.db.models.Field.is_relation "django.db.models.Field.is_relation")),则它们只有布尔值(而不是`None`)。 + + + +`Field.many_to_many` + + + +如果字段具有多对多关系,则布尔标志为`True`;否则为`False`。The only field included with Django where this is `True` is `ManyToManyField`. + + + + + + + +`Field.many_to_one` + + + +如果字段具有多对一关系,例如`ForeignKey`,则布尔标志为`True`;否则为`False`。 + + + + + + + +`Field.one_to_many` + + + +如果字段具有一对多关系(例如`GenericRelation`或`ForeignKey`的反向),则`True`的布尔标志;否则为`False`。 + + + + + + + +`Field.one_to_one` + + + +如果字段具有一对一关系,例如`OneToOneField`,则布尔标志为`True`;否则为`False`。 + + + + + + + +`Field.related_model` + + + +指向字段涉及的模型。例如,`ForeignKey(Author)`中的`Author`。如果字段具有通用关系(例如`GenericForeignKey`或`GenericRelation`),则`related_model`将为`None`。 + + + + + + + + + + + + + +# 模型字段参考 + +本文档包含了Django提供的全部模型[`字段`](#django.db.models.Field "django.db.models.Field")的[字段选项](#field-options) 和 [字段类型](#field-types)的API参考。 + + + +请看: + +如果内建的字段不能满足你的需要,你可以尝试包含对特定国家和文化有帮助的配套代码的 [django-localflavor](https://django-localflavor.readthedocs.org/)。当然,你也可以很容易的编写你[_自定义的字段_](../../howto/custom-model-fields.html)。 + + + + + +注意 + +严格意义上来讲, Model 是定义在[`django.db.models.fields`](#module-django.db.models.fields "django.db.models.fields: Built-in field types.")里面,但为了使用方便,它们被导入到 [`django.db.models`](../../topics/db/models.html#module-django.db.models "django.db.models")中;标准上,我们导入`from django.db import models`,然后使用?`models.Field`的形式使用字段。 + + + + + +## 字段选项(Field options) + +下列参数是全部字段类型都可用的,而且都是可选择的。 + + + +### null + + + +`Field.null` + + + +如果为`True`,Django 将空值以`NULL` 存储到数据库中。默认值是 `False`。 + +字符串字段例如[`CharField`](#django.db.models.CharField "django.db.models.CharField") 和[`TextField`](#django.db.models.TextField "django.db.models.TextField") 要避免使用[`null`](#django.db.models.Field.null "django.db.models.Field.null"),因为空字符串值将始终储存为空字符串而不是`NULL`。如果字符串字段的`null=True`,那意味着对于“无数据”有两个可能的值:`NULL` 和空字符串。在大多数情况下,对于“无数据”声明两个值是赘余的,Django 的惯例是使用空字符串而不是`NULL`。 + +无论是字符串字段还是非字符串字段,如果你希望在表单中允许空值,你将还需要设置`blank=True`,因为[`null`](#django.db.models.Field.null "django.db.models.Field.null") 仅仅影响数据库存储(参见[`blank`](#django.db.models.Field.blank "django.db.models.Field.blank"))。 + + + +注意 + +在使用Oracle 数据库时,数据库将存储`NULL` 来表示空字符串,而与这个属性无关。 + + + +如果你希望[`BooleanField`](#django.db.models.BooleanField "django.db.models.BooleanField") 接受[`null`](#django.db.models.Field.null "django.db.models.Field.null") 值,请用 [`NullBooleanField`](#django.db.models.NullBooleanField "django.db.models.NullBooleanField") 代替。 + + + + + +### blank + + + +`Field.blank` + + + +如果为`True`,则该字段允许为空白。 默认值是 `False`。 + +注意它与[`null`](#django.db.models.Field.null "django.db.models.Field.null")不同。[`null`](#django.db.models.Field.null "django.db.models.Field.null") 纯粹是数据库范畴的概念,而[`blank`](#django.db.models.Field.blank "django.db.models.Field.blank") 是数据验证范畴的。如果字段设置`blank=True`,表单验证时将允许输入空值。如果字段设置`blank=False`,则该字段为必填。 + + + + + +### choices + + + +`Field.choices` + + + +它是一个可迭代的结构(比如,列表或是元组),由可迭代的二元组组成(比如`[(A, B), (A, B) ...]`),用来给这个字段提供选择项。如果设置了 choices ,默认表格样式就会显示选择框,而不是标准的文本框,而且这个选择框的选项就是 choices 中的元组。 + +每个元组中的第一个元素,是存储在数据库中的值;第二个元素是该选项更易理解的描述。 比如: + + + + + +``` +YEAR_IN_SCHOOL_CHOICES = ( + ('FR', 'Freshman'), + ('SO', 'Sophomore'), + ('JR', 'Junior'), + ('SR', 'Senior'), +) + +``` + + + + + +一般来说,最好在模型类内部定义choices,然后再给每个值定义一个合适名字的常量。 + + + + + +``` +from django.db import models + +class Student(models.Model): + FRESHMAN = 'FR' + SOPHOMORE = 'SO' + JUNIOR = 'JR' + SENIOR = 'SR' + YEAR_IN_SCHOOL_CHOICES = ( + (FRESHMAN, 'Freshman'), + (SOPHOMORE, 'Sophomore'), + (JUNIOR, 'Junior'), + (SENIOR, 'Senior'), + ) + year_in_school = models.CharField(max_length=2, + choices=YEAR_IN_SCHOOL_CHOICES, + default=FRESHMAN) + + def is_upperclass(self): + return self.year_in_school in (self.JUNIOR, self.SENIOR) + +``` + + + + + +尽管你可以在模型类的外部定义choices然后引用它,但是在模型类中定义choices和其每个choice的name(即元组的第二个元素)可以保存所有使用choices的类的信息, 也使得choices更容易被应用(例如, `Student.SOPHOMORE` 可以在任何引入`Student` 模型的位置生效)。 + +你也可以归类可选的choices到已命名的组中用来达成组织整理的目的: + + + + + +``` +MEDIA_CHOICES = ( + ('Audio', ( + ('vinyl', 'Vinyl'), + ('cd', 'CD'), + ) + ), + ('Video', ( + ('vhs', 'VHS Tape'), + ('dvd', 'DVD'), + ) + ), + ('unknown', 'Unknown'), +) + +``` + + + + + +每个元组的第一个元素是组的名字。第二个元素是一组可迭代的二元元组,每一个二元元组包含一个值和一个给人看的名字构成一个选项。分组的选项可能会和未分组的选项合在同一个list中。 (就像例中的unknown选项)。 + +对于有[`choices`](#django.db.models.Field.choices "django.db.models.Field.choices") set的模型字段, Django 将会加入一个方法来获取当前字段值的易于理解的名称(即元组的第二个值)参见数据库API文档中的[`get_FOO_display()`](instances.html#django.db.models.Model.get_FOO_display "django.db.models.Model.get_FOO_display")。 + +请注意choices可以是任何可迭代的对象 – 不是必须是列表或者元组。这一点使你可以动态的构建choices。但是如果你发现你自己搞不定动态的[`choices`](#django.db.models.Field.choices "django.db.models.Field.choices"),你最好还是使用[`ForeignKey`](#django.db.models.ForeignKey "django.db.models.ForeignKey")来构建一个合适的数据库表。如果有数据变动的话,[`choices`](#django.db.models.Field.choices "django.db.models.Field.choices")意味着那些变动不多的静态数据。 + +New in Django 1.7. + +除非[`blank=False`](#django.db.models.Field.blank "django.db.models.Field.blank") 和[`default`](#django.db.models.Field.default "django.db.models.Field.default")一起在字段中被设置,否则,可选择菜单将会有`"---------"` 的标签。要重写这个行为, 需要加入一个包含`None`的元组到 `choices`里面; 例如 `(None, 'Your String For Display')`. 或者, 你可以在操作有意义的地方用一个空字符串代替`None` ?- 比如在一个 [`CharField`](#django.db.models.CharField "django.db.models.CharField"). + + + + + +### db_column + + + +`Field.db_column` + + + +数据库中用来表示该字段的名称。如果未指定,那么Django将会使用Field名作为字段名. + +如果你的数据库列名为SQL语句的保留字,或者是包含不能作为Python 变量名的字符,如连字符,没问题。Django 会在后台给列名和表名加上双引号。 + + + + + +### db_index + + + +`Field.db_index` + + + +若值为 `True`, 则 [`django-admin sqlindexes`](../django-admin.html#django-admin-sqlindexes) 将会为此字段输出 `CREATE INDEX` 语句。(译注:为此字段创建索引) + + + + + +### db_tablespace + + + +`Field.db_tablespace` + + + +?如果该字段有索引的话,[_database tablespace_](../../topics/db/tablespaces.html)的名称将作为该字段的索引名。 如果[`DEFAULT_INDEX_TABLESPACE`](../settings.html#std:setting-DEFAULT_INDEX_TABLESPACE) 已经设置,则默认值是由DEFAULT_INDEX_TABLESPACE指定, 如果没有设置则由 [`db_tablespace`](options.html#django.db.models.Options.db_tablespace "django.db.models.Options.db_tablespace") 指定,如果后台数据库不支持表空间,或者索引,则该选项被忽略 + + + + + +### default + + + +`Field.default` + + + +该字段的默认值. 它可以是一个值或者一个可调用对象. 如果是一个可调用对象,那么在每一次创建新对象的时候,它将会调用一次. + +这个默认值不可以是一个可变对象(如字典,列表,等等),因为对于所有模型的一个新的实例来说,它们指向同一个引用。或者,把他们包装为一个可调用的对象。例如,你有一个自定义的`JSONField`,并且想指定一个特定的字典值,可以如下使用: + + + + + +``` +def contact_default(): + return {"email": "to1@example.com"} + +contact_info = JSONField("ContactInfo", default=contact_default) + +``` + + + + + +请注意`lambda`s 函数不可作为如 `default` 这类可选参数的值.因为它们无法被 [_migrations命令序列化_](../../topics/migrations.html#migration-serializing). 请参见文档其他部分。 + +默认值会在新实例创建并且没有给该字段提供值时使用。如果字段为主键,默认值也会在设置为`None`时使用。 + +Changed in Django 1.8: + +之前的版本不会使用`None`作为主键? + + + + + + + +### editable + + + +`Field.editable` + + + +如果设为`False`, 这个字段将不会出现在 admin 或者其他 [`ModelForm`](../../topics/forms/modelforms.html#django.forms.ModelForm "django.forms.ModelForm"). 他们也会跳过 [_模型验证_](instances.html#validating-objects). 默认是`True`. + + + + + +### error_messages + + + +`Field.error_messages` + + + +`error_messages` 参数能够让你重写默认抛出的错误信息。通过指定 key 来确认你要重写的错误信息。 + +error_messages 的 key 值包括 `null`, `blank`, `invalid`, `invalid_choice`, `unique`, 和 `unique_for_date`. 其余的 error_messages 的 keys 是不一样的在不同的章节下 [Field types](#field-types) 。 + +New in Django 1.7. + +这个 `unique_for_date` 的 error_messages 的key 是在 1.7 中加的。 + + + + + +### help_text + + + +`Field.help_text` + + + +额外的 ‘help' 文本将被显示在表单控件form中。即便你的字段没有应用到一个form里面,这样的操作对文档化也很有帮助。 + +注意这 _不_ 会自动添加 HTML 标签。需要你在 [`help_text`](#django.db.models.Field.help_text "django.db.models.Field.help_text") 包含自己需要的格式。例如: + + + + + +``` +help_text="Please use the following format: YYYY-MM-DD." + +``` + + + + + +另外, 你可以使用简单文本和`django.utils.html.escape()`以避免任何HTML特定的字符.请确保你所使用的help text能够避免那些由不受信任的用户进行的跨站点脚本攻击。 + + + + + +### primary_key + + + +`Field.primary_key` + + + +若为 `True`, 则该字段会成为模型的主键字段。 + +如果你没有在模型的任何字段上指定 `primary_key=True`, Django会自动添加一个 [`AutoField`](#django.db.models.AutoField "django.db.models.AutoField") 字段来充当主键。 所以除非你想要覆盖默认的主键行为,否则不需要在任何字段上设定`primary_key=True` 。更多内容 请参考 [_Automatic primary key fields_](../../topics/db/models.html#automatic-primary-key-fields). + +`primary_key=True` 暗含着[`null=False`](#django.db.models.Field.null "django.db.models.Field.null") 和[`unique=True`](#django.db.models.Field.unique "django.db.models.Field.unique"). 一个对象上只能拥有一个主键. + +主键字段是只读的。如果你改变了一个已存在对象上的主键并且保存的话,会创建一个新的对象,而不是覆盖旧的. + + + + + +### unique + + + +`Field.unique` + + + +如果为?`True`, 这个字段在表中必须有唯一值. + +这是一个在数据库级别的强制性动作,并且通过模型来验证。如果你试图用一个重复的值来保存[`unique`](#django.db.models.Field.unique "django.db.models.Field.unique") 字段,那么一个[`django.db.IntegrityError`](../exceptions.html#django.db.IntegrityError "django.db.IntegrityError")就会通过模型的[`save()`](instances.html#django.db.models.Model.save "django.db.models.Model.save") 函数抛出来。 + +除了[`ManyToManyField`](#django.db.models.ManyToManyField "django.db.models.ManyToManyField")、[`OneToOneField`](#django.db.models.OneToOneField "django.db.models.OneToOneField")和[`FileField`](#django.db.models.FileField "django.db.models.FileField") 以外的其他字段类型都可以使用这个设置。 + +注意当设置`unique` 为`True` 时,你不需要再指定 [`db_index`](#django.db.models.Field.db_index "django.db.models.Field.db_index"),因为`unique` 本身就意味着一个索引的创建。 + + + + + +### unique_for_date + + + +`Field.unique_for_date` + + + +当设置它为[`DateField`](#django.db.models.DateField "django.db.models.DateField") 和[`DateTimeField`](#django.db.models.DateTimeField "django.db.models.DateTimeField") 字段的名称时,表示要求该字段对于相应的日期字段值是唯一的。 + +例如,你有一个`title` 字段设置`unique_for_date="pub_date"`,那么Django 将不允许两个记录具有相同的`title` 和`pub_date`。 + +注意,如果你将它设置为[`DateTimeField`](#django.db.models.DateTimeField "django.db.models.DateTimeField"),只会考虑其日期部分。此外,如果[`USE_TZ`](../settings.html#std:setting-USE_TZ) 为`True`,检查将以对象保存时的[_当前的时区_](../../topics/i18n/timezones.html#default-current-time-zone) 进行。 + +这是在模型验证期间通过[`Model.validate_unique()`](instances.html#django.db.models.Model.validate_unique "django.db.models.Model.validate_unique") 强制执行的,而不是在数据库层级进行验证。如果[`unique_for_date`](#django.db.models.Field.unique_for_date "django.db.models.Field.unique_for_date") 约束涉及的字段不是[`ModelForm`](../../topics/forms/modelforms.html#django.forms.ModelForm "django.forms.ModelForm")中的字段(例如,`exclude`中列出的字段或者设置了[`editable=False`](#django.db.models.Field.editable "django.db.models.Field.editable")),[`Model.validate_unique()`](instances.html#django.db.models.Model.validate_unique "django.db.models.Model.validate_unique") 将忽略该特殊的约束。 + + + + + +### unique_for_month + + + +`Field.unique_for_month` + + + +类似[`unique_for_date`](#django.db.models.Field.unique_for_date "django.db.models.Field.unique_for_date"),只是要求字段对于月份是唯一的。 + + + + + +### unique_for_year + + + +`Field.unique_for_year` + + + +类似[`unique_for_date`](#django.db.models.Field.unique_for_date "django.db.models.Field.unique_for_date") 和 [`unique_for_month`](#django.db.models.Field.unique_for_month "django.db.models.Field.unique_for_month")。 + + + + + +### verbose_name + + + +`Field.verbose_name` + + + +一个字段的可读性更高的名称。如果用户没有设定冗余名称字段,Django会自动将该字段属性名中的下划线转换为空格,并用它来创建冗余名称。可以参照 [_Verbose field names_](../../topics/db/models.html#verbose-field-names). + + + + + +### validators + + + +`Field.validators` + + + +该字段将要运行的一个Validator 的列表。更多信息参见[_Validators 的文档_](../validators.html)。 + + + +#### 注册和获取查询 + +`Field` 实现了[_查询注册API_](lookups.html#lookup-registration-api)。该API 可以用于自定义一个字段类型的可用的查询,以及如何从一个字段获取查询。 + + + + + + + + + +## 字段类型 (Field types) + + + +### 自增字段 + + + +_class_ `AutoField`(_**options_) + + + +一个根据实际ID自动增长的[`IntegerField`](#django.db.models.IntegerField "django.db.models.IntegerField") . 你通常不需要直接使用;如果不指定,一个主键字段将自动添加到你创建的模型中.详细查看?[_主键字段_](../../topics/db/models.html#automatic-primary-key-fields). + + + + + +### BigIntegerField + + + +_class_ `BigIntegerField`([_**options_]) + + + +一个64位整数, 类似于一个?[`IntegerField`](#django.db.models.IntegerField "django.db.models.IntegerField") ,它的值的范围是?`-9223372036854775808` 到`9223372036854775807`之间. 这个字段默认的表单组件是一个[`TextInput`](../forms/widgets.html#django.forms.TextInput "django.forms.TextInput"). + + + + + +### BinaryField + + + +_class_ `BinaryField`([_**options_]) + + + +这是一个用来存储原始二进制码的Field. 只支持`bytes` 赋值,注意这个Field只有很有限的功能。例如,不大可能在一个`BinaryField` 值的数据上进行查询 + + + +Abusing `BinaryField` + +尽管你可能想使用数据库来存储你的文件,但是99%的情况下这都是不好的设计。这个字段 _不是_替代[_static files_](../../howto/static-files/index.html) 的合理操作. + + + + + + + +### BooleanField + + + +_class_ `BooleanField`(_**options_) + + + +true/false 字段。 + +此字段的默认表单挂件是一个[`CheckboxInput`](../forms/widgets.html#django.forms.CheckboxInput "django.forms.CheckboxInput"). + +如果你需要设置[`null`](#django.db.models.Field.null "django.db.models.Field.null") 值,则使用[`NullBooleanField`](#django.db.models.NullBooleanField "django.db.models.NullBooleanField") 来代替BooleanField。 + +如果[`Field.default`](#django.db.models.Field.default "django.db.models.Field.default")没有指定的话, `BooleanField` 的默认值是 `None`。 + + + + + +### CharField + + + +_class_ `CharField`(_max_length=None_[, _**options_]) + + + +一个用来存储从小到很大各种长度的字符串的地方 + +如果是巨大的文本类型, 可以用 [`TextField`](#django.db.models.TextField "django.db.models.TextField"). + +这个字段默认的表单样式是 [`TextInput`](../forms/widgets.html#django.forms.TextInput "django.forms.TextInput"). + +[`CharField`](#django.db.models.CharField "django.db.models.CharField")必须接收一个额外的参数: + + + +`CharField.max_length` + + + +字段的最大字符长度.max_length将在数据库层和Django表单验证中起作用, 用来限定字段的长度.? + + + + + + + +Note + +如果你在写一个需要导出到多个不同数据库后端的应用,你需要注意某些后端对`max_length`有一些限制,查看[_数据库后端注意事项_](../databases.html)来获取更多的细节 + + + + + +MySQL的使用者们: + +如果你在使用该字段的同时使用MySQLdb 1.2.2以及 `utf8_bin` 校对 其一般_不是_ 默认情况), 你必须了解一些问题. 详细请参考 [_MySQL database notes_](../databases.html#mysql-collation) . + + + + + + + +### CommaSeparatedIntegerField + + + +_class_ `CommaSeparatedIntegerField`(_max_length=None_[, _**options_]) + + + +一个逗号分隔的整数字段。像 [`CharField`](#django.db.models.CharField "django.db.models.CharField")一样, 需要一个[`max_length`](#django.db.models.CharField.max_length "django.db.models.CharField.max_length") 参数, 同时数据库移植时也需要注意。 + + + + + +### DateField + + + +_class_ `DateField`([_auto_now=False_, _auto_now_add=False_, _**options_]) + + + +这是一个使用Python的`datetime.date`实例表示的日期. 有几个额外的设置参数: + + + +`DateField.auto_now` + + + +每次保存对象时,自动设置该字段为当前时间。用于"最后一次修改"的时间戳。注意,它_总是_使用当前日期;和你可以覆盖的那种默认值不一样。 + + + + + + + +`DateField.auto_now_add` + + + +当对象第一次被创建时自动设置当前时间。用于创建时间的时间戳. 它_总是_使用当前日期;和你可以覆盖的那种默认值不一样。 + + + + + +该字段默认对应的表单控件是一个[`TextInput`](../forms/widgets.html#django.forms.TextInput "django.forms.TextInput"). 在管理员站点添加了一个JavaScript写的日历控件,和一个“Today"的快捷按钮.包含了一个额外的`invalid_date`错误消息键. + +`auto_now_add`, `auto_now`, and `default` 这些设置是相互排斥的. 他们之间的任何组合将会发生错误的结果. + + + +Note + +在目前的实现中,设置`auto_now`或者`auto_now_add`为`True`将为让这个字段同时得到`editable=False`和`blank=True`这两个设置. + + + + + +Note + +`auto_now` and `auto_now_add`这两个设置会在对象创建或更新的时刻,总是使用[_default timezone_](../../topics/i18n/timezones.html#default-current-time-zone)(默认时区)的日期. 如果你不想这样,你可以考虑一下简单地使用你自己的默认调用或者重写`save()`(在save()函数里自己添加保存时间的机制.译者注)而不是使用`auto_now` or `auto_now_add`; 或者使用`DateTimeField`字段类来替换`DateField` 并且在给用户呈现时间的时候,决定如何处理从datetime到date的转换. + + + + + + + +### DateTimeField + + + +_class_ `DateTimeField`([_auto_now=False_, _auto_now_add=False_, _**options_]) + + + +它是通过Python `datetime.datetime`实例表示的日期和时间. 携带了跟[`DateField`](#django.db.models.DateField "django.db.models.DateField")一样的额外参数. + +该字段默认对应的表单控件是一个单个的[`TextInput`](../forms/widgets.html#django.forms.TextInput "django.forms.TextInput")(单文本输入框). 管理界面是使用两个带有 JavaScript控件的?[`TextInput`](../forms/widgets.html#django.forms.TextInput "django.forms.TextInput")?文本框. + + + + + +### DecimalField + + + +_class_ `DecimalField`(_max_digits=None_, _decimal_places=None_[, _**options_]) + + + +用python中 [`Decimal`](https://docs.python.org/3/library/decimal.html#decimal.Decimal "(in Python v3.4)") 的一个实例来表示十进制浮点数. 有两个 **必须的**参数: + + + +`DecimalField.max_digits` + + + +位数总数,包括小数点后的位数。该值必须大于等于`decimal_places`. + + + + + + + +`DecimalField.decimal_places` + + + +小数点后的数字数量 + + + + + +例如,要保存最大为 `999` 并有两位小数的数字,你应该使用: + + + + + +``` +models.DecimalField(..., max_digits=5, decimal_places=2) + +``` + + + + + +而要存储那些将近10亿,并且要求达到小数点后十位精度的数字: + + + + + +``` +models.DecimalField(..., max_digits=19, decimal_places=10) + +``` + + + + + +该字段默认的窗体组件是 [`TextInput`](../forms/widgets.html#django.forms.TextInput "django.forms.TextInput"). + + + +Note + +想获取更多关于 [`FloatField`](#django.db.models.FloatField "django.db.models.FloatField") 和 [`DecimalField`](#django.db.models.DecimalField "django.db.models.DecimalField") 差异, 请参照 [_FloatField vs. DecimalField_](#floatfield-vs-decimalfield). + + + + + + + +### DurationField + +New in Django 1.8. + + + +_class_ `DurationField`([_**options_]) + + + +用作存储一段时间的字段类型 - 类似Python中的[`timedelta`](https://docs.python.org/3/library/datetime.html#datetime.timedelta "(in Python v3.4)"). 当数据库使用的是PostgreSQL, 该数据类型使用的是一个 `interval` 而在Oracle上,则使用的是 `INTERVAL DAY(9) TO SECOND(6)`. Otherwise a `bigint` of microseconds is used. + + + +注意 + +`DurationField` 的算数运算在多数情况下可以很好的工作。?然而,在除了PostgreSQL之外的其他数据库中, 将 `DurationField` 与 `DateTimeField` 的实例比较则不会得到正确的结果。 + + + + + + + +### EmailField + + + +_class_ `EmailField`([_max_length=254_, _**options_]) + + + +一个 [`CharField`](#django.db.models.CharField "django.db.models.CharField") 用来检查输入的email地址是否合法。它使用 [`EmailValidator`](../validators.html#django.core.validators.EmailValidator "django.core.validators.EmailValidator") 来验证输入合法性。 + +Changed in Django 1.8: + +默认最大长度 `max_length` 从75增加到254以符合RFC3696/5321标准。 + + + + + + + +### FileField + + + +_class_ `FileField`([_upload_to=None_, _max_length=100_, _**options_]) + + + +一个上传文件的字段。 + + + +注意 + +FileField字段不支持`primary_key` 和`unique`参数,如果使用会生成?`TypeError`错误 + + + +有两个可选参数: + + + +`FileField.upload_to` + + + +Changed in Django 1.7: + +在旧版本Django中,`upload_to`?属性是必须要有的; + + + +一个本地文件系统的路径,它将附加到[`MEDIA_ROOT`](../settings.html#std:setting-MEDIA_ROOT) 设置的后面来确定[`url`](#django.db.models.fields.files.FieldFile.url "django.db.models.fields.files.FieldFile.url") 属性的值。 + +这个路径可能会包含一个 [`strftime()`](https://docs.python.org/3/library/time.html#time.strftime "(in Python v3.4)") 格式串,并且会在文件上传时被替换为 实际的date/time作为文件路径 (这样上传的文件就不会塞满你指定的文件夹了). + +它还可以是一个可调用对象如函数,将调用它来获取上传路径,包括文件名。这个可调用对象必须接受两个参数,并且返回一个Unix 风格的路径(带有前向/)给存储系统。将传递的两个参数为: + + +| Argument | Description | +| --- | --- | +| `instance` | + +`FileField` 定义所在的模型的实例。更准确地说,就是当前文件的所在的那个实例。 + +大部分情况下,这个实例将还没有保存到数据库中,若它用到默认的`AutoField` 字段,_它的主键字段还可能没有值_。 + + | +| `filename` | The filename that was originally given to the file. This may or may not be taken into account when determining the final destination path. | + + + + + + + +`FileField.storage` + + + +一个Storage 对象,用于你的文件的存取。参见[_管理文件_](../../topics/files.html) 获取如何提供这个对象的细节。 + + + + + +这个字段的默认表单Widget 是[`ClearableFileInput`](../forms/widgets.html#django.forms.ClearableFileInput "django.forms.ClearableFileInput")。 + +在模型中调用[`FileField`](#django.db.models.FileField "django.db.models.FileField") 或 [`ImageField`](#django.db.models.ImageField "django.db.models.ImageField") (见下方) 需如下几步: + +1. 在你的settings文件中, 你必须要定义 [`MEDIA_ROOT`](../settings.html#std:setting-MEDIA_ROOT) 作为Django存储上传文件的路径(从性能上考虑,这些文件不能存在数据库中。) 定义一个 [`MEDIA_URL`](../settings.html#std:setting-MEDIA_URL) 作为基础的URL或者目录。确保这个目录可以被web server使用的账户写入。 +2. 在模型中添加[`FileField`](#django.db.models.FileField "django.db.models.FileField") 或 [`ImageField`](#django.db.models.ImageField "django.db.models.ImageField") 字段, 定义 [`upload_to`](#django.db.models.FileField.upload_to "django.db.models.FileField.upload_to")参数,内容是 [`MEDIA_ROOT`](../settings.html#std:setting-MEDIA_ROOT) 的子目录,用来存放上传的文件。 +3. 数据库中存放的仅是这个文件的路径 (相对于[`MEDIA_ROOT`](../settings.html#std:setting-MEDIA_ROOT)). 你很可能会想用由Django提供的便利的[`url`](#django.db.models.fields.files.FieldFile.url "django.db.models.fields.files.FieldFile.url") 属性。比如说, 如果你的[`ImageField`](#django.db.models.ImageField "django.db.models.ImageField") 命名为 `mug_shot`, 你可以在template中用 `{{ object.mug_shot.url }}`获得你照片的绝对路径。 + +例如,如果你的 [`MEDIA_ROOT`](../settings.html#std:setting-MEDIA_ROOT)设定为 `'/home/media'`,并且 [`upload_to`](#django.db.models.FileField.upload_to "django.db.models.FileField.upload_to")设定为 `'photos/%Y/%m/%d'`。 [`upload_to`](#django.db.models.FileField.upload_to "django.db.models.FileField.upload_to")的`'%Y/%m/%d'`被[`strftime()`](https://docs.python.org/3/library/time.html#time.strftime "(in Python v3.4)")所格式化;`'%Y'` 将会被格式化为一个四位数的年份, `'%m'` 被格式化为一个两位数的月份`'%d'`是两位数日份。如果你在Jan.15.2007上传了一个文件,它将被保存在`/home/media/photos/2007/01/15`目录下. + +如果你想获得上传文件的存盘文件名,或者是文件大小,你可以分别使用 [`name`](../files/file.html#django.core.files.File.name "django.core.files.File.name") 和 [`size`](../files/file.html#django.core.files.File.size "django.core.files.File.size") 属性; 更多可用属性及方法信息,请参见 [`File`](../files/file.html#django.core.files.File "django.core.files.File") 类索引 和 [_Managing files_](../../topics/files.html) 主题指导. + + + +Note + +保存的文件作为模型存储在数据库中的一部分,所以在磁盘上使用的实际的文件名在模型保存完毕之前是不可靠的。 + + + +上传的文件对应的URL可以通过使用 [`url`](#django.db.models.fields.files.FieldFile.url "django.db.models.fields.files.FieldFile.url") 属性获得. 在内部,它会调用 ?[`Storage`](../files/storage.html#django.core.files.storage.Storage "django.core.files.storage.Storage") 类下的[`url()`](../files/storage.html#django.core.files.storage.Storage.url "django.core.files.storage.Storage.url")方法. + +值得注意的是,无论你在任何时候处理上传文件的需求,你都应该密切关注你的文件将被上传到哪里,上传的文件类型,以避免安全漏洞。_认证所有上传文件_ 以确保那些上传的文件是你所认为的文件。例如,如果你盲目的允许其他人在无需认证的情况下上传文件至你的web服务器的root目录中,那么别人可以上传一个CGI或者PHP脚本然后通过访问一个你网站的URL来执行这个脚本。所以,不要允许这种事情发生。 + +甚至是上传HTML文件也值得注意,它可以通过浏览器(虽然不是服务器)执行,也可以引发相当于是XSS或者CSRF攻击的安全威胁。 + +[`FileField`](#django.db.models.FileField "django.db.models.FileField") 实例将会在你的数据库中创建一个默认最大长度为100字符的`varchar` 列。就像其他的fields一样, 你可以用 [`max_length`](#django.db.models.CharField.max_length "django.db.models.CharField.max_length") 参数改变最大长度的值. + + + +#### FileField 和FieldFile + + + +_class_ `FieldFile`[[source]](../../_modules/django/db/models/fields/files.html#FieldFile) + + + +当你添加 [`FileField`](#django.db.models.FileField "django.db.models.FileField") 到你的模型中时, 你实际上会获得一个 [`FieldFile`](#django.db.models.fields.files.FieldFile "django.db.models.fields.files.FieldFile")的实例来替代将要访问的文件。 除了继承至 [`django.core.files.File`](../files/file.html#django.core.files.File "django.core.files.File")的功能外, 这个类还有其他属性和方法可以用于访问文件: + + + +`FieldFile.url` + + + +通过潜在[`Storage`](../files/storage.html#django.core.files.storage.Storage "django.core.files.storage.Storage") 类的[`url()`](../files/storage.html#django.core.files.storage.Storage.url "django.core.files.storage.Storage.url")方法可以只读地访问文件的URL。 + + + +`FieldFile.open`(_mode='rb'_)[[source]](../../_modules/django/db/models/fields/files.html#FieldFile.open) + + + +该方法像标准的Python `open()` 方法,并可通过?`mode`参数设置打开模式. + + + +`FieldFile.close`()[[source]](../../_modules/django/db/models/fields/files.html#FieldFile.close) + + + +该方法像标准的Python`file.close()` 方法,并关闭相关文件. + + + +`FieldFile.save`(_name_, _content_, _save=True_)[[source]](../../_modules/django/db/models/fields/files.html#FieldFile.save) + + + +这个方法会将文件名以及文件内容传递到字段的storage类中,并将模型字段与保存好的文件关联. 如果想要手动关联文件数据到你的模型中的 [`FileField`](#django.db.models.FileField "django.db.models.FileField")实例, 则`save()` 方法总是用来保存该数据. + +方法接受两个必选参数: `name` 文件名, 和 `content` 文件内容.可选参数`save` 控制模型实例在关联的文件被修改时是否保存.默认为 `True`. + +注意参数 `content` 应该是 [`django.core.files.File`](../files/file.html#django.core.files.File "django.core.files.File")的一个实例, 而不是Python内建的File对象.你可以用如下方法从一个已经存在的Python文件对象来构建 [`File`](../files/file.html#django.core.files.File "django.core.files.File") : + + + + + +``` +from django.core.files import File +# Open an existing file using Python's built-in open() +f = open('/tmp/hello.world') +myfile = File(f) + +``` + + + + + +或者,你可以像下面的一样从一个python字符串中构建 + + + + + +``` +from django.core.files.base import ContentFile +myfile = ContentFile("hello world") + +``` + + + + + +更多信息, 请参见 [_Managing files_](../../topics/files.html). + + + +`FieldFile.delete`(_save=True_)[[source]](../../_modules/django/db/models/fields/files.html#FieldFile.delete) + + + +删除与此实例关联的文件,并清除该字段的所有属性。注意︰ 如果它碰巧是开放的调用 `delete()` 方法 时,此方法将关闭该文件。 + +模型实例`save`的文件与此字段关联的可选 保存 参数控件已被删除。默认值为 `True`。 + +注意,model删除的时候,与之关联的文件并不会被删除。如果你要把文件也清理掉,你需要自己处理。 + + + + + + + +### FilePathField + + + +_class_ `FilePathField`(_path=None_[, _match=None_, _recursive=False_, _max_length=100_, _**options_]) + + + +一个?[`CharField`](#django.db.models.CharField "django.db.models.CharField") ,内容只限于文件系统内特定目录下的文件名。有三个参数, 其中第一个是 **必需的**: + + + +`FilePathField.path` + + + +必填。这个[`FilePathField`](#django.db.models.FilePathField "django.db.models.FilePathField") 应该得到其选择的目录的绝对文件系统路径。例如: `"/home/images"`. + + + + + + + +`FilePathField.match` + + + +可选的.[`FilePathField`](#django.db.models.FilePathField "django.db.models.FilePathField") 将会作为一个正则表达式来匹配文件名。但请注意正则表达式将将被作用于基本文件名,而不是完整路径。例如: `"foo.*.txt$"`, 将会匹配到一个名叫 `foo23.txt` 的文件,但不匹配到 `bar.txt` 或者 `foo23.png`. + + + + + + + +`FilePathField.recursive` + + + +可选的.`True` 或 `False`.默认是`False`.声明是否包含所有子目录的[`路径`](#django.db.models.FilePathField.path "django.db.models.FilePathField.path") + + + + + + + +`FilePathField.allow_files` + + + +可选的.`True` 或 `False`.默认是`True`.声明是否包含指定位置的文件。该参数或[`allow_folders`](#django.db.models.FilePathField.allow_folders "django.db.models.FilePathField.allow_folders") 中必须有一个为 `True`. + + + + + + + +`FilePathField.allow_folders` + + + +是可选的.输入 `True` 或者 `False`.默认值为 `False`.声明是否包含指定位置的文件夹。该参数或 [`allow_files`](#django.db.models.FilePathField.allow_files "django.db.models.FilePathField.allow_files") 中必须有一个为 `True`. + + + + + +当然,这些参数可以同时使用。 + +有一点需要提醒的是 [`match`](#django.db.models.FilePathField.match "django.db.models.FilePathField.match")只匹配基本文件名(base filename), 而不是整个文件路径(full path). 例如: + + + + + +``` +FilePathField(path="/home/images", match="foo.*", recursive=True) + +``` + + + + + +...将匹配`/home/images/foo.png`而不是`/home/images/foo/bar.png` 因为只允许[`匹配`](#django.db.models.FilePathField.match "django.db.models.FilePathField.match") 基本文件名(`foo.png` 和 `bar.png`). + +[`FilePathField`](#django.db.models.FilePathField "django.db.models.FilePathField")实例被创建在您的数据库为`varchar`列默认最大长度为 100 个字符。作为与其他字段,您可以更改使用的[`max_length`](#django.db.models.CharField.max_length "django.db.models.CharField.max_length")最大长度。 + + + + + +### FloatField + + + +_class_ `FloatField`([_**options_]) + + + +用Python的一个`float` 实例来表示一个浮点数. + +该字段的默认组件是一个 [`TextInput`](../forms/widgets.html#django.forms.TextInput "django.forms.TextInput"). + + + +`FloatField` 与`DecimalField` + +有时候[`FloatField`](#django.db.models.FloatField "django.db.models.FloatField") 类会和[`DecimalField`](#django.db.models.DecimalField "django.db.models.DecimalField") 类发生混淆. 虽然它们表示都表示实数,但是二者表示数字的方式不一样。`FloatField` 使用的是Python内部的 `float` 类型, 而`DecimalField` 使用的是Python的 `Decimal` 类型. 想要了解更多二者的差别, 可以查看Python文档中的 [`decimal`](https://docs.python.org/3/library/decimal.html#module-decimal "(in Python v3.4)") 模块. + + + + + + + +### ImageField + + + +_class_ `ImageField`([_upload_to=None_, _height_field=None_, _width_field=None_, _max_length=100_, _**options_]) + + + +继承了 [`FileField`](#django.db.models.FileField "django.db.models.FileField")的所有属性和方法, 但还对上传的对象进行校验,确保它是个有效的image. + +除了从[`FileField`](#django.db.models.FileField "django.db.models.FileField")继承来的属性外,[`ImageField`](#django.db.models.ImageField "django.db.models.ImageField") 还有`宽`和 `高`属性。 + +为了更便捷的去用那些属性值, [`ImageField`](#django.db.models.ImageField "django.db.models.ImageField") 有两个额外的可选参数 + + + +`ImageField.height_field` + + + +该属性的设定会在模型实例保存时,自动填充图片的高度. + + + + + + + +`ImageField.width_field` + + + +该属性的设定会在模型实例保存时,自动填充图片的宽度. + + + + + +ImageField字段需要调用[Pillow](http://pillow.readthedocs.org/en/latest/) 库. + +[`ImageField`](#django.db.models.ImageField "django.db.models.ImageField")会创建在你的数据库中 和 `varchar` 一样,默认最大长度为100和其他字段一样, 你可以使用[`max_length`](#django.db.models.CharField.max_length "django.db.models.CharField.max_length") 参数来设置默认文件最大值. + +此字段的默认表单工具是[`ClearableFileInput`](../forms/widgets.html#django.forms.ClearableFileInput "django.forms.ClearableFileInput"). + + + + + +### IntegerField + + + +_class_ `IntegerField`([_**options_]) + + + +一个整数。在Django所支持的所有数据库中,从 `-2147483648` 到 `2147483647` 范围内的值是合法的。默认的表单输入工具是[`TextInput`](../forms/widgets.html#django.forms.TextInput "django.forms.TextInput"). + + + + + +### IPAddressField + + + +_class_ `IPAddressField`([_**options_]) + + + + + +Deprecated since version 1.7: 该字段已废弃,从1.7开始支持[`GenericIPAddressField`](#django.db.models.GenericIPAddressField "django.db.models.GenericIPAddressField"). + + + +IP地址,会自动格式化(例如:“192.0.2.30”)。默认表单控件为 [`TextInput`](../forms/widgets.html#django.forms.TextInput "django.forms.TextInput"). + + + + + +### GenericIPAddressField + + + +_class_ `GenericIPAddressField`([_protocol=both_, _unpack_ipv4=False_, _**options_]) + + + +一个 IPv4 或 IPv6 地址, 字符串格式 (例如 `192.0.2.30` 或 `2a02:42fe::4`). 这个字段的默认表单小部件是一个[`TextInput`](../forms/widgets.html#django.forms.TextInput "django.forms.TextInput"). + +IPv6 地址会根据 [**RFC 4291**](http://tools.ietf.org/html/rfc4291.html#section-2.2) 章节 2.2所规范, 包括该章节中第三段的的IPv4格式建议, 就像 `::ffff:192.0.2.0`这样. 例如, `2001:0::0:01` 将会被规范成 `2001::1`, `::ffff:0a0a:0a0a` 被规范成 `::ffff:10.10.10.10`. 所有字符都会被转换成小写. + + + +`GenericIPAddressField.protocol` + + + +限制有效输入的协议类型. 允许的值是 `'both'` (默认值), `'IPv4'` 或 `'IPv6'`. 匹配不区分大小写. + + + + + + + +`GenericIPAddressField.unpack_ipv4` + + + +解析IPv4映射地址如 `::ffff:192.0.2.1`.如果启用该选项,则地址将被解析到`192.0.2.1`.默认为禁用。只有当`协议` 设置为`'both'`时才可以使用。 + + + + + +如果允许空白值,则必须允许null值,因为空白值存储为null。 + + + + + +### NullBooleanField + + + +_class_ `NullBooleanField`([_**options_]) + + + +类似[`BooleanField`](#django.db.models.BooleanField "django.db.models.BooleanField"), 但是允许 `NULL` 作为一个选项.使用此代替`null=True`的[`BooleanField`](#django.db.models.BooleanField "django.db.models.BooleanField")。此字段的默认表单widget为[`NullBooleanSelect`](../forms/widgets.html#django.forms.NullBooleanSelect "django.forms.NullBooleanSelect")。 + + + + + +### PositiveIntegerField(正整数字段) + + + +_class_ `PositiveIntegerField`([_**options_]) + + + +类似 [`IntegerField`](#django.db.models.IntegerField "django.db.models.IntegerField"), 但值必须是正数或者零(`0`). 从`0`到`2147483647`的值在Django支持的所有数据库中都是安全的。由于向后兼容性原因,接受值`0`。 + + + + + +### PositiveSmallIntegerField + + + +_class_ `PositiveSmallIntegerField`([_**options_]) + + + +该模型字段类似 [`PositiveIntegerField`](#django.db.models.PositiveIntegerField "django.db.models.PositiveIntegerField"), 但是只允许小于某一特定值(依据数据库类型而定)。从`0` 到 `32767` 这个区间,对于Django所支持的所有数据库而言都是安全的。 + + + + + +### SlugField + + + +_class_ `SlugField`([_max_length=50_, _**options_]) + + + +[_Slug_](../../glossary.html#term-slug) 是一个新闻术语(通常叫做短标题)。一个slug只能包含字母、数字、下划线或者是连字符,通常用来作为短标签。通常它们是用来放在URL里的。 + +像CharField一样,你可以指定[`max_length`](#django.db.models.CharField.max_length "django.db.models.CharField.max_length")(也请参阅该部分中的有关数据库可移植性的说明和[`max_length`](#django.db.models.CharField.max_length "django.db.models.CharField.max_length"))。如果没有指定 [`max_length`](#django.db.models.CharField.max_length "django.db.models.CharField.max_length"), Django将会默认长度为50。 + +Implies setting [`Field.db_index`](#django.db.models.Field.db_index "django.db.models.Field.db_index") to `True`. + +根据某些其他值的值自动预填充SlugField通常很有用。你可以在admin中使用[`prepopulated_fields`](../contrib/admin/index.html#django.contrib.admin.ModelAdmin.prepopulated_fields "django.contrib.admin.ModelAdmin.prepopulated_fields")自动执行此操作。 + + + + + +### SmallIntegerField + + + +_class_ `SmallIntegerField`([_**options_]) + + + +与 [`IntegerField`](#django.db.models.IntegerField "django.db.models.IntegerField")这个字段类型很类似,不同的是SmallInteger类型只能在一个确定的范围内(数据库依赖)。对于django来讲,该字段值在 `-32768` 至 `32767`这个范围内对所有可支持的数据库都是安全的。 + + + + + +### TextField + + + +_class_ `TextField`([_**options_]) + + + +大文本字段。该模型默认的表单组件是[`Textarea`](../forms/widgets.html#django.forms.Textarea "django.forms.Textarea")。 + +Changed in Django 1.7: + +如果你在这个字段类型中使用了`max_length`属性,它将会在渲染页面表单元素[`Textarea`](../forms/widgets.html#django.forms.Textarea "django.forms.Textarea") 时候体现出来。但是并不会在model或者数据库级别强制性的限定字段长度。请使用[`CharField`](#django.db.models.CharField "django.db.models.CharField")。 + + + + + +MySQL 用户 + +如果你将此字段用于MySQLdb 1.2.1p2和`utf8_bin`排序规则(这_不是_默认值),则需要注意一些问题。有关详细信息,请参阅[_MySQL数据库注释_](../databases.html#mysql-collation)。 + + + + + + + +### TimeField + + + +_class_ `TimeField`([_auto_now=False_, _auto_now_add=False_, _**options_]) + + + +时间字段,和Python中 `datetime.time` 一样。接受与[`DateField`](#django.db.models.DateField "django.db.models.DateField")相同的自动填充选项。 + +表单默认为 [`TextInput`](../forms/widgets.html#django.forms.TextInput "django.forms.TextInput").输入框。Admin添加一些JavaScript快捷方式。 + + + + + +### URLField + + + +_class_ `URLField`([_max_length=200_, _**options_]) + + + +一个[`CharField`](#django.db.models.CharField "django.db.models.CharField") 类型的URL + +此字段的默认表单widget为[`TextInput`](../forms/widgets.html#django.forms.TextInput "django.forms.TextInput")。 + +与所有[`CharField`](#django.db.models.CharField "django.db.models.CharField")子类一样,[`URLField`](#django.db.models.URLField "django.db.models.URLField")接受可选的[`max_length`](#django.db.models.CharField.max_length "django.db.models.CharField.max_length")参数。如果不指定[`max_length`](#django.db.models.CharField.max_length "django.db.models.CharField.max_length"),则使用默认值200。 + + + + + +### UUIDField + +New in Django 1.8. + + + +_class_ `UUIDField`([_**options_]) + + + +一个用来存储UUID的字段。使用Python的[`UUID`](https://docs.python.org/3/library/uuid.html#uuid.UUID "(in Python v3.4)")类。 当使用PostgreSQL数据库时,该字段类型对应的数据库中的数据类型是`uuid`,使用其他数据库时,数据库对应的是`char(32)`类型。 + +使用UUID类型相对于使用具有[`primary_key`](#django.db.models.Field.primary_key "django.db.models.Field.primary_key")参数的[`AutoField`](#django.db.models.AutoField "django.db.models.AutoField")类型是一个更好的解决方案。 数据库不会自动生成UUID,所以推荐使用[`default`](#django.db.models.Field.default "django.db.models.Field.default")参数: + + + + + +``` +import uuid +from django.db import models + +class MyUUIDModel(models.Model): + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + # other fields + +``` + + + + + +注意:这里传递给default是一个可调用的对象(即一个省略了括号的方法),而不是传递一个`UUID`实例给`default` + + + + + + + +## 关系字段 + +Django 同样定义了一系列的字段来描述数据库之间的关联。 + + + +### ForeignKey + + + +_class_ `ForeignKey`(_othermodel_[, _**options_]) + + + +多对一关系。需要一个位置参数:与该模型关联的类。 + +若要创建一个递归的关联 —— 对象与自己具有多对一的关系 —— 请使用`models.ForeignKey('self')`。 + +如果你需要关联到一个还没有定义的模型,你可以使用模型的名字而不用模型对象本身: + + + + + +``` +from django.db import models + +class Car(models.Model): + manufacturer = models.ForeignKey('Manufacturer') + # ... + +class Manufacturer(models.Model): + # ... + pass + +``` + + + + + +若要引用在其它应用中定义的模型,你可以用带有完整标签名的模型来显式指定。例如,如果上面提到的`Manufacturer` 模型是在一个名为`production` 的应用中定义的,你应该这样使用它: + + + + + +``` +class Car(models.Model): + manufacturer = models.ForeignKey('production.Manufacturer') + +``` + + + + + +在解析两个应用之间具有相互依赖的导入时,这种引用将会很有帮助。 + +`ForeignKey` 会自动创建数据库索引。你可以通过设置[`db_index`](#django.db.models.Field.db_index "django.db.models.Field.db_index") 为`False` 来取消。如果你创建外键是为了一致性而不是用来Join,或者如果你将创建其它索引例如部分或多列索引,你也许想要避免索引的开销。 + + + +警告 + +不建议从一个没有迁移的应用中创建一个`ForeignKey` 到一个具有迁移的应用。更多详细信息,请参阅[_依赖性文档_](../../topics/migrations.html#unmigrated-dependencies)。 + + + + + +#### 数据库中的表示 + +在幕后,Django 会在字段名上添加`"_id"` 来创建数据库中的列名。在上面的例子中,`Car` 模型的数据库表将会拥有一个`manufacturer_id` 列。(你可以通过显式指定[`db_column`](#django.db.models.Field.db_column "django.db.models.Field.db_column")?改变它)。但是,你的代码永远不应该处理数据库中的列名称,除非你需要编写自定义的SQL。你应该永远只处理你的模型对象中的字段名称。 + + + + + +#### 参数 + +[`ForeignKey`](#django.db.models.ForeignKey "django.db.models.ForeignKey") 接受额外的参数集 —— 全都是可选的 —— 它们定义关联关系如何工作的细节。 + + + +`ForeignKey.limit_choices_to` + + + +当这个字段使用`模型表单`或者Admin 渲染时(默认情况下,查询集中的所有对象都可以使用),为这个字段设置一个可用的选项。它可以是一个字典、一个[`Q`](querysets.html#django.db.models.Q "django.db.models.Q") 对象或者一个返回字典或[`Q`](querysets.html#django.db.models.Q "django.db.models.Q")对象的可调用对象。 + +例如: + + + + + +``` +staff_member = models.ForeignKey(User, limit_choices_to={'is_staff': True}) + +``` + + + + + +将使得`模型表单` 中对应的字段只列出`is_staff=True` 的`Users`。 这在Django 的Admin 中也可能有用处。 + +可调用对象的形式同样非常有用,比如与Python 的`datetime`模块一起使用来限制选择的时间范围。例如: + + + + + +``` +def limit_pub_date_choices(): + return {'pub_date__lte': datetime.date.utcnow()} + +limit_choices_to = limit_pub_date_choices + +``` + + + + + +如果`limit_choices_to` 自己本身是或者返回一个用于[_复杂查询_](../../topics/db/queries.html#complex-lookups-with-q)的[`Q 对象`](querysets.html#django.db.models.Q "django.db.models.Q"),当字段没有在模型的`ModelAdmin`中的[`raw_id_fields`](../contrib/admin/index.html#django.contrib.admin.ModelAdmin.raw_id_fields "django.contrib.admin.ModelAdmin.raw_id_fields") 列出时,它将只会影响Admin中的可用的选项。 + +Changed in Django 1.7: + +以前的Django 版本不允许传递一个可调用的对象给`limit_choices_to`。 + + + + + +注 + +如果`limit_choices_to` 使用可调用对象,这个可调用对象将在每次创建一个新表单的时候都调用。它还可能在一个模型校验的时候调用,例如被管理命令或者Admin。Admin 多次构造查询集来验证表单在各种边缘情况下的输入,所以你的可调用对象可能调用多次。 + + + + + + + + + +`ForeignKey.related_name` + + + +这个名称用于让关联的对象反查到源对象。它还是[`related_query_name`](#django.db.models.ForeignKey.related_query_name "django.db.models.ForeignKey.related_query_name") 的默认值(关联的模型进行反向过滤时使用的名称)。完整的解释和示例参见[_关联对象的文档_](../../topics/db/queries.html#backwards-related-objects)。注意,当你为[_抽象模型_](../../topics/db/models.html#abstract-base-classes)定义关联关系的时,必须设置这个参数的值;而且当你这么做的时候需要用到[_一些特殊语法_](../../topics/db/models.html#abstract-related-name)。 + +如果你不想让Django 创建一个反向关联,请设置`related_name` 为 `'+'` 或者以`'+'` 结尾。 例如,下面这行将确定`User` 模型将不会有到这个模型的返回关联: + + + + + +``` +user = models.ForeignKey(User, related_name='+') + +``` + + + + + + + + + + + +`ForeignKey.related_query_name` + + + +这个名称用于目标模型的反向过滤。如果设置了[`related_name`](#django.db.models.ForeignKey.related_name "django.db.models.ForeignKey.related_name"),则默认为它的值,否则默认值为模型的名称: + + + + + +``` +# Declare the ForeignKey with related_query_name +class Tag(models.Model): + article = models.ForeignKey(Article, related_name="tags", related_query_name="tag") + name = models.CharField(max_length=255) + +# That's now the name of the reverse filter +Article.objects.filter(tag__name="important") + +``` + + + + + + + + + + + +`ForeignKey.to_field` + + + +关联到的关联对象的字段名称。默认地,Django 使用关联对象的主键。 + + + + + + + +`ForeignKey.db_constraint` + + + +控制是否在数据库中为这个外键创建约束。默认值为`True`,而且这几乎一定是你想要的效果;设置成`False` 对数据的完整性来说是很糟糕的。即便如此,有一些场景你也许想要这么设置: + +* 你有遗留的无效数据。 +* 你正在对数据库缩容。 + +如果被设置成`False`,访问一个不存在的关联对象将抛出 `DoesNotExist` 异常。 + + + + + + + +`ForeignKey.on_delete` + + + +当一个[`ForeignKey`](#django.db.models.ForeignKey "django.db.models.ForeignKey") 引用的对象被删除时,Django 默认模拟SQL 的`ON DELETE CASCADE` 的约束行为,并且删除包含该`ForeignKey`的对象。这种行为可以通过设置[`on_delete`](#django.db.models.ForeignKey.on_delete "django.db.models.ForeignKey.on_delete") 参数来改变。例如,如果你有一个可以为空的[`ForeignKey`](#django.db.models.ForeignKey "django.db.models.ForeignKey"),在其引用的对象被删除的时你想把这个ForeignKey 设置为空: + + + + + +``` +user = models.ForeignKey(User, blank=True, null=True, on_delete=models.SET_NULL) + +``` + + + + + + + + + +[`on_delete`](#django.db.models.ForeignKey.on_delete "django.db.models.ForeignKey.on_delete") 在[`django.db.models`](../../topics/db/models.html#module-django.db.models "django.db.models")中可以找到的值有: + +* + + `CASCADE` + + + + 级联删除;默认值。 + + + + + +* + + `PROTECT` + + + + 抛出[`ProtectedError`](../exceptions.html#django.db.models.ProtectedError "django.db.models.ProtectedError") 以阻止被引用对象的删除,它是[`django.db.IntegrityError`](../exceptions.html#django.db.IntegrityError "django.db.IntegrityError") 的一个子类。 + + + + + +* + + `SET_NULL` + + + + 把[`ForeignKey`](#django.db.models.ForeignKey "django.db.models.ForeignKey") 设置为null; [`null`](#django.db.models.Field.null "django.db.models.Field.null") 参数为`True` 时才可以这样做。 + + + + + +* + + `SET_DEFAULT` + + + + [`ForeignKey`](#django.db.models.ForeignKey "django.db.models.ForeignKey") 值设置成它的默认值;此时必须设置[`ForeignKey`](#django.db.models.ForeignKey "django.db.models.ForeignKey") 的default 参数。 + + + + + +* + + `SET`() + + + + 设置[`ForeignKey`](#django.db.models.ForeignKey "django.db.models.ForeignKey") 为传递给[`SET()`](#django.db.models.SET "django.db.models.SET") 的值,如果传递的是一个可调用对象,则为调用后的结果。在大部分情形下,传递一个可调用对象用于避免models.py 在导入时执行查询: + + + + + + ``` +from django.conf import settings + from django.contrib.auth import get_user_model + from django.db import models + + def get_sentinel_user(): + return get_user_model().objects.get_or_create(username='deleted')[0] + + class MyModel(models.Model): + user = models.ForeignKey(settings.AUTH_USER_MODEL, + on_delete=models.SET(get_sentinel_user)) + +``` + + + + + + + + + +* + + `DO_NOTHING` + + + + 不采取任何动作。如果你的数据库后端强制引用完整性,它将引发一个[`IntegrityError`](../exceptions.html#django.db.IntegrityError "django.db.IntegrityError") ,除非你手动添加一个`ON DELETE` 约束给数据库自动(可能要用到[_初始化的SQL_](../../howto/initial-data.html#initial-sql))。 + + + + + + + +`ForeignKey.swappable` + + + +New in Django 1.7. + +控制迁移框架的的重复行为如果该[`ForeignKey`](#django.db.models.ForeignKey "django.db.models.ForeignKey") 指向一个可切换的模型。如果它是默认值`True`,那么如果[`ForeignKey`](#django.db.models.ForeignKey "django.db.models.ForeignKey") 指向的模型与`settings.AUTH_USER_MODEL` 匹配(或其它可切换的模型),则保存在迁移中的关联关系将使用对setting 中引用而不是直接对模型的引用。 + +只有当你确定你的模型将永远指向切换后的模型 —— 例如如果它是专门为你的自定义用户模型设计的模型时,你才会想将它设置成`False`。 + +设置为`False` 并不表示你可以引用可切换的模型即使在它被切换出去之后 —— `False` 只是表示生成的迁移中ForeignKey 将始终引用你指定的准确模型(所以,如果用户试图允许一个你不支持的User 模型时将会失败)。 + +如果有疑问,请保留它的默认值`True`。 + + + + + + + +`ForeignKey.allow_unsaved_instance_assignment` + + + +New in Django 1.8: + +添加这个标志是为了向后兼容,因为老版本的Django 始终允许赋值未保存的模型实例。 + + + +Django 阻止未保存的模型实例被分配给一个`ForeignKey` 字段以防止意味的数据丢失(当保存一个模型实例时,未保存的外键将默默忽略)。 + +如果你需要允许赋值未保存的实例且不关心数据的丢失(例如你不会保存对象到数据库),你可以通过创建这个字段的子类并设置其`allow_unsaved_instance_assignment` 属性为`True` 来关闭这个检查。例如: + + + + + +``` +class UnsavedForeignKey(models.ForeignKey): + # A ForeignKey which can point to an unsaved object + allow_unsaved_instance_assignment = True + +class Book(models.Model): + author = UnsavedForeignKey(Author) + +``` + + + + + + + + + + + + + + + +### ManyToManyField + + + +_class_ `ManyToManyField`(_othermodel_[, _**options_]) + + + +一个多对多关联。要求一个关键字参数:与该模型关联的类,与[`ForeignKey`](#django.db.models.ForeignKey "django.db.models.ForeignKey") 的工作方式完全一样,包括[_递归关系_](#recursive-relationships) 和[_惰性关系_](#lazy-relationships)。 + +关联的对象可以通过字段的[`RelatedManager`](relations.html#django.db.models.fields.related.RelatedManager "django.db.models.fields.related.RelatedManager") 添加、删除和创建。 + + + +警告 + +不建议从一个没有迁移的应用中创建一个`ManyToManyField`到一个具有迁移的应用。 更多细节参见[_依赖性文档_](../../topics/migrations.html#unmigrated-dependencies)。 + + + + + +#### 数据库中的表示 + +在幕后,Django 创建一个中间表来表示多对多关系。默认情况下,这张中间表的名称使用多对多字段的名称和包含这张表的模型的名称生成。因为某些数据库支持的表的名字的长度有限制,这些标的名称将自动截短到64 个字符并加上一个唯一性的哈希值。这意味着,你看的表的名称可能类似 `author_books_9cdf4`;这再正常不过了。你可以使用[`db_table`](#django.db.models.ManyToManyField.db_table "django.db.models.ManyToManyField.db_table") 选项手工提供中间表的名称。 + + + + + +#### 参数 + +[`ManyToManyField`](#django.db.models.ManyToManyField "django.db.models.ManyToManyField") 接收一个参数集来控制关联关系的功能 —— 它们都是可选的。 + + + +`ManyToManyField.related_name` + + + +与[`ForeignKey.related_name`](#django.db.models.ForeignKey.related_name "django.db.models.ForeignKey.related_name") 相同。 + + + + + + + +`ManyToManyField.related_query_name` + + + +与[`ForeignKey.related_query_name`](#django.db.models.ForeignKey.related_query_name "django.db.models.ForeignKey.related_query_name") 相同。 + + + + + + + +`ManyToManyField.limit_choices_to` + + + +与[`ForeignKey.limit_choices_to`](#django.db.models.ForeignKey.limit_choices_to "django.db.models.ForeignKey.limit_choices_to") 相同。 + +`limit_choices_to` 对于使用[`through`](#django.db.models.ManyToManyField.through "django.db.models.ManyToManyField.through") 参数自定义中间表的`ManyToManyField` 不生效。 + + + + + + + +`ManyToManyField.symmetrical` + + + +只用于与自身进行关联的ManyToManyField。例如下面的模型: + + + + + +``` +from django.db import models + +class Person(models.Model): + friends = models.ManyToManyField("self") + +``` + + + + + +当Django 处理这个模型的时候,它定义该模型具有一个与自身具有多对多关联的[`ManyToManyField`](#django.db.models.ManyToManyField "django.db.models.ManyToManyField"),因此它不会向`Person` 类添加`person_set` 属性。Django 将假定这个[`ManyToManyField`](#django.db.models.ManyToManyField "django.db.models.ManyToManyField") 字段是对称的 —— 如果我是你的朋友,那么你也是我的朋友。 + +如果你希望与`self` 进行多对多关联的关系不具有对称性,可以设置[`symmetrical`](#django.db.models.ManyToManyField.symmetrical "django.db.models.ManyToManyField.symmetrical") 为`False`。这会强制让Django 添加一个描述器给反向的关联关系,以使得[`ManyToManyField`](#django.db.models.ManyToManyField "django.db.models.ManyToManyField") 的关联关系不是对称的。 + + + + + + + +`ManyToManyField.through` + + + +Django 会自动创建一个表来管理多对多关系。不过,如果你希望手动指定中介表,可以使用[`through`](#django.db.models.ManyToManyField.through "django.db.models.ManyToManyField.through") 选项来指定Django 模型来表示你想要使用的中介表。 + +这个选项最常见的使用场景是当你想要关联[_额外的数据到多对多关联关系_](../../topics/db/models.html#intermediary-manytomany)的时候。 + +如果你没有显式指定`through` 的模型,仍然会有一个隐式的`through` 模型类,你可以用它来直接访问对应的表示关联关系的数据库表。它由三个字段来链接模型。 + +如果源模型和目标不同,则生成以下字段: + +* `id`:关系的主键。 +* `_id`:声明`ManyToManyField` 字段的模型的`id`。 +* `_id`:`ManyToManyField` 字段指向的模型的`id`。 + +如果`ManyToManyField` 的源模型和目标模型相同,则生成以下字段: + +* `id`:关系的主键。 +* `from__id`:源模型实例的`id`。 +* `to__id`:目标模型实例的`id`。 + +这个类可以让一个给定的模型像普通的模型那样查询与之相关联的记录。 + + + + + + + +`ManyToManyField.through_fields` + + + +New in Django 1.7. + +只能在指定了自定义中间模型的时候使用。 Django 一般情况会自动决定使用中间模型的哪些字段来建立多对多关联。但是,考虑如下模型: + + + + + +``` +from django.db import models + +class Person(models.Model): + name = models.CharField(max_length=50) + +class Group(models.Model): + name = models.CharField(max_length=128) + members = models.ManyToManyField(Person, through='Membership', through_fields=('group', 'person')) + +class Membership(models.Model): + group = models.ForeignKey(Group) + person = models.ForeignKey(Person) + inviter = models.ForeignKey(Person, related_name="membership_invites") + invite_reason = models.CharField(max_length=64) + +``` + + + + + +`Membership` 有_两个_ 外键指向`Person` (`person` 和`inviter`),这使得关联关系含混不清并让Django 不知道使用哪一个。在这种情况下,你必须使用`through_fields` 明确指定Django 应该使用哪些外键,就像上面例子一样。 + +`through_fields` 接收一个二元组`('field1', 'field2')`,其中`field1` 为指向定义[`ManyToManyField`](#django.db.models.ManyToManyField "django.db.models.ManyToManyField") 字段的模型的外键名称(本例中为`group`),`field2` 为指向目标模型的外键的名称(本例中为`person`)。 + +当中间模型具有多个外键指向多对多关联关系模型中的任何一个(或两个),你_必须_ 指定`through_fields`。这通用适用于[_递归的关联关系_](#recursive-relationships),当用到中间模型而有多个外键指向该模型时,或当你想显式指定Django 应该用到的两个字段时。 + +递归的关联关系使用的中间模型始终定义为非对称的,也就是[`symmetrical=False`](#django.db.models.ManyToManyField.symmetrical "django.db.models.ManyToManyField.symmetrical") —— 所以具有源和目标的概念。这种情况下,`'field1'` 将作为管理关系的源,而`'field2'` 作为目标。 + + + + + + + +`ManyToManyField.db_table` + + + +为存储多对多数据而创建的表的名称。如果没有提供,Django 将基于定义关联关系的模型和字段假设一个默认的名称。 + + + + + + + +`ManyToManyField.db_constraint` + + + +控制中间表中的外键是否创建约束。默认为`True`,而且这是几乎就是你想要的;设置为`False` 对数据完整性将非常糟糕。下面是你可能需要这样设置的一些场景: + +* 你具有不合法的遗留数据。 +* 你正在对数据库缩容。 + +不可以同时传递`db_constraint` 和 `through`。 + + + + + + + +`ManyToManyField.swappable` + + + +New in Django 1.7. + +控制[`ManyToManyField`](#django.db.models.ManyToManyField "django.db.models.ManyToManyField") 指向一个可切换的模型时迁移框架的行为。如果它是默认值`True`,那么如果[`ManyToManyField`](#django.db.models.ManyToManyField "django.db.models.ManyToManyField") 指向的模型与`settings.AUTH_USER_MODEL` 匹配(或其它可切换的模型),则保存在迁移中的关联关系将使用对setting 中引用而不是直接对模型的引用。 + +只有当你确定你的模型将永远指向切换后的模型 —— 例如如果它是专门为你的自定义用户模型设计的模型时,你才会想将它设置成`False`。 + +如果不确定,请将它保留为`True`。 + + + + + + + +`ManyToManyField.allow_unsaved_instance_assignment` + + + +New in Django 1.8. + +与[`ForeignKey.allow_unsaved_instance_assignment`](#django.db.models.ForeignKey.allow_unsaved_instance_assignment "django.db.models.ForeignKey.allow_unsaved_instance_assignment") 的工作方式类似。 + + + + + +[`ManyToManyField`](#django.db.models.ManyToManyField "django.db.models.ManyToManyField") 不支持[`validators`](#django.db.models.Field.validators "django.db.models.Field.validators")。 + +[`null`](#django.db.models.Field.null "django.db.models.Field.null") 不生效,因为无法在数据库层次要求关联关系。 + + + + + + + +### OneToOneField + + + +_class_ `OneToOneField`(_othermodel_[, _parent_link=False_, _**options_]) + + + +一对一关联关系。概念上讲,这个字段很像是[`ForeignKey`](#django.db.models.ForeignKey "django.db.models.ForeignKey") 设置了[`unique=True`](#django.db.models.Field.unique "django.db.models.Field.unique"),不同的是它会直接返回关系另一边的单个对象。 + +它最主要的用途是作为扩展自另外一个模型的主键;例如,[_多表继承_](../../topics/db/models.html#multi-table-inheritance)就是通过对子模型添加一个隐式的一对一关联关系到父模型实现的。 + +需要一个位置参数:与该模型关联的类。 它的工作方式与[`ForeignKey`](#django.db.models.ForeignKey "django.db.models.ForeignKey") 完全一致,包括所有与[_递归关系_](#recursive-relationships)和[_惰性关系_](#lazy-relationships)相关的选项。 + +如果你没有指定`OneToOneField` 的[`related_name`](#django.db.models.ForeignKey.related_name "django.db.models.ForeignKey.related_name") 参数,Django 将使用当前模型的小写的名称作为默认值。 + +例如下面的例子: + + + + + +``` +from django.conf import settings +from django.db import models + +class MySpecialUser(models.Model): + user = models.OneToOneField(settings.AUTH_USER_MODEL) + supervisor = models.OneToOneField(settings.AUTH_USER_MODEL, related_name='supervisor_of') + +``` + + + + + +你将使得`User` 模型具有以下属性: + + + + + +``` +>>> user = User.objects.get(pk=1) +>>> hasattr(user, 'myspecialuser') +True +>>> hasattr(user, 'supervisor_of') +True + +``` + + + + + +当反向访问关联关系时,如果关联的对象不存在对应的实例,则抛出`DoesNotExist` 异常。例如,如果一个User 没有`MySpecialUser` 指定的supervisor: + + + + + +``` +>>> user.supervisor_of +Traceback (most recent call last): + ... +DoesNotExist: User matching query does not exist. + +``` + + + + + +另外,`OneToOneField` 除了接收[`ForeignKey`](#django.db.models.ForeignKey "django.db.models.ForeignKey") 接收的所有额外的参数之外,还有另外一个参数: + + + +`OneToOneField.parent_link` + + + +当它为`True` 并在继承自另一个[_具体模型_](../../glossary.html#term-concrete-model) 的模型中使用时,表示该字段应该用于反查的父类的链接,而不是在子类化时隐式创建的`OneToOneField`。 + + + + + +`OneToOneField` 的使用示例参见[_One-to-one 关联关系_](../../topics/db/examples/one_to_one.html)。 + + + + + + + +## Field API 参考 + + + +_class_ `Field` + + + +`Field` 是一个抽象的类, 用来代表数据库中的表的一列. Django 使用这些fields 去创建表 ([`db_type()`](#django.db.models.Field.db_type "django.db.models.Field.db_type")), 去建立Python中的类型和数据库中类型的映射关系 ([`get_prep_value()`](#django.db.models.Field.get_prep_value "django.db.models.Field.get_prep_value")) 反之亦然 ([`from_db_value()`](#django.db.models.Field.from_db_value "django.db.models.Field.from_db_value")), 并且实现[_Lookup API reference_](lookups.html) ([`get_prep_lookup()`](#django.db.models.Field.get_prep_lookup "django.db.models.Field.get_prep_lookup")). + +field 是不同Django版本API中最根本的部分,尤其是[`models`](instances.html#django.db.models.Model "django.db.models.Model") and [`querysets`](querysets.html#django.db.models.query.QuerySet "django.db.models.query.QuerySet"). + +在模型中,一个字段被实例化为类的属性,并表现为一个特定的表的列,详情查看[_Models_](../../topics/db/models.html). 它具有[`null`](#django.db.models.Field.null "django.db.models.Field.null")和[`唯一`](#django.db.models.Field.unique "django.db.models.Field.unique")等属性,以及Django用于将字段值映射到数据库特定值的方法。 + +`字段`是[`RegisterLookupMixin`](lookups.html#django.db.models.lookups.RegisterLookupMixin "django.db.models.lookups.RegisterLookupMixin")的子类,因此可以在其上注册[`Transform`](lookups.html#django.db.models.Transform "django.db.models.Transform")和[`Lookup`](lookups.html#django.db.models.Lookup "django.db.models.Lookup") `QuerySet` s(例如`field_name__exact =“foo”`)。默认情况下,所有[_内置查找_](querysets.html#field-lookups)都已注册。 + +Django的所有内建字段,如[`CharField`](#django.db.models.CharField "django.db.models.CharField")都是`Field`的特定实现。如果您需要自定义字段,则可以将任何内置字段子类化,也可以从头开始写入`字段`。无论哪种情况,请参阅[_编写自定义模型字段_](../../howto/custom-model-fields.html)。 + + + +`description` + + + +字段的详细说明,例如用于[`django.contrib.admindocs`](../contrib/admin/admindocs.html#module-django.contrib.admindocs "django.contrib.admindocs: Django's admin documentation generator.")应用程序。 + +描述可以是以下形式: + + + + + +``` +description = _("String (up to %(max_length)s)") + +``` + + + + + +其中参数从字段的`__ dict __`插入。 + + + + + +To map a `Field` to a database-specific type, Django exposes two methods: + + + +`get_internal_type`() + + + +返回一个字符串,命名此字段以用于后端特定用途。默认情况下,它返回类名。 + +有关自定义字段中的用法,请参见[_模拟内置字段类型_](../../howto/custom-model-fields.html#emulating-built-in-field-types)。 + + + + + + + +`db_type`(_connection_) + + + +返回[`字段`](#django.db.models.Field "django.db.models.Field")的数据库列数据类型,同时考虑`连接`。 + +有关自定义字段中的用法,请参见[_自定义数据库类型_](../../howto/custom-model-fields.html#custom-database-types)。 + + + + + +有三种主要情况,Django需要与数据库后端和字段交互: + +* 当它查询数据库(Python值 转为 数据库后端值) +* 当它从数据库加载数据(数据库后端值 转为 Python值) +* 当它保存到数据库(Python值 转为 数据库后端值) + +查询时,使用[`get_db_prep_value()`](#django.db.models.Field.get_db_prep_value "django.db.models.Field.get_db_prep_value")和[`get_prep_value()`](#django.db.models.Field.get_prep_value "django.db.models.Field.get_prep_value"): + + + +`get_prep_value`(_value_) + + + +`value`是模型属性的当前值,方法应以已准备好用作查询中的参数的格式返回数据。 + +有关使用方式,请参阅[_将Python对象转换为查询值_](../../howto/custom-model-fields.html#converting-python-objects-to-query-values)。 + + + + + + + +`get_db_prep_value`(_value_, _connection_, _prepared=False_) + + + +将`值`转换为后端特定值。如果`prepared = True`和[`get_prep_value()`](#django.db.models.Field.get_prep_value "django.db.models.Field.get_prep_value") if为`False`,则默认返回`值`。 + +有关用法,请参见[_将查询值转换为数据库值_](../../howto/custom-model-fields.html#converting-query-values-to-database-values)。 + + + + + +加载数据时,使用[`from_db_value()`](#django.db.models.Field.from_db_value "django.db.models.Field.from_db_value"): + + + +`from_db_value`(_value_, _expression_, _connection_, _context_) + + + +New in Django 1.8. + +将数据库返回的值转换为Python对象。它与[`get_prep_value()`](#django.db.models.Field.get_prep_value "django.db.models.Field.get_prep_value")相反。 + +此方法不用于大多数内置字段,因为数据库后端已返回正确的Python类型,或后端本身执行转换。 + +有关用法,请参见[_将值转换为Python对象_](../../howto/custom-model-fields.html#converting-values-to-python-objects)。 + + + +注意 + +出于性能原因,`from_db_value`在不需要它的字段(所有Django字段)上不实现为无操作。因此,您不能在定义中调用`super`。 + + + + + + + +保存时,使用[`pre_save()`](#django.db.models.Field.pre_save "django.db.models.Field.pre_save")和[`get_db_prep_save()`](#django.db.models.Field.get_db_prep_save "django.db.models.Field.get_db_prep_save") + + + +`get_db_prep_save`(_value_, _connection_) + + + +与[`get_db_prep_value()`](#django.db.models.Field.get_db_prep_value "django.db.models.Field.get_db_prep_value")相同,但在字段值必须_保存到数据库_时调用。默认返回[`get_db_prep_value()`](#django.db.models.Field.get_db_prep_value "django.db.models.Field.get_db_prep_value")。 + + + + + + + +`pre_save`(_model_instance_, _add_) + + + +在[`get_db_prep_save()`](#django.db.models.Field.get_db_prep_save "django.db.models.Field.get_db_prep_save")之前调用方法以在保存之前准备值(例如,对于[`DateField.auto_now`](#django.db.models.DateField.auto_now "django.db.models.DateField.auto_now"))。 + +`model_instance`是此字段所属的实例,`add`是实例是否第一次保存到数据库。 + +它应该返回此字段的`model_instance`适当属性的值。属性名称位于`self.attname`(这是由[`Field`](#django.db.models.Field "django.db.models.Field")设置)。 + +有关使用情况,请参阅[_保存前预处理值_](../../howto/custom-model-fields.html#preprocessing-values-before-saving)。 + + + + + +当在字段上使用查找时,该值可能需要“准备”。Django公开了两种方法: + + + +`get_prep_lookup`(_lookup_type_, _value_) + + + +在用于查找之前,准备`values`到数据库。The `lookup_type` will be one of the valid Django filter lookups: `"exact"`, `"iexact"`, `"contains"`, `"icontains"`, `"gt"`, `"gte"`, `"lt"`, `"lte"`, `"in"`, `"startswith"`, `"istartswith"`, `"endswith"`, `"iendswith"`, `"range"`, `"year"`, `"month"`, `"day"`, `"isnull"`, `"search"`, `"regex"`, and `"iregex"`. + +New in Django 1.7. + +如果你使用[_自定义查找_](lookups.html),则`lookup_type`可以是在字段中注册的任何`lookup_name`。 + +有关用法,请参见[_准备在数据库查找中使用的值_](../../howto/custom-model-fields.html#preparing-values-for-use-in-database-lookups)。 + + + + + + + +`get_db_prep_lookup`(_lookup_type_, _value_, _connection_, _prepared=False_) + + + +类似于[`get_db_prep_value()`](#django.db.models.Field.get_db_prep_value "django.db.models.Field.get_db_prep_value"),但用于执行查找。 + +与[`get_db_prep_value()`](#django.db.models.Field.get_db_prep_value "django.db.models.Field.get_db_prep_value")一样,将用于查询的特定连接作为`connection`传递。此外,`prepared`描述该值是否已经用[`get_prep_lookup()`](#django.db.models.Field.get_prep_lookup "django.db.models.Field.get_prep_lookup")准备好。 + + + + + +字段经常从不同的类型接收它们的值,从序列化或从表单。 + + + +`to_python`(_value_) + + + +改变这个值为正确的python对象。它作为[`value_to_string()`](#django.db.models.Field.value_to_string "django.db.models.Field.value_to_string")的反向操作,也在[`clean()`](instances.html#django.db.models.Model.clean "django.db.models.Model.clean")中调用。 + +有关用法,请参见[_将值转换为Python对象_](../../howto/custom-model-fields.html#converting-values-to-python-objects)。 + + + + + +除了保存到数据库,该字段还需要知道如何序列化其值: + + + +`value_to_string`(_obj_) + + + +将`obj`转换为字符串。用于序列化字段的值。 + +有关用法,请参见[_转换字段数据以进行序列化_](../../howto/custom-model-fields.html#converting-model-field-to-serialization)。 + + + + + +当使用[`模型 表单`](../../topics/forms/modelforms.html#django.forms.ModelForm "django.forms.ModelForm")时,`Field`需要知道应由哪个表单字段表示: + + + +`formfield`(_form_class=None_, _choices_form_class=None_, _**kwargs_) + + + +返回[`ModelForm`](../../topics/forms/modelforms.html#django.forms.ModelForm "django.forms.ModelForm")的此字段的默认[`django.forms.Field`](../forms/fields.html#django.forms.Field "django.forms.Field")。 + +By default, if both `form_class` and `choices_form_class` are `None`, it uses [`CharField`](../forms/fields.html#django.forms.CharField "django.forms.CharField"); if `choices_form_class` is given, it returns [`TypedChoiceField`](../forms/fields.html#django.forms.TypedChoiceField "django.forms.TypedChoiceField"). + +有关用法,请参见[_为模型字段指定表单字段_](../../howto/custom-model-fields.html#specifying-form-field-for-model-field)。 + + + + + + + +`deconstruct`() + + + +New in Django 1.7. + +返回具有足够信息的4元组,以重新创建字段: + +1. 模型上字段的名称。 +2. 字段的导入路径(例如`“django.db.models.IntegerField”`)。这应该是兼容的版本,所以不要太具体可能会更好。 +3. 位置参数列表。 +4. 关键字参数的字典。 + +必须将此方法添加到1.7之前的字段,才能使用[_迁移_](../../topics/migrations.html)迁移其数据。 + + + + + + + + + + + + + + + +# Field属性参考 + +New in Django 1.8. + +每个`字段`实例包含几个允许内省其行为的属性。Use these attributes instead of `isinstance` checks when you need to write code that depends on a field’s functionality. 这些属性可与[_Model._meta API_](meta.html#model-meta-field-api)一起使用,以缩小特定字段类型的搜索范围。自定义模型字段应实现这些标志。 + + + +## Attributes for fields + + + +`Field.auto_created` + + + +布尔标志,指示是否自动创建字段,例如模型继承使用的`OneToOneField`。 + + + + + + + +`Field.concrete` + + + +布尔标志,指示字段是否具有与其相关联的数据库列。 + + + + + + + +`Field.hidden` + + + +Boolean flag that indicates if a field is used to back another non-hidden field’s functionality (e.g. the `content_type` and `object_id` fields that make up a `GenericForeignKey`). The `hidden` flag is used to distinguish what constitutes the public subset of fields on the model from all the fields on the model. + + + +Note + +[`Options.get_fields()`](meta.html#django.db.models.options.Options.get_fields "django.db.models.options.Options.get_fields")默认排除隐藏字段。传入`include_hidden = True`可返回结果中的隐藏字段。 + + + + + + + + + +`Field.is_relation` + + + +布尔标志,指示字段是否包含对一个或多个其他模型的功能的引用(例如`ForeignKey`,`ManyToManyField`,`OneToOneField`等) 。 + + + + + + + +`Field.model` + + + +返回定义字段的模型。如果在模型的超类上定义了字段,则`模型`将引用超类,而不是实例的类。 + + + + + + + + + +## 具有关系的字段的属性 + +这些属性用于查询关系的基数和其他详细信息。这些属性存在于所有字段上;但是,如果字段是关系类型([`Field.is_relation=True`](#django.db.models.Field.is_relation "django.db.models.Field.is_relation")),则它们只有布尔值(而不是`None`)。 + + + +`Field.many_to_many` + + + +如果字段具有多对多关系,则布尔标志为`True`;否则为`False`。The only field included with Django where this is `True` is `ManyToManyField`. + + + + + + + +`Field.many_to_one` + + + +如果字段具有多对一关系,例如`ForeignKey`,则布尔标志为`True`;否则为`False`。 + + + + + + + +`Field.one_to_many` + + + +如果字段具有一对多关系(例如`GenericRelation`或`ForeignKey`的反向),则`True`的布尔标志;否则为`False`。 + + + + + + + +`Field.one_to_one` + + + +如果字段具有一对一关系,例如`OneToOneField`,则布尔标志为`True`;否则为`False`。 + + + + + + + +`Field.related_model` + + + +指向字段涉及的模型。例如,`ForeignKey(Author)`中的`Author`。如果字段具有通用关系(例如`GenericForeignKey`或`GenericRelation`),则`related_model`将为`None`。 + + + +> 译者:[Django 文档协作翻译小组](http://python.usyiyi.cn/django/index.html),原文:[Field types](https://docs.djangoproject.com/en/1.8/ref/models/fields)。 +> +> 本文以 [CC BY-NC-SA 3.0](http://creativecommons.org/licenses/by-nc-sa/3.0/cn/) 协议发布,转载请保留作者署名和文章出处。 +> +> [Django 文档协作翻译小组](http://python.usyiyi.cn/django/index.html)人手紧缺,有兴趣的朋友可以加入我们,完全公益性质。交流群:467338606。 + + + + + + diff --git a/2_5_3_Transactions.md b/2_5_3_Transactions.md new file mode 100644 index 0000000000000000000000000000000000000000..103036d91bdb00513a3afb3d7955f3b756bbba69 --- /dev/null +++ b/2_5_3_Transactions.md @@ -0,0 +1,555 @@ + + +# 数据库事务 + +Django 为你提供几种方法来控制如何管理数据库事务。 + + + +## 管理数据库事务 + + + +### Django’s default transaction behavior + +Django 的默认行为是运行在自动提交模式下。任何一个查询都立即被提交到数据库中,除非激活一个事务。[_具体细节看下面_](#autocommit-details). + +Django 用事务或者保存点去自动的保证复杂ORM各种查询操作的统一性,尤其是 [_delete()_](queries.html#topics-db-queries-delete) 和[_update()_](queries.html#topics-db-queries-update) 查询. + +Django’s [`测试用例`](../testing/tools.html#django.test.TestCase "django.test.TestCase") 也包装了事务性能原因的测试类 + + + + + +### 把事务绑定到HTTP 请求上 + +在web上一种简单处理事务的方式是把每个请求用事务包装起来.在每个你想保存这种行为的数据库的配置文件中,设置 [`ATOMIC_REQUESTS`](../../ref/settings.html#std:setting-DATABASE-ATOMIC_REQUESTS)值为 `True`, + +它是这样工作的。在调用一个view里面的方法之前,django开始一个事务如果发出的响应没有问题,Django就会提交这个事务。如果在view这里产生一个异常,Django就会回滚这次事务 + +你可能会在你的视图代码中执行一部分提交并且回滚,通常使用[`atomic()`](#django.db.transaction.atomic "django.db.transaction.atomic")context管理器.但是最后你的视图,要么是所有改变都提交执行,要么是都不提交。 + + + +警告 + +虽然这种简洁的事物模型看上去很吸引人, 但要注意当流量增长时它会表现出较差的效率。对每个视图开启一个事务是有所耗费的。其对性能的影响依赖于应用程序对数据库的查询语句效率和数据库当前的锁竞争情况。 + + + + + +预请求事务和流式响应 + +当一个视图返回一个 [`StreamingHttpResponse`](../../ref/request-response.html#django.http.StreamingHttpResponse "django.http.StreamingHttpResponse")时, 其读取的内容是由执行代码来产生的。因为视图调用已经返回,这样代码在事务的外部运行。 + +一般而言,在产生一个流式响应时,不建议再进行写数据库的操作,因为没有明智的方式在开始发送响应之后来处理错误。 + + + +在实际操作时,可以通过如下 [`atomic()`](#django.db.transaction.atomic "django.db.transaction.atomic")装饰器把这一功能简单地加载到视图函数上。? + +表示事务仅仅是在当前视图中有效,诸如模板响应之类的中间件(Middleware)操作是运行在事务之外的。 + +当 [`ATOMIC_REQUESTS`](../../ref/settings.html#std:setting-DATABASE-ATOMIC_REQUESTS)被启用后,仍然有办法来阻止视图运行一个事务操作。 + + + +`non_atomic_requests`(_using=None_)[[source]](../../_modules/django/db/transaction.html#non_atomic_requests) + + + +这个装饰器会否定一个由 [`ATOMIC_REQUESTS`](../../ref/settings.html#std:setting-DATABASE-ATOMIC_REQUESTS)设定的视图: + + + + + +``` +from django.db import transaction + +@transaction.non_atomic_requests +def my_view(request): + do_stuff() + +@transaction.non_atomic_requests(using='other') +def my_other_view(request): + do_stuff_on_the_other_database() + +``` + + + + + +它将仅工作在设定了此装饰器的视图上。 + + + + + + + + + +### 更加明确地控制事务 + +Django提供了单一的API来控制数据库事务。 + + + +`atomic`(_using=None_, _savepoint=True_)[[source]](../../_modules/django/db/transaction.html#atomic) + + + +原子性是由数据库的事务操作来界定的。 `atomic`允许我们在执行代码块时,在数据库层面提供原子性保证。 如果代码块成功完成, 相应的变化会被提交到数据库进行commit;如果执行期间遇到异常,则会将该段代码所涉及的所有更改回滚。. + +`atomic`块可以嵌套。 在下面的例子里,使用with语句,当一个内部块完成后,如果某个异常在外部块被抛出,内部块上的操作仍然可以回滚(前提是外部块也被atomic装饰过)。 + +`atomic` 被用作[_装饰器_](https://docs.python.org/3/glossary.html#term-decorator "(in Python v3.4)"): + + + + + +``` +from django.db import transaction + +@transaction.atomic +def viewfunc(request): + # This code executes inside a transaction. + do_stuff() + +``` + + + + + +atomic被用作 [_上下文管理器_](https://docs.python.org/3/glossary.html#term-context-manager "(in Python v3.4)"): + + + + + +``` +from django.db import transaction + +def viewfunc(request): + # This code executes in autocommit mode (Django's default). + do_stuff() + + with transaction.atomic(): + # This code executes inside a transaction. + do_more_stuff() + +``` + + + + + +经过 `atomic`装饰的代码在一个 try/except 块内允许使用常见的完整性错误检测语法: + + + + + +``` +from django.db import IntegrityError, transaction + +@transaction.atomic +def viewfunc(request): + create_parent() + + try: + with transaction.atomic(): + generate_relationships() + except IntegrityError: + handle_exception() + + add_children() + +``` + + + + + +在这个例子中,即使`generate_relationships()` 违反完整性约束导致了数据库错误, 你仍可以进行 `add_children()`的操作, 并且`create_parent()`的变化仍然存在。注意,当?`handle_exception()`被触发时,在`generate_relationships()`上的尝试操作已经被安全回滚,所以若有必要,这个异常的句柄也能够操作数据库。 + + + +避免在 `atomic`里捕获异常! + +当一个`原子块`执行完退出时,Django会审查是正常提交还是回滚。如果你在 `原子`块中捕获了异常的句柄, 你可能就向Django隐藏了问题的发生。这可能会导致意想不到的后果。 + +这主要是考虑到 [`DatabaseError`](../../ref/exceptions.html#django.db.DatabaseError "django.db.DatabaseError")和其诸如[`IntegrityError`](../../ref/exceptions.html#django.db.IntegrityError "django.db.IntegrityError")这样的子类。 若是遇到这样的错误,事务的原子性会被打破,Django会在`原子`代码块上执行回滚操作。如果你试图在回滚发生前运行数据库查询,Django会产生一个[`TransactionManagementError`](../../ref/exceptions.html#django.db.transaction.TransactionManagementError "django.db.transaction.TransactionManagementError")的异常。当一个ORM-相关的信号句柄操作异常时,你可能也会遇到类似的情形。 + +正确捕捉数据库异常应该是类似上文所讲 ,基于`atomic` 代码块来做。若有必要,可以额外增加一层`atomic`代码来用于此目的。这种模式还有另一个优势:它明确了当一个异常发生时,哪些操作将回滚。 + +如果你是从原始的SQL查询语句中捕获异常,则Django的行为是不明确的,而且是依赖于数据库的。 + + + +为了确保原子性达成, `atomic`会 disables一些 APIs. 在`atomic`代码块内试图 commit, roll back,或者更改数据库autocommit的状态都会导致异常。 + +`atomic`使用的 `using` 参数必须是数据库的名字. 如果这个参数没提供, Django默认使用 `"default"` 数据库。 + +在底层,Django的事务管理代码: + +* 当进入到最外层的 `atomic` 代码块时会打开一个事务; +* 当进入到内层`atomic`代码块时会创建一个保存点; +* 当退出内部块时会释放或回滚保存点; +* 当退出外部块时提交或回退事物。 + +你可以通过设置`savepoint` 参数为 `False`来使对内层的保存点失效。如果异常发生,若设置了savepoint,Django会在退出第一层代码块时执行回滚,否则会在最外层的代码块上执行回滚。 原子性始终会在外层事物上得到保证。这个选项仅仅用在设置保存点开销很明显时的情况下。它的缺点是打破了上述错误处理的原则。 + +在autocommit关闭的情况下,你可以使用`atomic`. 这只使用savepoints功能,即使是对于最外层的块。如果在最外层的块上声明`savepoint=False`,这将会产生一个错误。 + + + + + + + +性能考虑 + +所有打开的事务会对数据库带来性能成本。要尽量减少这种开销,尽量保持您的交易尽可能短。 在Django的请求/响应周期,如果你使用?[`atomic()`](#django.db.transaction.atomic "django.db.transaction.atomic")来执行长运行的进程,这尤其重要。 + + + + + + + + + +## Autocommit + + + +### 为什么 Django会使用autocommit + +在SQL标准中, 每个SQL语句在执行时都会启动一个事务,除非已经存在一个事务了。这样事务必须明确是提交还是回滚。 + +对应用程序开发者而言,这样非常不方便。为了避免这个问题,大多数数据库提供了一个autocommit模式。当 autocommit被打开并且事物处于活动状态时,每个SQL查询都可以看成是一个事物。也就是说, 不但每个查询是每个事物的开始,而且每个事物会自动提交或回滚,这取决于该查询是否成功执行。 + +[**PEP 249**](http://www.python.org/dev/peps/pep-0249), Python数据库API 规范v2.0, 需要将autocommit初试设置为关闭状态。Django覆盖了这个默认规范并且将autocommit设置为 on. + +要想避免这样, 你可以[_关闭事务管理器_](#deactivate-transaction-management), 但不建议这样做。 + + + + + +### 关闭事务管理器 + +你可以在配置文件里通过设置[`AUTOCOMMIT`](../../ref/settings.html#std:setting-DATABASE-AUTOCOMMIT)为?`False` 完全关闭Django的事物管理。如果这样做了,Django将不能启用autocommit,也不能执行任何 commits. 你只能遵照数据库层面的规则行为。 + +这就需要你对每个事物执行明确的commit操作,即使由Django或第三方库创建的。因此,这最好只用于你自定义的事物控制中间件或者是一些比较奇特的场景。 + + + + + + + +## 更低级别的API + + + +警告 + +如果可能的话,尽量优先选择[`atomic()`](#django.db.transaction.atomic "django.db.transaction.atomic")来控制事物 ,它遵守数据库的相关特性并且防止了非法操作。 + +低级别 API仅仅用于你自定义的事物管理场景。 + + + + + +### Autocommit + +在 [`django.db.transaction`](#module-django.db.transaction "django.db.transaction")模块里,Django提供了一个简单的API, 用于管理每个数据库的自提交状态。 + + + +`get_autocommit`(_using=None_)[[source]](../../_modules/django/db/transaction.html#get_autocommit) + + + + + +`set_autocommit`(_autocommit_, _using=None_)[[source]](../../_modules/django/db/transaction.html#set_autocommit) + + + +这些函数使用了一个 `using`参数,参数的值是数据库的名字。 如果参数没有提供, Django使用 `"default"` 数据库。 + +Autocommit初始是打开的。如果你把它关掉了,那么你有义务恢复它。 + +一旦你把autocommit 关掉了, 那么你得到就是数据库的默认行为, Django 不会帮你做任何事。虽然在 [**PEP 249**](http://www.python.org/dev/peps/pep-0249)有描述此规范,但数据库适配器的实现并不总与规范是一致的。请仔细检查你当前正在使用的数据库适配器文档。 + +在把autocommit改回on之前,你必须确保所有的SQL事物处于非活跃状态,通常是使用[`commit()`](#django.db.transaction.commit "django.db.transaction.commit")或者[`rollback()`](#django.db.transaction.rollback "django.db.transaction.rollback")这样的语法操作。 + +当 [`atomic()`](#django.db.transaction.atomic "django.db.transaction.atomic")代码块处于活跃状态时,Django会拒绝将autocommit从on的状态调整为off,因为这样会破坏原子性。? + + + + + +### 事务 + +事务是一系列数据库语句的原子集。即使程序在运行时崩溃了,数据库可以确保事物集中的所有变更要么都被提交,要么都被放弃。 + +Django 并没有提供一个API来开启一个事务。因为开始事务的预期方式是将[`set_autocommit()`](#django.db.transaction.set_autocommit "django.db.transaction.set_autocommit")设置为disable状态。 + +一旦你处于一个事物之中,你可以选择要么apply所有变更?[`commit()`](#django.db.transaction.commit "django.db.transaction.commit")提交它,要么取消所有变化 [`rollback()`](#django.db.transaction.rollback "django.db.transaction.rollback"). 这个函数功能是在 [`django.db.transaction`](#module-django.db.transaction "django.db.transaction")定义的。 + + + +`commit`(_using=None_)[[source]](../../_modules/django/db/transaction.html#commit) + + + + + +`rollback`(_using=None_)[[source]](../../_modules/django/db/transaction.html#rollback) + + + +这个函数通过 `using`指定数据库的名字作为参数。如果没提供, Django 使用`"default"` 数据库. + +当一个?[`atomic()`](#django.db.transaction.atomic "django.db.transaction.atomic")程序块在运行状态,Django会拒绝 commit 或rollback操作,因为这些操作是自动的。 + + + + + +### Savepoints + +保存点是在事物执行过程中的一个标记,它可以让你执行回滚事物的一部分 ,而不是整个事物。 保存点在 SQLite (≥ 3.6.8), PostgreSQL, Oracle和MySQL (当使用nnoDB存储引擎时)是有效的。在其他的数据库后端虽然也提供保存点的函数,但其实它们是空操作,实际不起任何作用。 + +如果你开启了autocommit,Savepoints并没有太多用处,因为这是Django的默认行为。然而, 一旦你使用 [`atomic()`](#django.db.transaction.atomic "django.db.transaction.atomic")开启了一个事物, 那么你所建立的一系列数据库操作将被视为一个整体,等待同时提交或回滚。 如果你触发了一个回滚,那么整个事物就要进行回滚。Savepoints提供了更细粒度的回滚,而不是用 `transaction.rollback()`对整个事物进行回滚. + +当嵌套使用 [`atomic()`](#django.db.transaction.atomic "django.db.transaction.atomic")装饰器时, 它会创建 savepoint以允许部分提交或回滚。 强烈建议你使用 [`atomic()`](#django.db.transaction.atomic "django.db.transaction.atomic")而不是下面描述的函数功能,当然他们也是公开API的一部分,并且现在也没有废除它们的计划。 + +每个函数都带一个 `using`参数,这个参数是你要操作的数据库的名字。 如果没有 `using`参数,则会使用 `"default"` 数据库。 + +Savepoints 是由[`django.db.transaction`](#module-django.db.transaction "django.db.transaction")里的三个函数来控制的: + + + +`savepoint`(_using=None_)[[source]](../../_modules/django/db/transaction.html#savepoint) + + + +创建一个新的保存点。这将实现在事物里对“好”的状态做一个标记点。返回值是 savepoint ID (`sid`). + + + + + + + +`savepoint_commit`(_sid_, _using=None_)[[source]](../../_modules/django/db/transaction.html#savepoint_commit) + + + +释放保存点`sid`. 自创建保存点进行的更改将成为事物的一部分。 + + + + + + + +`savepoint_rollback`(_sid_, _using=None_)[[source]](../../_modules/django/db/transaction.html#savepoint_rollback) + + + +回滚事物保存点`sid`. + + + + + +如果不支持保存点或者数据库未处于autocommit模式,这些函数将什么也不做。 + +此外,还有一个实用的功能: + + + +`clean_savepoints`(_using=None_)[[source]](../../_modules/django/db/transaction.html#clean_savepoints) + + + +重置用来生成唯一保存点ID的计数器: + + + + + +下面的例子演示了如何使用保存点: + + + + + +``` +from django.db import transaction + +# open a transaction +@transaction.atomic +def viewfunc(request): + + a.save() + # transaction now contains a.save() + + sid = transaction.savepoint() + + b.save() + # transaction now contains a.save() and b.save() + + if want_to_keep_b: + transaction.savepoint_commit(sid) + # open transaction still contains a.save() and b.save() + else: + transaction.savepoint_rollback(sid) + # open transaction now contains only a.save() + +``` + + + + + +保存点通过实现部分回滚实现对数据库报错的恢复。如果你是在一个 [`atomic()`](#django.db.transaction.atomic "django.db.transaction.atomic")块中这么干的话, 那整个块都会被回滚,因为Django并不知你在下一层做此处理操作。为了避免这样,你可以在下面函数中控制回滚行为。 + + + +`get_rollback`(_using=None_)[[source]](../../_modules/django/db/transaction.html#get_rollback) + + + + + +`set_rollback`(_rollback_, _using=None_)[[source]](../../_modules/django/db/transaction.html#set_rollback) + + + +当退出最内层atomic块时设置回滚标记为 `True`以实现强制回滚。这对在没有抛出异常的情况下触发一个回滚操作是很有用的。 + +将标志设为 `False` 阻止这样一个回滚。 在此之前,请确保你已经把事物回滚到了该原子块内一个已知良好的保存点。否则,你打破原子性,并且数据损坏可能会发生。 + + + + + + + +## 具体数据库的相关说明 + + + +### Savepoints in SQLite + +?SQLite ≥ 3.6.8之后开始支持savepoints,但由于[`sqlite3`](https://docs.python.org/3/library/sqlite3.html#module-sqlite3 "(in Python v3.4)")模块的设计缺陷导致其很难使用。 + +当自动提交被启用,保存点是没有意义的. 当被禁用时, [`sqlite3`](https://docs.python.org/3/library/sqlite3.html#module-sqlite3 "(in Python v3.4)")在savepoint之前已经进行了的隐式的提交。(实际上, 在任何诸如`SELECT`, `INSERT`, `UPDATE`, `DELETE`和 `REPLACE`等操作语句之前都会进行提交.) 这个问题有两个后果: + +* savepoint的低级别API只能用于内部事物。在一个[`atomic()`](#django.db.transaction.atomic "django.db.transaction.atomic") 块之内。 +* 当autocommit处于关闭状态时,是不可能使用[`atomic()`](#django.db.transaction.atomic "django.db.transaction.atomic") 的。 + + + + + +### MySQL中的事务 + +如果你正在使用 MySQL, 你的表也许支持事务,也许不支持。这具体依赖于你的MySQL版本和你正在使用的表的engine类型。(这里的表engine类型是指“InnoDB” 或 “MyISAM”等.) MySQL的事物特性不在本文讨论的范围之内,你可以从MySQL的官方站点获取[MySQL事物的相关信息](http://dev.mysql.com/doc/refman/5.6/en/sql-syntax-transactions.html). + +如果你安装的MySQL _不_支持事务, 那么Django会一直工作在自动提交模式 : 语句一旦被调用就会被执行和提交。 如果你安装的MySQL_确定_支持事物, Django会遵循本文所介绍的关于事务的处理原则。 + + + + + +### 处理PostgreSQL的交易中的异常 + + + +注意 + +本节内容只有当你实现你自己的事务管理时才相关。 这个问题不会出现在 Django默认模式和 [`atomic()`](#django.db.transaction.atomic "django.db.transaction.atomic") 自动控制的情况下。 + + + +在一个事物内部, 当调用一个PostgreSQL光标抛出一个异常 (通常是 `IntegrityError`), 后续所有在此同一个事物中的SQL将失败并报以下错误“current transaction is aborted, queries ignored until end of transaction block”. 虽然有时简单用`save()`是不太可能导致PostgreSQL异常的, 但仍然有更高级模式用法的可能, 例如在一个有唯一约束的字段保存对象, 保存使用force_insert/force_update 标志,或者调用一些定制化的SQL。 + +有几种方法可以从这种错误中恢复过来。 + + + +#### 事务回滚 + +第一个选择是回滚整个事物。例如: + + + + + +``` +a.save() # Succeeds, but may be undone by transaction rollback +try: + b.save() # Could throw exception +except IntegrityError: + transaction.rollback() +c.save() # Succeeds, but a.save() may have been undone + +``` + + + + + +调用 `transaction.rollback()` 回滚整个事物。任何未提交的数据库操作都会丢失。在此例中, 由 `a.save()`所保存的变更将会丢失,即使这个操作自身没有产生错误。 + + + + + +#### 保存点回滚 + +你可以使用 [_savepoints_](#topics-db-transactions-savepoints)来控制一个回滚的扩展。 在执行数据库操作可能失败之前,你可以设置或更新保存点; 这样,如果操作失败,您可以回滚该单违规操作,而不是整个事务。例如: + + + + + +``` +a.save() # Succeeds, and never undone by savepoint rollback +sid = transaction.savepoint() +try: + b.save() # Could throw exception + transaction.savepoint_commit(sid) +except IntegrityError: + transaction.savepoint_rollback(sid) +c.save() # Succeeds, and a.save() is never undone + +``` + + + + + +在此例中, 当`b.save()`抛出异常的情况下,`a.save()` 所做的更改将不会丢失。 + + + + + + + diff --git a/2_6_1_Supported databases.md b/2_6_1_Supported databases.md new file mode 100644 index 0000000000000000000000000000000000000000..fb03e00c11db1dd8308f12465e41f47a743107a4 --- /dev/null +++ b/2_6_1_Supported databases.md @@ -0,0 +1,819 @@ + + +# 数据库 + +Django试图尽可能多的支持所有数据库后端的特性。然而,并不是所有数据库都一样,所以我们必须在支持哪些特性和做出哪些安全的假定上做出设计决策。 + +本文描述了一些Django使用数据库的有关特性。当然,它并不想成为各服务器指定的文档或者参考手册的替代品。 + + + +## 综合说明 + + + +### 持续连接特性 + +持续连接的特性避免了每一次重新建立与数据库的连接的请求中所增加的压力。这些连接通过 [`CONN_MAX_AGE`](settings.html#std:setting-CONN_MAX_AGE) 参数(控制一个连接的最长存活时间)来控制。它可以被单独的设置在每一个数据库中。 + +参数的默认值为 `0` ,对每次请求结束时终止数据库连接的历史行为提供保护。当启用持续连接时,设置 [`CONN_MAX_AGE`](settings.html#std:setting-CONN_MAX_AGE) 的值(正数,单位为秒)为每个连接的最大存活时间。对于无限制的连接,请设置为 `None`。 + + + +#### 连接管理 + +Django在它第一次建立数据库查询的时候打开与数据库的连接。它保持着连接的通畅使得在随后的请求中可以再利用。一旦这个连接超过参数 [`CONN_MAX_AGE`](settings.html#std:setting-CONN_MAX_AGE) 的最长存活时间的限制或者不能更长久的存活时,Django就会终止连接。 + +详细地说,当Django需要的时或者当前不存在数据库连接时,它会自动的与数据库之间建立连接————要么是因为这是第一次连接,要么是因为先前连接被关闭。 + +在每个请求开始时后,如果连接到了设置的最长存活时间时,Django就会关闭它。如果你的数据库在之后一段时间终止了闲置的连接,你应该设置[`CONN_MAX_AGE`](settings.html#std:setting-CONN_MAX_AGE) 为一个更低的值,以至于Django不会尝试去连接数据库服务器上已经终止的连接。(这个问题可能仅仅影响流量比较低的站点。) + +在每个请求结束,如果它已经到达了最长存活时间或者它处在一个不可恢复的错误状态时,Django就会关闭连接。在处理请求中如果有任何数据库错误产生,Django就会检查是否连接仍然工作,不工作就关闭它。因此,数据库错误最多影响一个请求。如果连接不可用,下一个 请求会打开一个新的连接。 + + + + + +#### 注意事项 + +因为每个线程都保持着自己的连接,你的数据库至少必须在你的工作线程中支持尽可能多的并发线程。 + +有的时候数据库并不会在你的视图中有太多的访问,例如,因为数据库是一个外部系统,或者因为缓存的作用。在这种情况下,你应该设置[`CONN_MAX_AGE`](settings.html#std:setting-CONN_MAX_AGE)为一个低的值或者为`0`,因为这对保持一个连接并没有意义,而且连接不太可能被重复调用。这会有助于保持数据库的并发性连接数为一个小的值。 + +开发服务器对每个需要处理的请求创建一个新的线程,用来否定持续连接的影响。不要在开发过程中启用他们。 + +当Django建立与数据库的连接时,它会设置相应的参数,这取决于后台的使用情况。如果你启用了持续性连接,那么程序将不再重复每个请求。如果你修改了参数,例如连接的隔离级别或者是时区,你也应该恢复Django原有的默认值在每个请求结束时,强制使用一个合适的值在每个请求开始时,或者禁用持续性连接。 + + + + + + + +### 编码 + +Django假定所有的数据库使用UTF-8编码。使用其他的编码有可能会导致不可预知的行为发生,例如在Django中你的数据库里有效的数据可能会出现“value too long”的错误。在下面的信息中你可以查看数据库的具体说明来正确的设置你的数据库。 + + + + + + + +## PostgreSQL 说明 + +Django支持PostgreSQL 9.0和更高版本。它需要使用[psycopg2](http://initd.org/psycopg/) 2.4.5或更高版本 (或者 2.5+ 如果你想使用[`django.contrib.postgres`](contrib/postgres/index.html#module-django.contrib.postgres "django.contrib.postgres: PostgreSQL-specific fields and features")). + +如果你使用的是Windows操作系统,请查看一下我们非官方psycopg2的[compiled Windows version](http://stickpeople.com/projects/python/win-psycopg/) + + + +### PostgreSQL 连接设置 + +参考[`HOST`](settings.html#std:setting-HOST)来了解详细信息。 + + + + + +### Optimizing PostgreSQL’s configuration + +Django规定它的数据库连接需要下列参数: + +* `client_encoding`: `'UTF8'`, +* `default_transaction_isolation`: `'read committed'`为默认值,或者是连接选项中设置的值(参见下文), +* `timezone`: `'UTC'` when [`USE_TZ`](settings.html#std:setting-USE_TZ) is `True`, value of [`TIME_ZONE`](settings.html#std:setting-TIME_ZONE) otherwise. + +如果这些参数已经有了正确的值,Django将不会为每一个新的连接设置它们,这略微提高了性能。你可以直接在`postgresql.conf`中配置它们或者更方便的使用[ALTER ROLE](http://www.postgresql.org/docs/current/interactive/sql-alterrole.html)来为每一个database user(...设置?). + +没有这个优化,Django也会工作得很好,但每一个新的连接会做一些额外的查询来设置这些参数。 + + + + + +### 隔离级别 + +像 PostgreSQL 本身,Django默认`READ COMMITTED` [隔离级别isolation level](http://www.postgresql.org/docs/current/static/transaction-iso.html). 如果你需要一个更高的隔离级别,例如 `REPEATABLE READ` 或者 `SERIALIZABLE`, 在 [`数据库databases`](settings.html#std:setting-DATABASES) [`OPTIONS`](settings.html#std:setting-OPTIONS) 设置部分的数据库配置 : + + + + + +``` +import psycopg2.extensions + +DATABASES = { + # ... + 'OPTIONS': { + 'isolation_level': psycopg2.extensions.ISOLATION_LEVEL_SERIALIZABLE, + }, +} + +``` + + + + + + + +说明 + +在较高的隔离级别中,你的application应该做好准备去处理序列化失败中引发的异常(?不确定)。这个选项被设计为高级的用法。 + + + + + + + +### `varchar` 和 `text` 列的索引 + +当你在你的模块字段中指定了`db_index=True`, Django通常会输出一个单一的`CREATE INDEX`语句。但是,如果数据库类型对应的字段是`varchar`或者`text` (e.g., used by `CharField`, `FileField`, and `TextField`), 针对该列,Django会使用适当的[PostgreSQL operator class](http://www.postgresql.org/docs/current/static/indexes-opclass.html) 创建一个additional index.使用`LIKE`操作在SQL中,额外的索引是执行正确的查找所必需的,即用`contains` and `startswith`查找类型。 + + + + + + + +## MySQL 说明 + + + +### 版本支持 + +Django 支持MySQL 5.5 和更高版本。 + +Django的`inspectdb`功能使用了`information_schema` database,它在所有的database schemas包含了详细的数据。 + +Django希望数据库支持Unicode (UTF-8编码),并且代理它去执transactions and referential integrity的任务。当你使用MyISAM储存引擎时,一个你需要注意到的事情是在MYSQL,后两个实际上是不去执行的,请参阅下一节。 + + + + + +### 存储引擎 + +MySQL有数种[存储引擎](http://dev.mysql.com/doc/refman/5.6/en/storage-engines.html). 你可以改变默认的存储引擎在不同的配置中。 + +直到MySQL 5.5.4版本, 默认存数引擎是[MyISAM](http://dev.mysql.com/doc/refman/5.6/en/myisam-storage-engine.html) [[1]](#id6). MyISAM数据的主要缺点是,它不支 transactions或者执行foreign-key约束。从好的方面来看,直到MySQL 5.6.4,它是唯一一个支持全文索引和搜索的引擎。 + +自从MySQL 5.5.5,默认的储存引擎变为了 [InnoDB](http://dev.mysql.com/doc/refman/5.6/en/innodb-storage-engine.html). 这个引擎是全事务和支持foreign key引用的。这可能是目前最好的选择。但是,请注意由于它不能够记取`AUTO_INCREMENT`的值,而不是重新创建像 “max(id)+1”这样,导致InnoDB的autoincrement counter在MySQL中丢失了。这可能导致一个无意重用的[`AutoField`](models/fields.html#django.db.models.AutoField "django.db.models.AutoField") + +如果将现有项目升级到MySQL5.5.5,随后添加一些表,确保您的表可以使用相同的存储引擎(如MyISAM数据与InnoDB的)。特别注意,如果你的表中存在`ForeignKey`,比较他们在不同的存储引擎中,你可能会看到如下的错误当运行`migrate`的时候: + + + + + +``` +_mysql_exceptions.OperationalError: ( + 1005, "Can't create table '\\db_name\\.#sql-4a8_ab' (errno: 150)" +) + +``` + + + + + + +| [[1]](#id4) | Unless this was changed by the packager of your MySQL package. We’ve had reports that the Windows Community Server installer sets up InnoDB as the default storage engine, for example. | + + + + + +### MySQL DB API 驱动 + +Python的Database API 被描述为[**PEP 249**](http://www.python.org/dev/peps/pep-0249). MySQL拥有三种很棒的能够实现API的驱动: + +* [MySQLdb](https://pypi.python.org/pypi/MySQL-python/1.2.4)是一个由Andy Dustman开发,已经发展并支持十多年的一个本地驱动。 +* [mysqlclient](https://pypi.python.org/pypi/mysqlclient)是`MySQLdb`的一个分支,它与python3有着特别好的契合并且可以作为MySQLdb的直接替代。在书写这篇的时候,这是在Django使用MySQL的**推荐的选择。** +* [MySQL Connector/Python](http://dev.mysql.com/downloads/connector/python)是一个来自Oracle的纯python驱动,它不需要MySQL client库或在标准库之外的任何Python模块。 + +所有这些驱动都是线程安全的,并提供连接池。`MySQLdb`是当前唯一一个不支持python3的。 + +除了一个DB API驱动,Django需要一个适配器来通过它的ORM访问数据库驱动。Django对 MySQLdb/mysqlclient提供了适配器直到MySQL Connector/Python包含了[its own](http://dev.mysql.com/doc/refman/5.6/en/connector-python-info.html)。 + + + +#### MySQLdb + +Django需要MySQLdb version 1.2.1p2或更高版本。 + +在写文档的时候,最新版本MySQLdb、(1.2.5)还没有支持python3为了在Python 3使用MySQLdb, 你需要安装`mysqlclient`来替代它 。 + + + +说明 + +关于将date strings转化为 datetime objects,MySQLdb有已经存在的问题。具体来说,值为`0000-00-00`的date strings对于MySQL是有效的,但是会被MySQLdb转换成`None`。 + +这意味着当你在列中使用值可能为`0000-00-00`的[`loaddata`](django-admin.html#django-admin-loaddata)和 [`dumpdata`](django-admin.html#django-admin-dumpdata)时,你需要注意,因为它们会被转换为`None`。 + + + + + + + +#### mysqlclient + +Django需要[mysqlclient](https://pypi.python.org/pypi/mysqlclient) 1.3.3或更高版本。需要注意,Python 3.2 不支持。除了 Python 3.3+ 支持, mysqlclient在其他版本表现的更相似于 MySQLDB。 + + + + + +#### MySQL Connector/Python + +MySQL Connector/Python可从[download page](http://dev.mysql.com/downloads/connector/python/)下载。 Django适配器在1.1.X或更高版本可用。它可能不支持最新的Django的版本。 + + + + + + + +### 时区规定 + +如果您计划使用Django的[_timezone support,使用_](../topics/i18n/timezones.html)_ [mysql_tzinfo_to_sql加载时区表到MySQL数据库。](http://dev.mysql.com/doc/refman/5.6/en/mysql-tzinfo-to-sql.html)_这需要在你的MySQL服务器部署一次就好,而不是在每个数据库上。 + + + + + +### 创建数据库 + +你可以使用命令行工具运行这条SQL语句来[create your database:](http://dev.mysql.com/doc/refman/5.6/en/create-database.html) + + + + + +``` +CREATE DATABASE CHARACTER SET utf8; + +``` + + + + + +这可以确保所有表和列默认使用UTF-8。 + + + +#### 排序设置 + +一列的排序规则设置控制了数据排序的顺序,以及字符串的比较。它可以被设置在数据库的水平,也可以在每一个表和每一列这个是在MySQL文档中[详细记载的](http://dev.mysql.com/doc/refman/5.6/en/charset.html)。在所有情况下,你可以直接操作数据库中的表来设置排序。Django不提供在模型定义上设置它的方式。 + +默认情况下,在一个UTF8编码的数据库中,MySQL将会使用`utf8_general_ci`排序规则。这会导致所有的字符串比较是否相等的结果是以_不区分大小写_ 的方式完成的。就像这样,`"Fred"` and `"freD"` 被认为是相等的在数据库级别上。如果你在字段里有一个特殊的约束,如果试图将 `"aa"` 和`"AA"`插入到同一列会是非法的,因为他们在默认的排序(and, hence, non-unique) 中比较相等。 + +在许多情况下,这种默认情况将不会成为一个问题。然而,如果你真的想要区分大小写比较在一个特定的列或表,你要把这个列或表应用`utf8_bin` 排序。主要的在这种情况下需要注意的是,如果您正在使用MySQLdb 1.2.2 Django的数据库后端会返回bytestrings(而非unicode字符串)对于从数据库中获得的任何字符字段。Django平常的做法_总是_返回unicode字符串,与之相比这是一个显著的变化。这取决于你,开发者们,事实中你将会得到bytestrings的返回如果你配置你的表使用`utf8_bin`排序时。通常Django自身应当顺利地工作使用这样的列(除了`contrib.sessions` `Session` and `contrib.admin` `LogEntry`表描述的),如果你想要在你的工作中是数据保持一致,你的代码必须时刻准备调用`django.utils.encoding.smart_text()` ---Django不会为你做这些(数据库后曾和群体模块层在内部被分离以至于数据库层不知道它需要在这种特殊情况下作出转换)。 + +如果你使用MySQLdb 1.2.1p2,Django的标准[`CharField`](models/fields.html#django.db.models.CharField "django.db.models.CharField")类型将会返回unicode字符串即使存在`utf8_bin`序列。然而, [`TextField`](models/fields.html#django.db.models.TextField "django.db.models.TextField")字段将会像一个`array.array`实例返回(来自Python的标准 `array` 模块)。Django关于这方面不能做出很多,因为,下一次,当从数据库读取数据时,这些信息需要作出必要转换的行为将不可用。这个问题[固定在 MySQLdb 1.2.2](http://sourceforge.net/tracker/index.php?func=detail&aid=1495765&group_id=22307&atid=374932),所以如果你想使用使用`utf8_bin`序列的[`TextField`](models/fields.html#django.db.models.TextField "django.db.models.TextField"),升级到1.2.2版本然后结合bytestrings处理(这不会太难)如上所述是推荐的解决方案。 + +你应该决定在 MySQLdb 1.2.1p2 or 1.2.2对你的表使用 `utf8_bin`排序规则,对于`django.contrib.sessions.models.Session`表(通常称为`django_session`)和`django.contrib.admin.models.LogEntry` 表 (通常称为`django_admin_log`),你仍然应该使用`utf8_general_ci` (默认)排序规则。Those are the two standard tables that use [`TextField`](models/fields.html#django.db.models.TextField "django.db.models.TextField") internally. + +请注意,根据 [MySQL Unicode字符集](http://dev.mysql.com/doc/refman/5.7/en/charset-unicode-sets.html), 比较下`utf8_general_ci`排序规则更加快速,但是与`utf8_unicode_ci`比较明显正确率略低。如果在你的应用中这是可接受的,你应该使用`utf8_general_ci`因为它更快。如果这是不能接受的 (例如,如果您需要德国字典顺序), 使用`utf8_unicode_ci` ,因为它更准确。 + + + +警告 + +模块层组以区分大小写的方式验证唯一的字段。因此当使用一个不区分大小写的序列,一个带有唯一字段值的层组,只有通过不同层组(?)才可通过验证,但是依据调用`save()`,`IntegrityError`错误将会被指出。 + + + + + + + + + +### 连接数据库 + +请参阅 [_settings documentation_](settings.html). + +连接设置将被用在这些命令上: + +1. [`OPTIONS`](settings.html#std:setting-OPTIONS). +2. [`NAME`](settings.html#std:setting-NAME), [`USER`](settings.html#std:setting-USER), [`PASSWORD`](settings.html#std:setting-PASSWORD), [`HOST`](settings.html#std:setting-HOST), [`PORT`](settings.html#std:setting-PORT) +3. MySQL option files. + +换句话说,如果你设置数据库的名称在[`OPTIONS`](settings.html#std:setting-OPTIONS), 这将优先于[`NAME`](settings.html#std:setting-NAME), 这将覆盖任何在[MySQL option file](http://dev.mysql.com/doc/refman/5.6/en/option-files.html)中的东西. + +下面是使用一个MySQL选择文件一个示例配置: + + + + + +``` +# settings.py +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.mysql', + 'OPTIONS': { + 'read_default_file': '/path/to/my.cnf', + }, + } +} + +# my.cnf +[client] +database = NAME +user = USER +password = PASSWORD +default-character-set = utf8 + +``` + + + + + +其他几个MySQLdb连接选项可能是有用的,例如`ssl`, `init_command`, and `sql_mode`. 查阅 [MySQLdb documentation](http://mysql-python.sourceforge.net/)了解更多细节。 + + + + + +### 创建你的表 + +当Django生成数据库得schema(数据库对象的集合)时,它没有指定一个储存引擎,所以你的表将被创建成你服务器配置的默认储存引擎。最简单的解决方案是设置你的数据库服务器的默认存储引擎为你所需求的引擎。 + +如果你使用托管服务,并且不能改变你的服务器的默认存储引擎,你有两种选择。 + +* 在表创建之后, 执行 `ALTER TABLE`语句来转换表为一个新的储存引擎 (例如 InnoDB): + + + + + +``` +ALTER TABLE ENGINE=INNODB; + +``` + + + + + + 如果你有很多表,这会很繁琐。 + +* 另一个选择是对于MySQLdb使用`init_command`选项在你创建表之前。 + + + + + +``` +'OPTIONS': { + 'init_command': 'SET storage_engine=INNODB', + } + +``` + + + + + + 这设置默认的储存引擎在连接数据库之上。创建了表之后,您应该删除这个选项,因为它增加了一个只需要在每个表创建数据库连接时的查询。 + + + + + +### 表名 + +甚至在最新版本的MySQL中,这里存在[已知的问题](http://bugs.mysql.com/bug.php?id=48875)导致当某些SQL语句在一定条件下被执行下,表的名称可以被改变。建议您使用小写的表名,如果可能的话,避免任何可能从这个行为产生的问题。当Django从模型中自动生成表名时,它使用小写的表名,这主要考虑到如果你不顾问题让表名通过[` db_table `](models/options.html#django.db.models.Options.db_table "django.db.models.Options.db_table")参数。 + + + + + +### 保存点 + +Django ORM和MySQL (当使用InnoDB[_存储引擎_](#mysql-storage-engines)) 支持数据库[_保存点_](../topics/db/transactions.html#topics-db-transactions-savepoints)。 + +如果你使用MyISAM存储引擎请注意,你将会收到数据库生成的错误如果你尝试去使用[_savepoint-related methods of the transactions API_](../topics/db/transactions.html#topics-db-transactions-savepoints)。这个原因是检测一个MySQL数据库或表的存储引擎是一项expensive operation,所以它认定为在这些操作结果中无操作的情况下,它不值得去动态转换这些方法。 + + + + + +### 特定字段说明 + + + +#### Character fields + +任何字段都被存储在 `VARCHAR`列类型的`max_length` ,它被限制在255个字符之内,如果您对这些字段使用`unique=True`的话。 这个影响到[`CharField`](models/fields.html#django.db.models.CharField "django.db.models.CharField"), [`SlugField`](models/fields.html#django.db.models.SlugField "django.db.models.SlugField") 和[`CommaSeparatedIntegerField`](models/fields.html#django.db.models.CommaSeparatedIntegerField "django.db.models.CommaSeparatedIntegerField")。 + + + + + +#### Time and DateTime fields的小数秒的支持 + +MySQL 5.6.4和更高版本可以存储小数秒,只需要列定义包含一个小数指示 (例如`DATETIME(6)`).。早期版本不支持它们。此外,MySQLdb的1.2.5以上版本存在一个[bug](https://github.com/farcepest/MySQLdb1/issues/24),它会阻止MySQL使用小数秒。 + +如果数据库服务器支持的话,Django不会升级现有的列去包含小数秒。如果你想确保它们在一个当前的数据库中,这决定由你自己在目标数据库中手动去升级列,通过执行这样的一个命令: + + + + + +``` +ALTER TABLE `your_table` MODIFY `your_datetime_column` DATETIME(6) + +``` + + + + + +或者在[_数据迁移_](../topics/migrations.html#data-migrations)中使用[`RunSQL`](migration-operations.html#django.db.migrations.operations.RunSQL "django.db.migrations.operations.RunSQL") 选项。 + +Changed in Django 1.8: + +以前,Django在使用MySQL后端时删除(truncated)了来自`datetime` 和`time`值的小数秒。现在它让数据库决定他是否应该去删除(drop)部分的值。默认情况下, 新的`DateTimeField`或者`TimeField`列现在创建小数秒支持在MySQL5.6.4或更高版本和每个mysqlclient或MySQLdb 1.2.5或更高版本。 + + + + + + + +#### `TIMESTAMP`列 + +如果你使用了包含`TIMESTAMP` 列的遗留数据库,你必须设置[`USE_TZ = False`](settings.html#std:setting-USE_TZ)来避免数据丢失。[`inspectdb`](django-admin.html#django-admin-inspectdb) 映射这些列在[`DateTimeField`](models/fields.html#django.db.models.DateTimeField "django.db.models.DateTimeField")并且如果你启用时区支持,MySQL和Django将尝试从UTC的值转换为本地时间。 + + + + + + + +### Row locking with `QuerySet.select_for_update()` + +MySQL不支持`NOWAIT`选项来 `SELECT ...` FOR UPDATE statement. 如果`select_for_update()`和`nowait=True`一起被使用,会导致一个`DatabaseError(数据库错误)` 产生。 + + + + + +### 自动类型转换会导致意想不到的结果 + +当你执行一个字符串类型的查询时,存在整型值时,MySQL会强制转换表里的所有值变为整型在你进行比较之前。如果你的表里包含`'abc'`, `'def'`和你查询`WHERE mycolumn=0`, 两行相匹配。同样, `WHERE mycolumn=1` 将会匹配`'abc1'`.。因此,Django中的字符串类型将会总是转换这个值为一个字符串当你查询中使用它之前。 + +如果你执行直接继承自[`Field`](models/fields.html#django.db.models.Field "django.db.models.Field")的自定义模型字段,会覆盖[`get_prep_value()`](models/fields.html#django.db.models.Field.get_prep_value "django.db.models.Field.get_prep_value"),或者使用[`extra()`](models/querysets.html#django.db.models.query.QuerySet.extra "django.db.models.query.QuerySet.extra")或者[`raw()`](../topics/db/sql.html#django.db.models.Manager.raw "django.db.models.Manager.raw"), 你应该确保执行了适当的类型。 + + + + + + + +## SQLite说明 + +[SQLite](http://www.sqlite.org/) 提供了一个极好的开发替代品对于主要为只读或者安装一个小的程序的应用程序。你应该知道的是,如同所有的数据库服务一样,SQLLite有一些不同于其他数据库的差异。 + + + +### 子串匹配和大小写敏感性 + +对于所有的SQLite版本,存在一些细微的违反语感的行为当你试图匹配某些类型的字符串。当你在Querysets中使用 [`iexact`](models/querysets.html#std:fieldlookup-iexact)或者 [`contains`](models/querysets.html#std:fieldlookup-contains) 过滤器,这些行为会被触发。 这种行为分为两个情况: + +1\. 对于子串匹配,所有的匹配都会不区分大小写。这是一个过滤器例如 `filter(name__contains="aa")`将会匹配一个名称为`"Aabb"`。 + +2\. 对于字符串包含ASCII范围之外的字符,所有准确的字符串匹配都会被执行区分大小写的操作,甚至当不区分大小写的选项在查询中通过。所以在这些情况下,[`iexact`](models/querysets.html#std:fieldlookup-iexact)过滤器将会表现与[`exact`](models/querysets.html#std:fieldlookup-exact)过滤器一模一样。 + +一些可能的解决办法在 [documented at sqlite.org](http://www.sqlite.org/faq.html#q18),但是他们不能利用默认的SQLite后端在Django中,强行将他们合并将会非常的困难。因此,Django显示默认的SQLite行为,当你使用不区分大小写或者子串过滤操作时你应该意识到这点。 + + + + + +### 旧版SQLite和`CASE`表达式 + +SQLite 3.6.23.1和更老版本包含了一个bug 当[handling query parameters](https://code.djangoproject.com/ticket/24148) 在一个 `CASE`表达式中包含了一个`ELSE`和算法。 + +2010年3月SQLite 3.6.23.1发布,目前大多数不同平台的二进制发行版包括一个新版本的SQLite,值得注意的是python 2.7 安装在windows平台上。 + +在撰写本文时,最新版本Windows - Python 2.7.9包括SQLite 3.6.21。你可以安装`pysqlite2`或者替换`sqlite3.dll` (默认情况下安装在`C:Python27DLLs`)通过来自[http://www.sqlite.org/](http://www.sqlite.org/) 的新版本来补救这个问题。 + + + + + +### 使用新版本的 SQLite DB-API 2.0驱动 + +Django将会使用一个`pysqlite2`模块优先于附带的`sqlite3`如果你发现一个是可用的在Python的标准库中。 + +这能够升级 DB-API 2.0接口或SQLite 3本身比那些包含在您的特定版本Python二进制发行版更新,如果你需要的话。 + + + + + +### “Database is locked” errors + +SQLite是一个轻量级的数据库,因此不能支持一个高水平的并发性。`OperationalError: database is locked`错误表明你的应用正在经受更高的并发相较于`sqlite`能在默认配置中处理的那样。这个错误意味着一个线程或进程在数据库连接上存在一个交互型锁,另一个超时线程等待独自锁被释放。 + +Python的SQLite封装有一个默认的超时时间值决定了在超时之前第二个线程被允许等待多长时间在lock上并且会反馈`OperationalError: database is locked` error。 + +如果你出现了这个错误,你可以这样解决: + +* 切换到另一个数据库端。在某种程度上SQLite变得太“lite”对于现实世界的应用程序,并且这些并发错误表明你已经达到了这一点。 + +* 重写代码,以减少并发性和确保数据库事务是短期的。 + +* 增加默认超时时间值通过设置`timeout`数据库选项: + + + + + +``` +'OPTIONS': { + # ... + 'timeout': 20, + # ... + } + +``` + + + + + + 这只会使SQLite等一段时间才会抛出“database is locked”错误;它不会真的做一些事情去解决他们。 + + + + + +### `QuerySet.select_for_update()` 不被支持 + +SQLite不支持 `SELECT ...` FOR UPDATE syntax. 它将没有影响。 + + + + + +### “pyformat” parameter style in raw queries not supported + +对于大多数的数据库后端, 原始查询(`Manager.raw()`或者`cursor.execute()`) 可以使用 “pyformat”参数风格,在查询中placeholders作为 `'%(name)s'`并且被传递的参数是一个字典而不是列表。SQLite 不支持这个。 + + + + + +### 在 `connection.queries`参数不被引用 + +`sqlite3` 没有提供一种方式来检索SQL引用后和替换的参数。相反,SQL在 `connection.queries`被一个简单的字符串插值重建。它可能是不正确的。确保你在必要时添加引用钱拷贝一个引用在SQLite shell中。 + + + + + + + +## Oracle说明 + +Django 支持 [Oracle Database Server](http://www.oracle.com/) 11.1和更高版本 。4.3.1或更高版本的[cx_Oracle](http://cx-oracle.sourceforge.net/) Python驱动程序是必需的,尽管我们建议5.1.3或更高的支持Python 3的版本。 + +注意由于一个Unicode-corruption bug 在 `cx_Oracle` 5.0, 这个版本的驱动程序 **不能** 支持Django使用; `cx_Oracle` 5.0.1解决了这个问题,所以如果你希望使用一个更近时间发布的`cx_Oracle`, 使用5.0.1版本。 + +`cx_Oracle` 5.0.1或更高版本支持被随意编辑通过`WITH_UNICODE` 环境变量。这个建议不是必须的。 + +为了使`python manage.py migrate` 命令工作, 您的Oracle数据库用户必须拥有特权运行以下命令: + +* CREATE TABLE +* CREATE SEQUENCE +* CREATE PROCEDURE +* CREATE TRIGGER + +运行一个项目的测试套件, 用户通常需要这些_additional_ 特权: + +* CREATE USER +* DROP USER +* CREATE TABLESPACE +* DROP TABLESPACE +* CREATE SESSION WITH ADMIN OPTION +* CREATE TABLE WITH ADMIN OPTION +* CREATE SEQUENCE WITH ADMIN OPTION +* CREATE PROCEDURE WITH ADMIN OPTION +* CREATE TRIGGER WITH ADMIN OPTION + +注意, 虽然 RESOURCE role 具有有需求 CREATE TABLE, CREATE SEQUENCE, CREATE PROCEDURE 和 CREATE TRIGGER 特权, 并且一个用户被允许RESOURCE当ADMIN OPTION能够授权 RESOURCE这样一个用户不能授予个人特权(例如 CREATE TABLE),因此RESOURCE伴随ADMIN OPTION通畅不满足运行测试。(这段没翻译好。。大家理解一下) + +一些测试套件也创建视图;运行这些,用户还需要 CREATE VIEW WITH ADMIN OPTION 特权。特别的是,这是Django自己的测试套件所需要的。 + +Changed in Django 1.8: + +Django 1.8之前,测试用户被允许CONNECT and RESOURCE roles,所以所需的额外的特权运行测试套件是不同的。 + + + +所有这些特权都包含在DBA role,这适合使用在一个私人开发人员的数据库。 + +Oracle数据库后端使用 `SYS.`DBMS_LOB包,所以用户需要执行权限。通常是在默认情况下所有用户都可以访问的,但如果不是,你需要授予权限如下: + + + + + +``` +GRANT EXECUTE ON SYS.DBMS_LOB TO user; + +``` + + + + + + + +### 连接到数据库 + +为了连接使用你的Oracle数据库的服务名, 你的 `settings.py` 文件应该像这样: + + + + + +``` +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.oracle', + 'NAME': 'xe', + 'USER': 'a_user', + 'PASSWORD': 'a_password', + 'HOST': '', + 'PORT': '', + } +} + +``` + + + + + +在这种情况下, 你应该让 [`HOST`](settings.html#std:setting-HOST) and [`PORT`](settings.html#std:setting-PORT) 置空。 然而,如果你不是使用一个 `tnsnames.ora`文件或者一个类似的命名方法 并且想要连接使用 SID (“xe” 在本例),填写[`HOST`](settings.html#std:setting-HOST) 和[`PORT`](settings.html#std:setting-PORT) 像如下: + + + + + +``` +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.oracle', + 'NAME': 'xe', + 'USER': 'a_user', + 'PASSWORD': 'a_password', + 'HOST': 'dbprod01ned.mycompany.com', + 'PORT': '1540', + } +} + +``` + + + + + +你应该同时填写 [`HOST`](settings.html#std:setting-HOST) and [`PORT`](settings.html#std:setting-PORT), 或者全部置空。Django将会取决你的选择使用一个不同的连接描述符。 + + + + + +### 线程的选项 + +如果你计划运行Django在多线程环境中(例如Apache使用默认MPM模块在任何现代的操作系统), 在你的Oracle数据库配置中你 **必须** 设置 `threaded`选项为True: + + + + + +``` +'OPTIONS': { + 'threaded': True, +}, + +``` + + + + + +未能这样做可能会导致崩溃和其他奇怪的行为。 + + + + + +### INSERT ... RETURNING INTO + +默认情况下,当插入新的行时, Oracle 后端使用 `RETURNING INTO` 去检索一个有效的`AutoField`。这个行为可能会导致一个`DatabaseError` 在某些不寻常设置中, 例如当插入到一个远程表,或者在一个视图中随着一个`INSTEAD OF` 触发。`RETURNING INTO` 子句可以禁用通过`use_returning_into`选项在数据库设置中为Flase: + + + + + +``` +'OPTIONS': { + 'use_returning_into': False, +}, + +``` + + + + + +在这种情况, Oracle后端将会使用一个单独的 `SELECT` 查询来检索AutoField值。 + + + + + +### 命名问题 + +Oracle强加了一个30个字符的名称限制。 为了适应, 后端缩短了数据库表示符为最适长度,取代带着一个可重复的MD5散列值的缩短的名称的最后四个字符。此外,后端数据库标识符变成大写。 + +为了防止这些转换(这通常是只需要在处理遗留数据库或访问表属于其他用户),使用一个引用名称像`db_table`值这样: + + + + + +``` +class LegacyModel(models.Model): + class Meta: + db_table = '"name_left_in_lowercase"' + +class ForeignModel(models.Model): + class Meta: + db_table = '"OTHER_USER"."NAME_ONLY_SEEMS_OVER_30"' + +``` + + + + + +引用名称也可以被使用在Django的其他受支持的数据库后端;除了r Oracle, 然而,引用没有影响。 + +当运行 `migrate`,一个 `ORA-06552`错误可能出现,如果某些Oracle的keywords被使用作为模型字段名称或一个`db_column`选项的值时。Django引用在查询使用的所有的标示符来防止大多数这样的问你,但是这个错误仍然可以发生当Oracle数据类型被用为一个列名。特别是,避免使用这样的名称`date`, `timestamp`, `number` or `float` 作为一个字段名。 + + + + + +### NULL和空字符串 + +Django通常更喜欢使用空字符串(“”)而不是NULL,但是Oracle将两个相同对待。为了解决这个问题,Oracle后端忽略了字段中一个explict的`null`选项,让空字符串作为一个可能的值并生成DDL就像`null=True`。当从数据库获取时,它假定一个`NULL`值在这些字段之一意味一个空字符串,并且数据默认转换去反映这种假设。 + + + + + +### `TextField`限制 + +The Oracle后端存储 `TextFields`像`NCLOB` 列.。通常使用LOB列时,Oracle 强加了一些限制: + +* LOB 列不被像主键一样使用。 +* LOB列不得用于索引。 +* LOB列不被使用在一个`SELECT DISTINCT` 列表。 这意味着当试图去使用 `QuerySet.distinct`方法在一个包含 `TextField`列的模型中将会导致一个错误当违反Oracle时。一个解决方案是, 使用 `QuerySet.defer`方法连同`distinct()`来防止将要包含在 `SELECT DISTINCT`列表中的 `TextField`列。 + + + + + + + +## 使用第三方数据库后端 + +除了官方支持的数据库,这些第三方提供的后端也允许您在Django中使用其他数据库: + +* [SAP SQL Anywhere](https://github.com/sqlanywhere/sqlany-django) +* [IBM DB2](http://code.google.com/p/ibm-db/) +* [Microsoft SQL Server](http://django-mssql.readthedocs.org/en/latest/) +* [Firebird](https://github.com/maxirobaina/django-firebird) +* [ODBC](https://github.com/lionheart/django-pyodbc/) +* [ADSDB](http://code.google.com/p/adsdb-django/) + +Django版本和ORM特性被支持通过这些非官方的后端相当大的变化。查询这些非官方的后端有关的特定功能,以及任何支持的查询,应该针对每个第三方项目提供的支持项目。 + + + diff --git a/3_2_2_Request response objects.md b/3_2_2_Request response objects.md new file mode 100644 index 0000000000000000000000000000000000000000..8744e9553e6e566a56c028ef4201fc32aea96065 --- /dev/null +++ b/3_2_2_Request response objects.md @@ -0,0 +1,1782 @@ + + +# Request 对象和Response 对象 + + + +## 概述 + +Django 使用Request 对象和Response 对象在系统间传递状态。 + +当请求一个页面时,Django会建立一个包含请求元数据的 [`HttpRequest`](#django.http.HttpRequest "django.http.HttpRequest") 对象。 当Django 加载对应的视图时,[`HttpRequest`](#django.http.HttpRequest "django.http.HttpRequest") 对象将作为视图函数的第一个参数。每个视图会返回一个[`HttpResponse`](#django.http.HttpResponse "django.http.HttpResponse") 对象。 + +本文档对[`HttpRequest`](#django.http.HttpRequest "django.http.HttpRequest") 和[`HttpResponse`](#django.http.HttpResponse "django.http.HttpResponse") 对象的API 进行说明,这些API 定义在[`django.http`](#module-django.http "django.http: Classes dealing with HTTP requests and responses.") 模块中。 + + + + + +## HttpRequest 对象 + + + +_class_ `HttpRequest` + + + + + +### 属性 + +下面除非特别说明,所有属性都认为是只读的。`会话(session)` 属性是个例外,需要注意。 + + + +`HttpRequest.scheme` + + + +New in Django 1.7. + +一个字符串,表示请求的方案(通常是`http` 或`https`)。 + + + + + + + +`HttpRequest.body` + + + +一个字节字符串,表示原始HTTP 请求的正文。它对于处理非HTML 形式的数据非常有用:二进制图像、XML等。 如果要处理常规的表单数据,应该使用`HttpRequest.POST`。 + +你也可以使用”类文件“形式的接口从HttpRequest 中读取数据。参见[`HttpRequest.read()`](#django.http.HttpRequest.read "django.http.HttpRequest.read")。 + + + + + + + +`HttpRequest.path` + + + +一个字符串,表示请求的页面的完整路径,不包含域名。 + +例如:`"/music/bands/the_beatles/"` + + + + + + + +`HttpRequest.path_info` + + + +在某些Web 服务器配置下,主机名后的URL 部分被分成脚本前缀部分和路径信息部分。`path_info` 属性将始终包含路径信息部分,不论使用的Web 服务器是什么。使用它代替[`path`](#django.http.HttpRequest.path "django.http.HttpRequest.path") 可以让代码在测试和开发环境中更容易地切换。 + +例如,如果应用的`WSGIScriptAlias` 设置为`"/minfo"`,那么当`path` 是`"/minfo/music/bands/the_beatles/"` 时`path_info` 将是`"/music/bands/the_beatles/"`。 + + + + + + + +`HttpRequest.method` + + + +一个字符串,表示请求使用的HTTP 方法。必须使用大写。例如: + + + + + +``` +if request.method == 'GET': + do_something() +elif request.method == 'POST': + do_something_else() + +``` + + + + + + + + + + + +`HttpRequest.encoding` + + + +一个字符串,表示提交的数据的编码方式(如果为`None` 则表示使用[`DEFAULT_CHARSET`](settings.html#std:setting-DEFAULT_CHARSET) 设置)。这个属性是可写的,你可以修改它来修改访问表单数据使用的编码。接下来对属性的任何访问(例如从`GET` 或 `POST` 中读取数据)将使用新的`encoding` 值。如果你知道表单数据的编码不在[`DEFAULT_CHARSET`](settings.html#std:setting-DEFAULT_CHARSET) 中,则使用它。 + + + + + + + +`HttpRequest.GET` + + + +一个类似于字典的对象,包含HTTP GET 的所有参数。详情请参考下面的[`QueryDict`](#django.http.QueryDict "django.http.QueryDict") 文档。 + + + + + + + +`HttpRequest.POST` + + + +一个包含所有给定的HTTP POST参数的类字典对象,提供了包含表单数据的请求。详情请参考下面的[`QueryDict`](#django.http.QueryDict "django.http.QueryDict") 文档。如果需要访问请求中的原始或非表单数据,可以使用[`HttpRequest.body`](#django.http.HttpRequest.body "django.http.HttpRequest.body") 属性。 + +POST 请求可以带有空的`POST` 字典 —— 如果通过HTTP POST 方法请求一个表单但是没有包含表单数据的话。因此,不应该使用`if request.POST` 来检查使用的是否是POST 方法;应该使用`if request.method == "POST"`(参见上文)。 + +注意:`POST` _不_包含上传的文件信息。参见`FILES`。 + + + + + + + +`HttpRequest.REQUEST` + + + + + +Deprecated since version 1.7: 使用更显式的`GET` 和`POST` 代替。 + + + +一个类似于字典的对象,它首先搜索`POST`,然后搜索`GET`,主要是为了方便。灵感来自于PHP 的`$_REQUEST`。 + +例如,如果`GET = {"name": "john"}` 而`POST = {"age": '34'}`,`REQUEST["name"]` 将等于`"john"`以及`REQUEST["age"]` 将等于`"34"`。 + +强烈建议使用`GET` 和`POST` 而不要用`REQUEST`,因为它们更加明确。 + + + + + + + +`HttpRequest.COOKIES` + + + +一个标准的Python 字典,包含所有的cookie。键和值都为字符串。 + + + + + + + +`HttpRequest.FILES` + + + +一个类似于字典的对象,包含所有的上传文件。`FILES` 中的每个键为`` 中的`name`。 + +更多信息参见[_管理文件_](../topics/files.html)。 + +注意,`FILES` 只有在请求的方法为POST 且提交的`
` 带有`enctype="multipart/form-data"` 的情况下才会包含数据。否则,`FILES` 将为一个空的类似于字典的对象。 + + + + + + + +`HttpRequest.META` + + + +一个标准的Python 字典,包含所有的HTTP 头部。具体的头部信息取决于客户端和服务器,下面是一些示例: + +* `CONTENT_LENGTH` —— 请求的正文的长度(是一个字符串)。 +* `CONTENT_TYPE` —— 请求的正文的MIME 类型。 +* `HTTP_ACCEPT` —— 响应可接收的Content-Type。 +* `HTTP_ACCEPT_ENCODING` —— 响应可接收的编码。 +* `HTTP_ACCEPT_LANGUAGE`?—— 响应可接收的语言。 +* `HTTP_HOST` —— 客服端发送的HTTP Host 头部。 +* `HTTP_REFERER` —— Referring 页面。 +* `HTTP_USER_AGENT` —— 客户端的user-agent 字符串。 +* `QUERY_STRING` —— 单个字符串形式的查询字符串(未解析过的形式)。 +* `REMOTE_ADDR` —— 客户端的IP 地址。 +* `REMOTE_HOST` —— 客户端的主机名。 +* `REMOTE_USER` —— 服务器认证后的用户。 +* `REQUEST_METHOD` —— 一个字符串,例如`"GET"` 或`"POST"`。 +* `SERVER_NAME` —— 服务器的主机名。 +* `SERVER_PORT` —— 服务器的端口(是一个字符串)。 + +从上面可以看到,除`CONTENT_LENGTH` 和`CONTENT_TYPE` 之外,请求中的任何HTTP 头部转换为`META` 的键时,都会将所有字母大写并将连接符替换为下划线最后加上`HTTP_` 前缀。所以,一个叫做`X-Bender` 的头部将转换成`META` 中的`HTTP_X_BENDER` 键。 + + + + + + + +`HttpRequest.user` + + + +一个[`AUTH_USER_MODEL`](settings.html#std:setting-AUTH_USER_MODEL) 类型的对象,表示当前登录的用户。如果用户当前没有登录,`user` 将设置为[`django.contrib.auth.models.AnonymousUser`](contrib/auth.html#django.contrib.auth.models.AnonymousUser "django.contrib.auth.models.AnonymousUser") 的一个实例。你可以通过[`is_authenticated()`](contrib/auth.html#django.contrib.auth.models.User.is_authenticated "django.contrib.auth.models.User.is_authenticated") 区分它们,像这样: + + + + + +``` +if request.user.is_authenticated(): + # Do something for logged-in users. +else: + # Do something for anonymous users. + +``` + + + + + +`user` 只有当Django 启用[`AuthenticationMiddleware`](middleware.html#django.contrib.auth.middleware.AuthenticationMiddleware "django.contrib.auth.middleware.AuthenticationMiddleware") 中间件时才可用。更多信息,参见[_Django 中的用户认证_](../topics/auth/index.html)。 + + + + + + + +`HttpRequest.session` + + + +一个既可读又可写的类似于字典的对象,表示当前的会话。只有当Django 启用会话的支持时才可用。完整的细节参见[_会话的文档_](../topics/http/sessions.html)。 + + + + + + + +`HttpRequest.urlconf` + + + +不是由Django 自身定义的,但是如果其它代码(例如,自定义的中间件类)设置了它,Django 就会读取它。如果存在,它将用来作为当前的请求的Root URLconf,并覆盖[`ROOT_URLCONF`](settings.html#std:setting-ROOT_URLCONF) 设置。细节参见[_Django 如何处理请求_](../topics/http/urls.html#how-django-processes-a-request)。 + + + + + + + +`HttpRequest.resolver_match` + + + +一个[`ResolverMatch`](urlresolvers.html#django.core.urlresolvers.ResolverMatch "django.core.urlresolvers.ResolverMatch") 的实例,表示解析后的URL。这个属性只有在URL 解析方法之后才设置,这意味着它在所有的视图中可以访问,但是在在URL 解析发生之前执行的中间件方法中不可以访问(比如`process_request`,但你可以使用`process_view` 代替)。 + + + + + + + + + +### 方法 + + + +`HttpRequest.get_host`() + + + +根据从`HTTP_X_FORWARDED_HOST`(如果打开[`USE_X_FORWARDED_HOST`](settings.html#std:setting-USE_X_FORWARDED_HOST))和`HTTP_HOST` 头部信息返回请求的原始主机。如果这两个头部没有提供相应的值,则使用`SERVER_NAME` 和`SERVER_PORT`,在[**PEP 3333**](http://www.python.org/dev/peps/pep-3333) 中有详细描述。 + +例如:`"127.0.0.1:8000"` + + + +注 + +当主机位于多个代理的后面,[`get_host()`](#django.http.HttpRequest.get_host "django.http.HttpRequest.get_host") 方法将会失败。有一个解决办法是使用中间件重写代理的头部,例如下面的例子: + + + + + +``` +class MultipleProxyMiddleware(object): + FORWARDED_FOR_FIELDS = [ + 'HTTP_X_FORWARDED_FOR', + 'HTTP_X_FORWARDED_HOST', + 'HTTP_X_FORWARDED_SERVER', + ] + + def process_request(self, request): + """ + Rewrites the proxy headers so that only the most + recent proxy is used. + """ + for field in self.FORWARDED_FOR_FIELDS: + if field in request.META: + if ',' in request.META[field]: + parts = request.META[field].split(',') + request.META[field] = parts[-1].strip() + +``` + + + + + +这个中间件应该放置在所有依赖于[`get_host()`](#django.http.HttpRequest.get_host "django.http.HttpRequest.get_host") 的中间件之前 —— 例如,[`CommonMiddleware`](middleware.html#django.middleware.common.CommonMiddleware "django.middleware.common.CommonMiddleware") 和[`CsrfViewMiddleware`](middleware.html#django.middleware.csrf.CsrfViewMiddleware "django.middleware.csrf.CsrfViewMiddleware")。 + + + + + + + + + +`HttpRequest.get_full_path`() + + + +返回`path`,如果可以将加上查询字符串。 + +例如:`"/music/bands/the_beatles/?print=true"` + + + + + + + +`HttpRequest.build_absolute_uri`(_location_) + + + +返回`location` 的绝对URI。如果location 没有提供,则设置为`request.get_full_path()`。 + +如果URI 已经是一个绝对的URI,将不会修改。否则,使用请求中的服务器相关的变量构建绝对URI。 + +例如:`"http://example.com/music/bands/the_beatles/?print=true"` + + + + + + + +`HttpRequest.get_signed_cookie`(_key_, _default=RAISE_ERROR_, _salt=''_, _max_age=None_) + + + +返回签名过的Cookie 对应的值,如果签名不再合法则返回`django.core.signing.BadSignature`。如果提供`default` 参数,将不会引发异常并返回default 的值。 + +可选参数`salt` 可以用来对安全密钥强力攻击提供额外的保护。`max_age` 参数用于检查Cookie 对应的时间戳以确保Cookie 的时间不会超过`max_age` 秒。 + +示例: + + + + + +``` +>>> request.get_signed_cookie('name') +'Tony' +>>> request.get_signed_cookie('name', salt='name-salt') +'Tony' # assuming cookie was set using the same salt +>>> request.get_signed_cookie('non-existing-cookie') +... +KeyError: 'non-existing-cookie' +>>> request.get_signed_cookie('non-existing-cookie', False) +False +>>> request.get_signed_cookie('cookie-that-was-tampered-with') +... +BadSignature: ... +>>> request.get_signed_cookie('name', max_age=60) +... +SignatureExpired: Signature age 1677.3839159 > 60 seconds +>>> request.get_signed_cookie('name', False, max_age=60) +False + +``` + + + + + +更多信息参见[_密钥签名_](../topics/signing.html)。 + + + + + + + +`HttpRequest.is_secure`() + + + +如果请求时是安全的,则返回`True`;即请求是通过HTTPS 发起的。 + + + + + + + +`HttpRequest.is_ajax`() + + + +如果请求是通过`XMLHttpRequest` 发起的,则返回`True`,方法是检查`HTTP_X_REQUESTED_WITH` 头部是否是字符串`'XMLHttpRequest'`。大部分现代的JavaScript 库都会发送这个头部。如果你编写自己的XMLHttpRequest 调用(在浏览器端),你必须手工设置这个值来让`is_ajax()` 可以工作。 + +如果一个响应需要根据请求是否是通过AJAX 发起的,并且你正在使用某种形式的缓存例如Django 的[`cache middleware`](middleware.html#module-django.middleware.cache "django.middleware.cache: Middleware for the site-wide cache."), 你应该使用[`vary_on_headers('HTTP_X_REQUESTED_WITH')`](../topics/http/decorators.html#django.views.decorators.vary.vary_on_headers "django.views.decorators.vary.vary_on_headers") 装饰你的视图以让响应能够正确地缓存。 + + + + + + + +`HttpRequest.read`(_size=None_) + + + + + +`HttpRequest.readline`() + + + + + +`HttpRequest.readlines`() + + + + + +`HttpRequest.xreadlines`() + + + + + +`HttpRequest.__iter__`() + + + +这几个方法实现类文件的接口用于读取HttpRequest· 实例。这使得可以用流的方式读取进来的请求。常见的使用常见是使用迭代的解析器处理一个大型的XML而不用在内存中构建一个完整的XML 树。 + +根据这个标准的接口,一个HttpRequest 实例可以直接传递给XML 解析器,例如ElementTree: + + + + + +``` +import xml.etree.ElementTree as ET +for element in ET.iterparse(request): + process(element) + +``` + + + + + + + + + + + + + + + +## QueryDict 对象 + + + +_class_ `QueryDict` + + + +在[`HttpRequest`](#django.http.HttpRequest "django.http.HttpRequest") 对象中,`GET` 和`POST` 属性是`django.http.QueryDict` 的实例,它是一个自定义的类似字典的类,用来处理同一个键带有多个值。这个类的需求来自某些HTML 表单元素传递多个值给同一个键,` + + +``` + + + + + +正如你所看到的,这里仅显示一个空表单。显示的表单的数目通过`extra` 参数控制。默认情况下,[`formset_factory()`](../../ref/forms/formsets.html#django.forms.formsets.formset_factory "django.forms.formsets.formset_factory") 定义一个表单;下面的示例将显示两个空表单: + + + + + +``` +>>> ArticleFormSet = formset_factory(ArticleForm, extra=2) + +``` + + + + + +对`formset` 的迭代将以它们创建时的顺序渲染表单。通过提供一个`__iter__()` 方法,可以改变这个顺序。 + +表单集还可以索引,它将返回对应的表单。如果覆盖`__iter__`,你还需要覆盖`__getitem__` 以获得一致的行为。 + + + +## 表单集的初始数据 + +初始数据体现着表单集的主要功能。如上所述,你可以定义表单的数目。它表示除了从初始数据生成的表单之外,还要生成多少个额外的表单。让我们看个例子: + + + + + +``` +>>> import datetime +>>> from django.forms.formsets 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()) + + + + + + + +``` + + + + + +上面现在一共有三个表单。一个是初始数据生成的,还有两个是额外的表单。还要注意的是,我们传递的初始数据是一个由字典组成的列表。 + + + +另见 + +[_利用模型表单集从模型中创建表单集_](modelforms.html#model-formsets)。 + + + + + + + +## Limiting the maximum number of forms + +?[`formset_factory()`](../../ref/forms/formsets.html#django.forms.formsets.formset_factory "django.forms.formsets.formset_factory")的?`max_num` 参数 ,给予你限制表单集展示表单个数的能力 + + + + + +``` +>>> from django.forms.formsets 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()) + + + +``` + + + + + +假如 `max_num`的值 比已经在初始化数据中存在的条目数目多的话, `extra`对应个数的额外空表单将会被添加到表单集, 只要表单总数不超过 `max_num`. For example, if `extra=2` and `max_num=2` and the formset is initialized with one `initial` item, a form for the initial item and one blank form will be displayed. + +假如初始化数据的条目超过 `max_num`的值, 所有初始化数据表单都会被展现并且忽视 `max_num`值的限定 ,而且不会有额外的表单被呈现。比如, 如果`extra=3` ,`max_num=1` 并且表单集由两个初始化条蜜,那么两个带有初始化数据的表单将被呈现。 + +`max_num` 的值为 `None` (默认值) 等同于限制了一个比较高的展现表单数目(1000个). 实际上就是等同于没限制. + +默认的, `max_num` 只影响了表单的数目展示,但不影响验证. 假如 `validate_max=True` 传给了 [`formset_factory()`](../../ref/forms/formsets.html#django.forms.formsets.formset_factory "django.forms.formsets.formset_factory"), 然后 `max_num`才将会影响验证. See [_Validating the number of forms in a formset_](#validate-max). + + + + + +## Formset validation + +表单集的验证几乎和 一般的`Form`一样. 表单集里面有一个 `is_valid` 的方法来提供快捷的验证所有表单的功能。 + + + + + +``` +>>> from django.forms.formsets 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 + +``` + + + + + +We passed in no data to the formset which is resulting in a valid form. The formset is smart enough to ignore extra forms that were not changed. If we provide an invalid article: + + + + + +``` +>>> 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` 是一个列表, 他包含的错误信息正好与表单集内的表单一一对应错误检查会在两个表单中分别执行,被预见的错误出现错误列表的第二项 + + + +`BaseFormSet.total_error_count`()[[source]](../../_modules/django/forms/formsets.html#BaseFormSet.total_error_count) + + + +想知道表单集内有多少个错误可以使用`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 + +``` + + + + + + + +### Understanding the ManagementForm + +你也许已经注意到了那些附加的数据 (`form-TOTAL_FORMS`, `form-INITIAL_FORMS` and `form-MAX_NUM_FORMS`) 他们是必要的,且必须位于表单集数据的最上方? 这些必须传递给`ManagementForm`. ManagementFormThis 用于管理表单集中的表单. 如果你不提供这些数据,将会触发异常 + + + + + +``` +>>> data = { +... 'form-0-title': 'Test', +... 'form-0-pub_date': '', +... } +>>> formset = ArticleFormSet(data) +Traceback (most recent call last): +... +django.forms.utils.ValidationError: ['ManagementForm data is missing or has been tampered with'] + +``` + + + + + +也同样用于记录多少的表单实例将被展示If you are adding new forms via JavaScript, you should increment the count fields in this form as well. 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. It is expected that all forms are present in the `POST` data regardless. + +The management form is available as an attribute of the formset itself. When rendering a formset in a template, you can include all the management data by rendering `{{ my_formset.management_form }}` (substituting the name of your formset as appropriate). + + + + + +### total_form_count and + +`BaseFormSet` has a couple of methods that are closely related to the `ManagementForm`, `total_form_count` and `initial_form_count`. + +`total_form_count` returns the total number of forms in this formset. `initial_form_count` returns the number of forms in the formset that were pre-filled, and is also used to determine how many forms are required. You will probably never need to override either of these methods, so please be sure you understand what they do before doing so. + + + + + +### empty_form + +`BaseFormSet` provides an additional attribute `empty_form` which returns a form instance with a prefix of `__prefix__` for easier use in dynamic forms with JavaScript. + + + + + +### Custom formset validation + +A formset has a `clean` method similar to the one on a `Form` class. This is where you define your own validation that works at the formset level: + + + + + +``` +>>> from django.forms.formsets import BaseFormSet +>>> from django.forms.formsets 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.'] + +``` + + + + + +The formset `clean` method is called after all the `Form.clean` methods have been called. The errors will be found using the `non_form_errors()` method on the formset. + + + + + + + +## Validating the number of forms in a formset + +Django 提供了两种方法去检查表单能够提交的最大数和最小数,应用如果需要更多的关于提交数量的自定义验证逻辑,应该使用自定义表单击验证 + + + +### validate_max + +I如果`validate_max=True` 被提交给 [`formset_factory()`](../../ref/forms/formsets.html#django.forms.formsets.formset_factory "django.forms.formsets.formset_factory"), validation 将在数据集中检查被提交表单的数量, 减去被标记删除的, 必须小于等于`max_num`. + + + + + +``` +>>> from django.forms.formsets 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.'] + +``` + + + + + +`validate_max=True` validates 将会对`max_num` 严格限制,即使提供的初始数据超过 `max_num` 而导致其无效 + + + +Note + +Regardless of `validate_max`, if the number of forms in a data set exceeds `max_num` by more than 1000, then the form will fail to validate as if `validate_max` were set, and additionally only the first 1000 forms above `max_num` will be validated. The remainder will be truncated entirely. This is to protect against memory exhaustion attacks using forged POST requests. + + + + + + + +### validate_min + +New in Django 1.7. + +If `validate_min=True` is passed to [`formset_factory()`](../../ref/forms/formsets.html#django.forms.formsets.formset_factory "django.forms.formsets.formset_factory"), validation will also check that the number of forms in the data set, minus those marked for deletion, is greater than or equal to `min_num`. + + + + + +``` +>>> from django.forms.formsets 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.'] + +``` + + + + + +Changed in Django 1.7: + +The `min_num` and `validate_min` parameters were added to [`formset_factory()`](../../ref/forms/formsets.html#django.forms.formsets.formset_factory "django.forms.formsets.formset_factory"). + + + + + + + + + +## 表单的排序和删除行为 + +[`formset_factory()`](../../ref/forms/formsets.html#django.forms.formsets.formset_factory "django.forms.formsets.formset_factory")提供两个可选参数`can_order` 和`can_delete` 来实现表单集中表单的排序和删除。 + + + +### can_order + + + +`BaseFormSet.can_order` + + + +Default: `False` + +使你创建能排序的表单集。 + + + + + +``` +>>> from django.forms.formsets 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()) + + + + + + + + + + +``` + + + + + +它会给每个表单添加一个字段,新字段命名 `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` + + + +Default: `False` + +使你创建一个表单集,可以选择删除一些表单。 + + + + + +``` +>>> from django.forms.formsets 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()) + + + + + + + + + + + +``` + + + + + +与`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`](modelforms.html#django.forms.models.BaseModelFormSet "django.forms.models.BaseModelFormSet"),调用 `formset.save()` 将删除那些有删除标记的表单的模型实例。 + +Changed in Django 1.7: + +如果你调用`formset.save(commit=False)`, 对像将不会被自动删除。你需要调用[`formset.deleted_objects`](modelforms.html#django.forms.models.BaseModelFormSet.deleted_objects "django.forms.models.BaseModelFormSet.deleted_objects")每个对像的 `delete()` 来真正删除他们。 + + + + + +``` +>>> instances = formset.save(commit=False) +>>> for obj in formset.deleted_objects: +... obj.delete() + +``` + + + + + +如果你想保持向前兼容 Django 1.6 或更早的版本,你需要这样做: + + + + + +``` +>>> try: +>>> # For Django 1.7+ +>>> for obj in formset.deleted_objects: +>>> obj.delete() +>>> except AssertionError: +>>> # Django 1.6 and earlier already deletes the objects, trying to +>>> # delete them a second time raises an AssertionError. +>>> pass + +``` + + + + + + + +On the other hand, if you are using a plain `FormSet`, it’s up to you to handle `formset.deleted_forms`, perhaps in your formset’s `save()` method, as there’s no general notion of what it means to delete a form. + + + + + + + +## 给表单集添加一个额外的字段 + +如果你想往表单集中添加额外的字段,是十分容易完成的,表单集的基类(BaseFormSet)提供了 `add_fields` 方法。可以简单的通过重写这个方法来添加你自己的字段,甚至重新定义order和deletion字段的方法和属性: + + + + + +``` +>>> from django.forms.formsets import BaseFormSet +>>> from django.forms.formsets import formset_factory +>>> from myapp.forms import ArticleForm +>>> class BaseArticleFormSet(BaseFormSet): +... def add_fields(self, form, index): +... super(BaseArticleFormSet, self).add_fields(form, index) +... form.fields["my_field"] = forms.CharField() + +>>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet) +>>> formset = ArticleFormSet() +>>> for form in formset: +... print(form.as_table()) + + + + +``` + + + + + + + + + +## 在视图和模板中使用表单集 + +在视图中使用表单集就像使用标准的`Form` 类一样简单,唯一要做的就是确信你在模板中处理表单。让我们看一个简单视图: + + + + + +``` +from django.forms.formsets import formset_factory +from django.shortcuts import render_to_response +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_to_response('manage_articles.html', {'formset': formset}) + +``` + + + + + +`manage_articles.html` 模板也可以像这样: + + + + + +``` + + {{ formset.management_form }} + + {% for form in formset + {{ form }} + {% endfor
+
+``` + + + + + +不过,上面可以用一个快捷写法,让表单集来分发管理表单: + + + + + +``` +
+ + {{ formset }} +
+
+ +``` + + + + + +上面表单集调用 `as_table` 方法。 + + + +### 手动渲染`can_delete` + +如果你在模板中手工渲染字段,那么渲染 `can_delete` 参数用`{{ form.`DELETE }}: + + + + + +``` +
+ {{ formset.management_form }} + {% for form in formset +
    +
  • {{ form.title }}
  • +
  • {{ form.pub_date }}
  • + {% if formset.can_delete +
  • {{ form.DELETE }}
  • + {% endif +
+ {% endfor
+``` + + + + + +类似的,如果表单集有排序功能(`can_order=True`),可以使用`{{ form.ORDER` }}渲染。 + + + + + +### 在视图中使用多个表单集 + +可以在视图中使用多个表单集,表单集从表单中借鉴了很多方法你可以使用 `prefix` 给每个表单字段添加前缀,以允许多个字段传递给视图,而不发生命名冲突?让我们看看可以怎么做 + + + + + +``` +from django.forms.formsets import formset_factory +from django.shortcuts import render_to_response +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_to_response('manage_articles.html', { + 'article_formset': article_formset, + 'book_formset': book_formset, + }) + +``` + + + + + +你可以以正常的方式渲染模板。记住 `prefix` 在POST请求和非POST 请求中均需设置,以便他能渲染和执行正确 + + + + + diff --git a/5_2_4_Customizing validation.md b/5_2_4_Customizing validation.md new file mode 100644 index 0000000000000000000000000000000000000000..3b8ff370f200f851171d0b0f0cd56e5a000b9eb6 --- /dev/null +++ b/5_2_4_Customizing validation.md @@ -0,0 +1,452 @@ + + +# 表单验证和字段验证 + +表单验证发生在数据验证之后。如果你需要定制化这个过程,有几个不同的地方可以修改,每个地方的目的不一样。表单处理过程中要运行三种类别的验证方法。它们通常在你调用表单的`is_valid()` 方法时执行。还有其它方法可以触发验证过程(访问`errors` 属性或直接调用`full_clean()` ),但是通用情况下不需要。 + +一般情况下,如果处理的数据有问题,每个类别的验证方法都会引发`ValidationError`,并将相关信息传递给`ValidationError`。 [_参见下文_](#raising-validation-error)中引发`ValidationError` 的最佳实践。如果没有引发`ValidationError`,这些方法应该返回验证后的(规整化的)数据的Python 对象。 + +大部分应该可以使用[validators](#validators) 完成,它们可以很容易地重用。Validators 是简单的函数(或可调用对象),它们接收一个参数并对非法的输入抛出`ValidationError`。 Validators 在字段的`to_python` 和`validate` 方法调用之后运行。 + +表单的验证划分成几个步骤,它们可以定制或覆盖: + +* 字段的`to_python()` 方法是验证的第一步。它将值强制转换为正确的数据类型,如果不能转换则引发`ValidationError`。 这个方法从Widget 接收原始的值并返回转换后的值。例如,FloatField 将数据转换为Python 的`float` 或引发`ValidationError`。 + +* 字段的`validate()` 方法处理字段特异性的验证,这种验证不适合位于validator 中。它接收一个已经转换成正确数据类型的值, 并在发现错误时引发`ValidationError`。这个方法不返回任何东西且不应该改变任何值。当你遇到不可以或不想放在validator 中的验证逻辑时,应该覆盖它来处理验证。 + +* 字段的`run_validators()` 方法运行字段的所有Validator,并将所有的错误信息聚合成一个单一的`ValidationError`。你应该不需要覆盖这个方法。 + +* Field子类的`clean()` 方法。它负责以正确的顺序运行`to_python`、`validate` 和 `run_validators` 并传播它们的错误。如果任何时刻、任何方法引发`ValidationError`,验证将停止并引发这个错误。这个方法返回验证后的数据,这个数据在后面将插入到表单的 `cleaned_data` 字典中。 + +* 表单子类中的`clean_()` 方法 —— `` 通过表单中的字段名称替换。这个方法完成于特定属性相关的验证,这个验证与字段的类型无关。这个方法没有任何传入的参数。你需要查找`self.cleaned_data` 中该字段的值,记住此时它已经是一个Python 对象而不是表单中提交的原始字符串(它位于`cleaned_data` 中是因为字段的`clean()` 方法已经验证过一次数据)。 + + 例如,如果你想验证名为`serialnumber` 的`CharField` 的内容是否唯一, `clean_serialnumber()` 将是实现这个功能的理想之处。你需要的不是一个特别的字段(它只是一个`CharField`),而是一个特定于表单字段特定验证,并规整化数据。 + + 这个方法返回从cleaned_data 中获取的值,无论它是否修改过。 + +* 表单子类的`clean()` 方法。这个方法可以实现需要同时访问表单多个字段的验证。这里你可以验证如果提供字段`A`,那么字段`B` 必须包含一个合法的邮件地址以及类似的功能。 这个方法可以返回一个完全不同的字典,该字典将用作`cleaned_data`。 + + 因为字段的验证方法在调用`clean()` 时会运行,你还可以访问表单的`errors` 属性,它包含验证每个字段时的所有错误。 + + 注意,你覆盖的[`Form.clean()`](api.html#django.forms.Form.clean "django.forms.Form.clean") 引发的任何错误将不会与任何特定的字段关联。它们位于一个特定的“字段”(叫做`__all__`)中,如果需要可以通过 [`non_field_errors()`](api.html#django.forms.Form.non_field_errors "django.forms.Form.non_field_errors") 方法访问。如果你想添加一个特定字段的错误到表单中,需要调用 [`add_error()`](api.html#django.forms.Form.add_error "django.forms.Form.add_error")。 + + 还要注意,覆盖`ModelForm` 子类的`clean()` 方法需要特殊的考虑。(更多信息参见[_ModelForm 文档_](../../topics/forms/modelforms.html#overriding-modelform-clean-method))。 + +这些方法按以上给出的顺序执行,一次验证一个字段。也就是说,对于表单中的每个字段(按它们在表单定义中出现的顺序),先运行`Field.clean()` ,然后运行`clean_()`。每个字段的这两个方法都执行完之后,最后运行[`Form.clean()`](api.html#django.forms.Form.clean "django.forms.Form.clean") 方法,无论前面的方法是否抛出过异常。 + +下面有上面每个方法的示例。 + +我们已经提到过,所有这些方法都可以抛出`ValidationError`。对于每个字段,如果`Field.clean()` 方法抛出 `ValidationError`,那么将不会调用该字段对应的clean_()方法。 但是,剩余的字段的验证方法仍然会执行。 + + + +## 抛出`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) + +``` + + + + + +New in Django 1.7. + +[`Form.errors.as_data()`](api.html#django.forms.Form.errors.as_data "django.forms.Form.errors.as_data") 和[`Form.errors.as_json()`](api.html#django.forms.Form.errors.as_json "django.forms.Form.errors.as_json") 方法很大程度上受益于`ValidationError`(利用`code` 名和`params` 字典)。 + + + +### 抛出多个错误 + +如果在一个验证方法中检查到多个错误并且希望将它们都反馈给表单的提交者,可以传递一个错误的列表给`ValidationError` 构造函数。 + +和上面一样,建议传递的列表中的`ValidationError` 实例都带有 `code` 和`params`,但是传递一个字符串列表也可以工作: + + + + + +``` +# Good +raise ValidationError([ + ValidationError(_('Error 1'), code='error1'), + ValidationError(_('Error 2'), code='error2'), +]) + +# Bad +raise ValidationError([ + _('Error 1'), + _('Error 2'), +]) + +``` + + + + + + + + + + + +## 实践验证 + +前面几节解释在一般情况下表单的验证是如何工作的。因为有时直接看功能在实际中的应用会更容易掌握,下面是一些列小例子,它们用到前面的每个功能。 + + + +### 使用Validator + +Django 的表单(以及模型)字段支持使用简单的函数和类用于验证,它们叫做Validator。Validator 是可调用对象或函数,它接收一个值,如果该值合法则什么也不返回,否则抛出[`ValidationError`](../exceptions.html#django.core.exceptions.ValidationError "django.core.exceptions.ValidationError")。它们可以通过字段的`validators` 参数传递给字段的构造函数,或者定义在[`Field`](fields.html#django.forms.Field "django.forms.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`](../validators.html#django.core.validators.RegexValidator "django.core.validators.RegexValidator") 的一个实例,它构造时的第一个参数为:`^[-a-zA-Z0-9_]+$`。[_编写Validator_](../validators.html) 一节可以查到已经存在的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`,就和其它表单字段一样。当调用表单的`is_valid()` 方法时,`MultiEmailField.clean()` 方法将作为验证过程的一部分运行,它将调用自定义的`to_python()` 和`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 the cleaned data, whether you have changed it or + # not. + return data + +``` + + + + + + + + + +### 验证相互依赖的字段 + +假设我们添加另外一个需求到我们的联系人表单中:如果`cc_myself` 字段为`True`,那么`subject` 必须包含单词`"help"`。我们的这个验证包含多个字段,所以表单的[`clean()`](api.html#django.forms.Form.clean "django.forms.Form.clean") 方法是个不错的地方。注意,我们这里讨论的是表单的`clean()` 方法,之前我们编写的字段的`clean()` 方法。区别字段和表单之间的差别非常重要。字段是单个数据,表单是字段的集合。 + +在调用表单`clean()` 方法的时候,所有字段的验证方法已经执行完(前两节),所以`self.cleaned_data` 填充的是目前为止已经合法的数据。所以你需要记住这个事实,你需要验证的字段可能没有通过初试的字段检查。 + +在这一步,有两种方法报告错误。最简单的方法是在表单的顶端显示错误。你可以在`clean()` 方法中抛出`ValidationError` 来创建错误。例如: + + + + + +``` +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.") + +``` + + + + + +Changed in Django 1.7: + +在以前版本的Django 中,要求`form.clean()` 返回`cleaned_data` 的一个字典。现在,这个方法仍然可以返回将要用到的数据的字典,但是不再是强制的。 + + + +在这段代码中,如果抛出验证错误,表单将在表单的顶部显示(通常是)描述该问题的一个错误信息。 + +注意,示例代码中`super(ContactForm, self).clean()` 的调用时为了保证维持父类中的验证逻辑。 + +第二种方法涉及将错误消息关联到某个字段。在这种情况下,让我们在表单的显示中分别关联一个错误信息到“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` 的一个实例。更多细节参见[_抛出ValidationError_](#raising-validation-error)。注意,`add_error()` 将从`cleaned_data` 中删除相应的字段。 + + + + + diff --git a/6_2_Overview.md b/6_2_Overview.md new file mode 100644 index 0000000000000000000000000000000000000000..69787bf52feb7b103509b2faedfa6691a3ae1082 --- /dev/null +++ b/6_2_Overview.md @@ -0,0 +1,537 @@ + + +# 应用 + +New in Django 1.7. + +Django 包含一个由已安装应用组成的注册表,它保存应用的配置并提供自省。它还维护一个可用的[_模型_](../topics/db/models.html)的列表。 + +这个注册表叫做[`apps`](#django.apps.apps "django.apps.apps"),位于[`django.apps`](#module-django.apps "django.apps") 中: + + + + + +``` +>>> from django.apps import apps +>>> apps.get_app_config('admin').verbose_name +'Admin' + +``` + + + + + + + +## 项目和应用 + +历史上,Django 使用**项目** 这个词来描述Django 安装的一个应用。现在项目主要通过一个设置模块定义。 + +**应用** 这个词表示一个Python 包,它提供某些功能的集合。应用可以在各种项目中重用。 + + + +注 + +最近,这个词有些令人困惑,因为使用词语“Web ?应用”来描述和Django 项目等同的事物渐渐变得常见。 + + + +应用包含模型、视图、模板、模板标签、静态文件、URL、中间件等等。它们一般通过[`INSTALLED_APPS`](settings.html#std:setting-INSTALLED_APPS) 设置和其它例如URLconf、[`MIDDLEWARE_CLASSES`](settings.html#std:setting-MIDDLEWARE_CLASSES) 设置以及模板继承等机制接入到项目中。 + +Django 的应用只是一个代码集,它与框架的其它部分进行交互,理解这点很重要。没有类似一个`应用` 对象这样的东西。然而,有一些地方Django 需要与安装的应用交互,主要用于配置和自省。这是为什么应用注册表要在[`AppConfig`](#django.apps.AppConfig "django.apps.AppConfig") 实例中维持每个安装的应用的元数据。 + + + + + +## 配置应用 + +要配置一个应用,请子类化[`AppConfig`](#django.apps.AppConfig "django.apps.AppConfig") 并将这个子类的路径放在[`INSTALLED_APPS`](settings.html#std:setting-INSTALLED_APPS) 中。 + +当[`INSTALLED_APPS`](settings.html#std:setting-INSTALLED_APPS) 包含一个应用模块的路径后,Django 将在这个模块中检查一个`default_app_config` 变量。 + +如果这个变量有定义,它应该是这个应用的[`AppConfig`](#django.apps.AppConfig "django.apps.AppConfig") 子类的路径。 + +如果没有`default_app_config`,Django 将使用[`AppConfig`](#django.apps.AppConfig "django.apps.AppConfig") 基类。 + + + +### 对于应用的开发者 + +如果你正在创建一个名叫“Rock ’n’ roll”的可插式应用,下面展示了如何给admin提供一个合适的名字 + + + + + +``` +# rock_n_roll/apps.py + +from django.apps import AppConfig + +class RockNRollConfig(AppConfig): + name = 'rock_n_roll' + verbose_name = "Rock ’n’ roll" + +``` + + + + + +可以让你的应用像下面一样以默认方式加载这个[`AppConfig`](#django.apps.AppConfig "django.apps.AppConfig")的子类 + + + + + +``` +# rock_n_roll/__init__.py + +default_app_config = 'rock_n_roll.apps.RockNRollConfig' + +``` + + + + + +这将使得[`INSTALLED_APPS`](settings.html#std:setting-INSTALLED_APPS) 只包含`'rock_n_roll'` 时将使用`RockNRollConfig`。这允许你使用[`AppConfig`](#django.apps.AppConfig "django.apps.AppConfig") 功能而不用要求你的用户更新他们的[`INSTALLED_APPS`](settings.html#std:setting-INSTALLED_APPS) 设置。 + +当然,你也可以告诉你的用户将`'rock_n_roll.apps.RockNRollConfig'` 放在他们的[`INSTALLED_APPS`](settings.html#std:setting-INSTALLED_APPS) 设置中。 你甚至可以提供几个具有不同行为的[`AppConfig`](#django.apps.AppConfig "django.apps.AppConfig") 子类,并让使用者通过他们的 [`INSTALLED_APPS`](settings.html#std:setting-INSTALLED_APPS) 设置选择。 + +建议的惯例做法是将配置类放在应用的`apps` 子模块中。但是,Django 不强制这一点。 + +你必须包含[`name`](#django.apps.AppConfig.name "django.apps.AppConfig.name") 属性来让Django 决定该配置适用的应用。你可以定义[`AppConfig`](#django.apps.AppConfig "django.apps.AppConfig") API 参考中记录的任何属性。 + + + +注 + +如果你的代码在应用的`__init__.py` 中导入应用程序注册表,名称`apps` 将与`apps` 子模块发生冲突。最佳做法是将这些代码移到子模块,并将其导入。一种解决方法是以一个不同的名称导入注册表︰ + + + + + +``` +from django.apps import apps as django_apps + +``` + + + + + + + + + + + +### 对于应用的使用者 + +如果你在`anthology`项目中使用"Rock ’n’ rol",但您希望显示成"Gypsy jazz",你可以提供你自己的配置︰ + + + + + +``` +# anthology/apps.py + +from rock_n_roll.apps import RockNRollConfig + +class GypsyJazzConfig(RockNRollConfig): + verbose_name = "Gypsy jazz" + +# anthology/settings.py + +INSTALLED_APPS = [ + 'anthology.apps.GypsyJazzConfig', + # ... +] + +``` + + + + + +再说一次,在`apps` 子模块定义特定于项目的配置类是一项惯例,并不要求。 + + + + + + + +## 应用程序配置 + + + +_class_ `AppConfig` + + + +应用程序配置对象存储应用程序的元数据。某些属性可以配置 [`AppConfig`](#django.apps.AppConfig "django.apps.AppConfig") 子类中。其他由Django 设置且是只读的。 + + + + + + + +### 可配置的属性 + + + +`AppConfig.name` + + + +应用的完整Python 路径,例如`django.contrib.admin`。 + +这个属性定义配置适用于哪个应用程序。它必须在所有 [`AppConfig`](#django.apps.AppConfig "django.apps.AppConfig") 子类中设置。 + +它在整个Django 项目中必须是唯一的。 + + + + + + + +`AppConfig.label` + + + +应用的缩写名称,例如`'admin'`。 + +此属性允许重新标记应用,当两个应用程序有冲突的标签。它默认为`name` 的最后一部分。它应该是一个有效的 Python 标识符。 + +它在整个Django 项目中必须是唯一的。 + + + + + + + +`AppConfig.verbose_name` + + + +应用的适合阅读的名称,例如“Administration”。 + +此属性默认为`label.title()`。 + + + + + + + +`AppConfig.path` + + + +应用目录的文件系统路径,如 `'/ usr/lib/python3.4/dist-packages/django/contrib/admin'`。 + +在大多数情况下,Django 可以自动检测并此设置,但你也可以在[`AppConfig`](#django.apps.AppConfig "django.apps.AppConfig") 子类上提供一个显式的类属性以覆盖它 。在有些情况下是需要这样的;例如,如果应用的包是一个具有多个路径的[命名空间包](#namespace-package)。 + + + + + + + + + +### 只读属性 + + + +`AppConfig.module` + + + +应用的根模块,例如``。 + + + + + + + +`AppConfig.models_module` + + + +包含模型的模块,例如``。 + +如果应用不包含`models`模块,它可能为`None`。注意与数据库相关的信号,如 [`pre_migrate`](signals.html#django.db.models.signals.pre_migrate "django.db.models.signals.pre_migrate") 和 [`post_migrate`](signals.html#django.db.models.signals.post_migrate "django.db.models.signals.post_migrate") 只有在应用具有`models` 模块时才发出。 + + + + + + + + + +### 方法 + + + +`AppConfig.get_models`() + + + +返回可迭代的[`Model`](models/instances.html#django.db.models.Model "django.db.models.Model")类。 + + + + + + + +`AppConfig.get_model`(_model_name_) + + + +返回具有给定 `model_name` 的 [`Model`](models/instances.html#django.db.models.Model "django.db.models.Model")。如果没有这种模型存在,抛出[`LookupError`](https://docs.python.org/3/library/exceptions.html#LookupError "(in Python v3.4)")。`model_name` 是不区分大小写。 + + + + + + + +`AppConfig.ready`() + + + +子类可以重写此方法以执行初始化任务,如注册信号。它在注册表填充完之后立即调用。 + +你不可以在定义应用程序配置类的模块中导入模型中,但你可以使用 [`get_model()`](#django.apps.AppConfig.get_model "django.apps.AppConfig.get_model") 来通过名称访问模型类,像这样︰ + + + + + +``` +def ready(self): + MyModel = self.get_model('MyModel') + +``` + + + + + + + +警告 + +虽然你可以如上文所述访问模型类,请避免与在你的[`ready()`](#django.apps.AppConfig.ready "django.apps.AppConfig.ready") 实现中与数据库交互。包括执行查询([`save()`](models/instances.html#django.db.models.Model.save "django.db.models.Model.save")、[`delete()`](models/instances.html#django.db.models.Model.delete "django.db.models.Model.delete")、管理方法等)的模型方法,以及通过 `django.db.connection` 的原始SQL 查询。你的[`ready()`](#django.apps.AppConfig.ready "django.apps.AppConfig.ready") 方法将在每个管理命令启动期间运行。例如,即使测试数据库配置与生产设置是分离的,`manage.py test` 仍会执行一些针对您的 **production** 数据库的查询 ! + + + + + +注 + +在常规的初始化过程中,`ready` 方法只由Django 调用一次。但在一些极端情况下,特别是在欺骗安装好的应用的测试,`ready` 可能被调用不止一次。在这种情况下,编写幂等方法,或者在`AppConfig` 类上放置一个标识来防止应该执行一次的代码重复运行。 + + + + + + + + + + + +### Namespace packages as apps (Python 3.3+) + +Python versions 3.3 and later support Python packages without an `__init__.py` file. These packages are known as “namespace packages” and may be spread across multiple directories at different locations on `sys.path` (see [**PEP 420**](http://www.python.org/dev/peps/pep-0420)). + +Django applications require a single base filesystem path where Django (depending on configuration) will search for templates, static assets, etc. Thus, namespace packages may only be Django applications if one of the following is true: + +1. The namespace package actually has only a single location (i.e. is not spread across more than one directory.) +2. The [`AppConfig`](#django.apps.AppConfig "django.apps.AppConfig") class used to configure the application has a [`path`](#django.apps.AppConfig.path "django.apps.AppConfig.path") class attribute, which is the absolute directory path Django will use as the single base path for the application. + +If neither of these conditions is met, Django will raise [`ImproperlyConfigured`](exceptions.html#django.core.exceptions.ImproperlyConfigured "django.core.exceptions.ImproperlyConfigured"). + + + + + + + +## 应用的注册表 + + + +`apps` + + + +应用的注册表提供下列公共API。没有在下面列出的方法被认为是私有的,恕不另行通知。 + + + + + + + +`apps.ready` + + + +布尔属性, 当填充完注册表设置为`True`。 + + + + + + + +`apps.get_app_configs`() + + + +返回一个由[`AppConfig`](#django.apps.AppConfig "django.apps.AppConfig") 实例组成可迭代对象。 + + + + + + + +`apps.get_app_config`(_app_label_) + + + +返回具有给定`app_label` 的[`AppConfig`](#django.apps.AppConfig "django.apps.AppConfig")。如果没有这种模型存在,抛出[`LookupError`](https://docs.python.org/3/library/exceptions.html#LookupError "(in Python v3.4)")。 + + + + + + + +`apps.is_installed`(_app_name_) + + + +检查注册表中是否存在具有给定名称的应用。`app_name` 是应用的完整名称,例如`django.contrib.admin` 。 + + + + + + + +`apps.get_model`(_app_label_, _model_name_) + + + +返回具有给定`app_label` 和`model_name`的[`Model`](models/instances.html#django.db.models.Model "django.db.models.Model")。作为快捷方式,此方法还接受`app_label.model_name` 形式的一个单一参数。`model_name` 不区分大小写。 + +如果没有这种模型存在,抛出[`LookupError`](https://docs.python.org/3/library/exceptions.html#LookupError "(in Python v3.4)")。使用不包含一个点的单个参数调用时将引发[`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError "(in Python v3.4)")。 + + + + + + + + + +## 初始化过程 + + + +### 应用是如何载入的 + +当Django 启动时,[`django.setup()`](#django.setup "django.setup") 负责填充应用注册表。 + + + +`setup`()[[source]](../_modules/django.html#setup) + + + +配置Django: + +* 加载设置。 +* 设置日志。 +* 初始化应用注册表。 + +自动调用此函数︰ + +* 当运行一个通过Django 的WSGI 支持 的HTTP 服务器。 +* 当调用管理命令。 + +在其他情况下它必须显式调用,例如在普通的 Python 脚本中。 + + + + + +应用注册表初始化分三个阶段。在每个阶段,Django 以[`INSTALLED_APPS`](settings.html#std:setting-INSTALLED_APPS) 中的顺序处理所有应用。 + +1. 首先,Django 会导入[`INSTALLED_APPS`](settings.html#std:setting-INSTALLED_APPS)中的所有应用。 + + 如果它是一个应用配置类,Django 导入应用的根包,通过其[`name`](#django.apps.AppConfig.name "django.apps.AppConfig.name") 属性。如果它是一个Python 包,Django 创建应用的一个默认配置。 + + _在这个阶段,你的代码不应该将任何模型导入!_ + + 换句话说,你的应用程序的根包和定义应用配置类的模块不应该导入任何模型,即使是间接导入。 + + 严格地讲,Django 允许应用配置加载后导入模型。然而,为了避免[`INSTALLED_APPS`](settings.html#std:setting-INSTALLED_APPS) 的顺序带来不必要的约束,强烈推荐在这一阶段不导入任何模型。 + + 这一阶段完成后,操作应用配置的API 开始变得可用,例如[`get_app_config()`](#django.apps.apps.get_app_config "django.apps.apps.get_app_config")。 + +2. 然后Django 试图导入每个应用的`models` 子模块,如果有的话。 + + 你必须在应用的`models.py` 或`models/__init__.py` 中定义或导入所有模型。否则,应用注册表在此时可能不会完全填充,这可能导致ORM 出现故障。 + + 一旦完成该步骤, ?[`get_model()`](#django.apps.apps.get_model "django.apps.apps.get_model") 之类的 model API 可以使用了. + +3. 最后,Django 运行每个应用程序配置的[`ready()`](#django.apps.AppConfig.ready "django.apps.AppConfig.ready") 方法。 + + + + + +### 故障排除 + +下面是一些在你初始化的时候可能经常碰到的问题: + +* `AppRegistryNotReady` 发生这种情况是当导入应用的配置或模型模块时触发取决于应用注册表的代码。 + + 例如,[`ugettext()`](utils.html#django.utils.translation.ugettext "django.utils.translation.ugettext") 使用应用注册表来查找应用中的翻译目录。若要在导入时翻译,你需要[`ugettext_lazy()`](utils.html#django.utils.translation.ugettext_lazy "django.utils.translation.ugettext_lazy")。(使用[`ugettext()`](utils.html#django.utils.translation.ugettext "django.utils.translation.ugettext") 将是一个bug,因为翻译会发生在导入的时候,而不是取决于每个请求的语言。) + + 模型模块中在导入时使用ORM 执行数据库查询也会引发此异常。ORM 直到所有的模型都可用才能正常运转。 + + 另一个常见的罪魁祸首,是[`django.contrib.auth.get_user_model()`](../topics/auth/customizing.html#django.contrib.auth.get_user_model "django.contrib.auth.get_user_model")。请在导入时使用[`AUTH_USER_MODEL`](settings.html#std:setting-AUTH_USER_MODEL) 设置来引用用户模型。 + + 如果在一个独立的 Python 脚本中你忘了调用[`django.setup()`](#django.setup "django.setup"),也会发生此异常。 + +* `ImportError: cannot import name ...` 如果导入出现循环,则会发生这种情况。 + + 要消除这种问题,应尽量减少模型模块之间的依赖项,并在导入时尽可能少做工作。为了避免在导入时执行代码,你可以移动它到一个函数和缓存其结果。当你第一次需要其结果时,将执行该代码。这一概念被称为"惰性求值"。 + +* `django.contrib.admin` 在安装的应用中自动发现`admin`。要阻止它,请更改你的[`INSTALLED_APPS`](settings.html#std:setting-INSTALLED_APPS) 以包含 `'django.contrib.admin.apps.SimpleAdminConfig'` 而不是 `'django.contrib.admin'`。 + + + + + diff --git a/8_6_Security Middleware.md b/8_6_Security Middleware.md new file mode 100644 index 0000000000000000000000000000000000000000..93eee628c8ddd431692a88be7af5c799a01c73e3 --- /dev/null +++ b/8_6_Security Middleware.md @@ -0,0 +1,444 @@ + + +# 中间件 + +这篇文档介绍了Django自带的所有中间件组件。 要查看关于如何使用它们以及如何编写自己的中间件,请见[_中间件使用指导_](../topics/http/middleware.html)。 + + + +## 可用的中间件 + + + +### 缓存中间件 + + + +_class_ `UpdateCacheMiddleware`[[source]](../_modules/django/middleware/cache.html#UpdateCacheMiddleware) + + + + + +_class_ `FetchFromCacheMiddleware`[[source]](../_modules/django/middleware/cache.html#FetchFromCacheMiddleware) + + + +开启全站范围的缓存。 如果开启了这些缓存,任何一个由Django提供的页面将会被缓存,缓存时长是由你在[`CACHE_MIDDLEWARE_SECONDS`](settings.html#std:setting-CACHE_MIDDLEWARE_SECONDS)配置中定义的。详见[_缓存文档_](../topics/cache.html)。 + + + + + +### “通用”的中间件 + + + +_class_ `CommonMiddleware`[[source]](../_modules/django/middleware/common.html#CommonMiddleware) + + + +给完美主义者增加一些便利条件: + +* 禁止访问[`DISALLOWED_USER_AGENTS`](settings.html#std:setting-DISALLOWED_USER_AGENTS)中设置的用户代理,这项配置应该是一个已编译的正则表达式对象的列表。 + +* 基于[`APPEND_SLASH`](settings.html#std:setting-APPEND_SLASH)和[`PREPEND_WWW`](settings.html#std:setting-PREPEND_WWW)的设置来重写URL。 + + 如果[`APPEND_SLASH`](settings.html#std:setting-APPEND_SLASH)设为`True`,并且初始URL 没有以斜线结尾以及在URLconf 中没找到对应定义,这时形成一个斜线结尾的新URL。如果这个新的URL存在于URLconf,那么Django 重定向请求到这个新URL上。否则,按正常情况处理初始的URL。 + + 比如,如果你没有为`foo.com/bar`定义有效的正则,但是_为_`foo.com/bar/`定义了有效的正则,`foo.com/bar`将会被重定向到`foo.com/bar/`。 + + 如果[`PREPEND_WWW`](settings.html#std:setting-PREPEND_WWW)设为`True`,前面缺少 "www."的url将会被重定向到相同但是以一个"www."开头的url。 + + 两种选项都是为了规范化url。其中的哲学就是,任何一个url应该在一个地方仅存在一个。技术上来讲,URL `foo.com/bar` 区别于`foo.com/bar/` —— 搜索引擎索引会把这里分开处理 —— 因此最佳实践就是规范化URL。 + +* 基于[`USE_ETAGS`](settings.html#std:setting-USE_ETAGS) 设置来处理ETag。如果设置[`USE_ETAGS`](settings.html#std:setting-USE_ETAGS)为`True`,Django会通过MD5-hashing处理页面的内容来为每一个页面请求计算Etag,并且如果合适的话,它将会发送携带`Not Modified`的响应。 + + + +`CommonMiddleware.response_redirect_class` + + + +New in Django 1.8. + +默认为[`HttpResponsePermanentRedirect`](request-response.html#django.http.HttpResponsePermanentRedirect "django.http.HttpResponsePermanentRedirect")。它继承了`CommonMiddleware`,并覆写了属性来自定义中间件发出的重定向。 + + + +_class_ `BrokenLinkEmailsMiddleware`[[source]](../_modules/django/middleware/common.html#BrokenLinkEmailsMiddleware) + + + +* 向[`MANAGERS`](settings.html#std:setting-MANAGERS)发送死链提醒邮件(详见[_错误报告_](../howto/error-reporting.html))。 + + + + + +### GZip中间件 + + + +_class_ `GZipMiddleware`[[source]](../_modules/django/middleware/gzip.html#GZipMiddleware) + + + + + +警告 + +安全研究员最近发现,当压缩技术(包括`GZipMiddleware`)用于一个网站的时候,网站会受到一些可能的攻击。此外,这些方法可以用于破坏Django的CSRF保护。在你的站点使用`GZipMiddleware`之前,你应该先仔细考虑一下你的站点是否容易受到这些攻击。 如果你不确定是否会受到这些影响,应该避免使用?`GZipMiddleware`。详见[the BREACH paper (PDF)](http://breachattack.com/resources/BREACH%20-%20SSL,%20gone%20in%2030%20seconds.pdf)和[breachattack.com](http://breachattack.com)。 + + + +为支持GZip压缩的浏览器(一些现代的浏览器)压缩内容。 + +建议把这个中间件放到中间件配置列表的第一个,这样压缩响应内容的处理会到最后才 发生。 + +如果满足下面条件的话,内容不会被压缩: + +* 消息体的长度小于200个字节。 +* 响应已经设置了`Content-Encoding`协议头。 +* 请求(浏览器)没有发送包含`gzip`的`Accept-Encoding`协议头。 + +你可以通过这个[`gzip_page()`](../topics/http/decorators.html#django.views.decorators.gzip.gzip_page "django.views.decorators.gzip.gzip_page")装饰器使用独立的GZip压缩。 + + + + + +### 带条件判断的GET中间件 + + + +_class_ `ConditionalGetMiddleware`[[source]](../_modules/django/middleware/http.html#ConditionalGetMiddleware) + + + +处理带有条件判断状态GET操作。 如果一个请求包含 `ETag`?或者`Last-Modified`协议头,并且请求包含`If-None-Match`或`If-Modified-Since`,这时响应会被 替换为[`HttpResponseNotModified`](request-response.html#django.http.HttpResponseNotModified "django.http.HttpResponseNotModified")。 + +另外,它会设置`Date`和`Content-Length`响应头。 + + + + + +### 地域性中间件 + + + +_class_ `LocaleMiddleware`[[source]](../_modules/django/middleware/locale.html#LocaleMiddleware) + + + +基于请求中的数据开启语言选择。 它可以为每个用户进行定制。 详见[_国际化文档_](../topics/i18n/translation.html)。 + + + +`LocaleMiddleware.response_redirect_class` + + + +默认为[`HttpResponseRedirect`](request-response.html#django.http.HttpResponseRedirect "django.http.HttpResponseRedirect")。继承自`LocaleMiddleware`并覆写了属性来自定义中间件发出的重定向。 + + + + + +### 消息中间件 + + + +_class_ `MessageMiddleware`[[source]](../_modules/django/contrib/messages/middleware.html#MessageMiddleware) + + + +开启基于Cookie和会话的消息支持。详见[_消息文档_](contrib/messages.html)。 + + + + + +### 安全中间件 + + + +警告 + +如果你的部署环境允许的话,让你的前端web服务器展示`SecurityMiddleware`提供的功能是个好主意。这样一来,如果有任何请求没有被Django处理(比如静态媒体或用户上传的文件),它们会拥有和向Django 应用的请求相同的保护。 + + + + + +_class_ `SecurityMiddleware`[[source]](../_modules/django/middleware/security.html#SecurityMiddleware) + + + +New in Django 1.8. + +`django.middleware.security.SecurityMiddleware`为请求/响应循环提供了几种安全改进。每一种可以通过一个选项独立开启或关闭。 + +* [SECURE_BROWSER_XSS_FILTER](settings.html#std:setting-SECURE_BROWSER_XSS_FILTER) +* [SECURE_CONTENT_TYPE_NOSNIFF](settings.html#std:setting-SECURE_CONTENT_TYPE_NOSNIFF) +* [SECURE_HSTS_INCLUDE_SUBDOMAINS](settings.html#std:setting-SECURE_HSTS_INCLUDE_SUBDOMAINS) +* [SECURE_HSTS_SECONDS](settings.html#std:setting-SECURE_HSTS_SECONDS) +* [SECURE_REDIRECT_EXEMPT](settings.html#std:setting-SECURE_REDIRECT_EXEMPT) +* [SECURE_SSL_HOST](settings.html#std:setting-SECURE_SSL_HOST) +* [SECURE_SSL_REDIRECT](settings.html#std:setting-SECURE_SSL_REDIRECT) + + + +#### HTTP Strict Transport Security (HSTS) + +对于那些应该只能通过HTTPS访问的站点,你可以通过设置[HSTS协议头](http://en.wikipedia.org/wiki/Strict_Transport_Security),通知现代的浏览器,拒绝用不安全的连接来连接你的域名。这会降低你受到SSL-stripping的中间人(MITM)攻击的风险。 + +如果你将[`SECURE_HSTS_SECONDS`](settings.html#std:setting-SECURE_HSTS_SECONDS)设置为一个非零值,`SecurityMiddleware`会在所有的HTTPS响应中设置这个协议头。 + +开启HSTS的时候,首先使用一个小的值来测试它是个好主意,例如,让[`SECURE_HSTS_SECONDS = 3600`](settings.html#std:setting-SECURE_HSTS_SECONDS)为一个小时。每当浏览器在你的站点看到HSTS协议头,都会在提供的时间段内拒绝使用不安全(HTTP)的方式连接到你的域名。一旦你确认你站点上的所有东西都以安全的方式提供(例如,HSTS并不会干扰任何事情),建议你增加这个值,这样不常访问你站点的游客也会被保护(比如,一般设置为31536000秒,一年)。 + +另外,如果你将?[`SECURE_HSTS_INCLUDE_SUBDOMAINS`](settings.html#std:setting-SECURE_HSTS_INCLUDE_SUBDOMAINS)设置为`True`,,`SecurityMiddleware`会将`includeSubDomains`标签添加到`Strict-Transport-Security`协议头中。强烈推荐这样做(假设所有子域完全使用HTTPS),否则你的站点仍旧有可能由于子域的不安全连接而受到攻击。 + + + +警告 + +HSTS策略在你的整个域中都被应用,不仅仅是你所设置协议头的响应中的url。所以,如果你的整个域都设置为HTTPS only,你应该只使用HSTS策略。 + +适当遵循HSTS协议头的浏览器,会通过显示警告的方式,拒绝让用户连接到证书过期的、自行签署的、或者其他SSL证书无效的站点。如果你使用了HSTS,确保你的证书处于一直有效的状态! + + + + + +注意 + +如果你的站点部署在负载均衡器或者反向代理之后,并且`Strict-Transport-Security`协议头没有添加到你的响应中,原因是Django有可能意识不到这是一个安全连接。你可能需要设置[`SECURE_PROXY_SSL_HEADER`](settings.html#std:setting-SECURE_PROXY_SSL_HEADER)。 + + + + + + + +#### X-Content-Type-Options: nosniff + +一些浏览器会尝试猜测他们所得内容的类型,而不是读取`Content-Type`协议头。虽然这样有助于配置不当的服务器正常显示内容,但也会导致安全问题。 + +如果你的站点允许用户上传文件,一些恶意的用户可能会上传一个精心构造的文件,当你觉得它无害的时候,文件会被浏览器解释成HTML或者Javascript。 + +欲知更多有关这个协议头和浏览器如何处理它的内容,你可以在[IE安全博客](http://blogs.msdn.com/b/ie/archive/2008/09/02/ie8-security-part-vi-beta-2-update.aspx)中读到它。 + +要防止浏览器猜测内容类型,并且强制它一直使用?`Content-Type`协议头中提供的类型,你可以传递`X-Content-Type-Options: nosniff`协议头。`SecurityMiddleware`将会对所有响应这样做,如果[`SECURE_CONTENT_TYPE_NOSNIFF`](settings.html#std:setting-SECURE_CONTENT_TYPE_NOSNIFF) 设置为`True`。 + +注意在大多数Django不涉及处理上传文件的部署环境中,这个设置不会有任何帮助。例如,如果你的[`MEDIA_URL`](settings.html#std:setting-MEDIA_URL)被前端web服务器直接处理(例如nginx和Apache),你可能想要在那里设置这个协议头。而在另一方面,如果你使用Django执行为了下载文件而请求授权之类的事情,并且你不能使用你的web服务器设置协议头,这个设置会很有用。 + + + + + +#### X-XSS-Protection: 1; mode=block + +一些浏览器能够屏蔽掉出现[XSS攻击](http://en.wikipedia.org/wiki/Cross-site_scripting)的内容。通过寻找页面中GET或者POST参数中的JavaScript内容来实现。如果JavaScript在服务器的响应中被重放,页面就会停止渲染,并展示一个错误页来取代。 + +[X-XSS-Protection协议头](http://blogs.msdn.com/b/ie/archive/2008/07/02/ie8-security-part-iv-the-xss-filter.aspx)用来控制XSS过滤器的操作。 + +要在浏览器中启用XSS过滤器,并且强制它一直屏蔽可疑的XSS攻击,你可以在协议头中传递`X-XSS-Protection: 1; mode=block`。 如果[`SECURE_BROWSER_XSS_FILTER`](settings.html#std:setting-SECURE_BROWSER_XSS_FILTER)设置为`True`,`SecurityMiddleware`会在所有响应中这样做。 + + + +警告 + +浏览器的XSS过滤器是一个十分有效的手段,但是不要过度依赖它。它并不能检测到所有的XSS攻击,也不是所有浏览器都支持这一协议头。确保你[_校验和过滤_](../topics/security.html#cross-site-scripting)了所有的输入来防止XSS攻击。 + + + + + + + +#### SSL重定向 + +如果你同时提供HTTP和HTTPS连接,大多数用户会默认使用不安全的(HTTP)链接。为了更高的安全性,你应该重定向所有的HTTP 连接到HTTPS。 + +如果你将[`SECURE_SSL_REDIRECT`](settings.html#std:setting-SECURE_SSL_REDIRECT)设置为True,`SecurityMiddleware`会将HTTP链接永久地(HTTP 301,permanently)重定向到HTTPS连接。 + + + +注意 + +由于性能因素,最好在Django外面执行这些重定向,在[nginx](http://nginx.org)这种前端负载均衡器或者反向代理服务器中执行。[`SECURE_SSL_REDIRECT`](settings.html#std:setting-SECURE_SSL_REDIRECT)专门为这种部署情况而设计,当这不可选择的时候。 + + + +如果[`SECURE_SSL_HOST`](settings.html#std:setting-SECURE_SSL_HOST)设置有一个值,所有重定向都会发到值中的主机,而不是原始的请求主机。 + +如果你站点上的一些页面应该以HTTP方式提供,并且不需要重定向到HTTPS,你可以[`SECURE_REDIRECT_EXEMPT`](settings.html#std:setting-SECURE_REDIRECT_EXEMPT)设置中列出匹配那些url的正则表达式。 + + + +注意 + +如果你在负载均衡器或者反向代理服务器后面部署应用,而且Django不能辨别出什么时候一个请求是安全的,你可能需要设置[`SECURE_PROXY_SSL_HEADER`](settings.html#std:setting-SECURE_PROXY_SSL_HEADER)。 + + + + + + + + + +### 会话中间件 + + + +_class_ `SessionMiddleware`[[source]](../_modules/django/contrib/sessions/middleware.html#SessionMiddleware) + + + +开启会话支持。详见[_会话文档_](../topics/http/sessions.html)。 + + + + + +### 站点中间件 + + + +_class_ `CurrentSiteMiddleware`[[source]](../_modules/django/contrib/sites/middleware.html#CurrentSiteMiddleware) + + + +New in Django 1.7. + +向每个接收到的`HttpRequest`对象添加一个`site`属性,表示当前的站点。详见[_站点文档_](contrib/sites.html#site-middleware)。 + + + + + +### 认证中间件 + + + +_class_ `AuthenticationMiddleware`[[source]](../_modules/django/contrib/auth/middleware.html#AuthenticationMiddleware) + + + +向每个接收到的`HttpRequest`对象添加`user`属性,表示当前登录的用户。详见[_web请求中的认证_](../topics/auth/default.html#auth-web-requests)。 + + + +_class_ `RemoteUserMiddleware`[[source]](../_modules/django/contrib/auth/middleware.html#RemoteUserMiddleware) + + + +使用web服务器提供认证的中间件。详见[_使用REMOTE_USER进行认证_](../howto/auth-remote-user.html)。 + + + +_class_ `SessionAuthenticationMiddleware`[[source]](../_modules/django/contrib/auth/middleware.html#SessionAuthenticationMiddleware) + + + +New in Django 1.7. + +当用户修改密码的时候使用户的会话失效。详见[_密码更改时的会话失效_](../topics/auth/default.html#session-invalidation-on-password-change)。在[`MIDDLEWARE_CLASSES`](settings.html#std:setting-MIDDLEWARE_CLASSES)中,这个中间件必须出现在[`django.contrib.auth.middleware.AuthenticationMiddleware`](#django.contrib.auth.middleware.AuthenticationMiddleware "django.contrib.auth.middleware.AuthenticationMiddleware")之后。 + + + + + +### CSRF保护中间件 + + + +_class_ `CsrfViewMiddleware`[[source]](../_modules/django/middleware/csrf.html#CsrfViewMiddleware) + + + +添加跨站点请求伪造的保护,通过向POST表单添加一个隐藏的表单字段,并检查请求中是否有正确的值。详见[_CSRF保护文档_](csrf.html)。 + + + + + +### X-Frame-Options中间件 + + + +_class_ `XFrameOptionsMiddleware`[[source]](../_modules/django/middleware/clickjacking.html#XFrameOptionsMiddleware) + + + +[_通过X-Frame-Options协议头进行简单的点击劫持保护_](clickjacking.html)。 + + + + + + + +## 中间件的排序 + +下面是一些关于Django中间件排序的提示。 + +1. [`UpdateCacheMiddleware`](#django.middleware.cache.UpdateCacheMiddleware "django.middleware.cache.UpdateCacheMiddleware") + + 放在修改`大量`协议头的中间件(`SessionMiddleware`, `GZipMiddleware`, `LocaleMiddleware`)之前。 + +2. [`GZipMiddleware`](#django.middleware.gzip.GZipMiddleware "django.middleware.gzip.GZipMiddleware") + + 放在任何可能修改或使用响应消息体的中间件之前。 + + 放在`UpdateCacheMiddleware`之后:会修改`大量`的协议头。 + +3. [`ConditionalGetMiddleware`](#django.middleware.http.ConditionalGetMiddleware "django.middleware.http.ConditionalGetMiddleware") + + 放在`CommonMiddleware`之前:当[`USE_ETAGS`](settings.html#std:setting-USE_ETAGS) = `True`时会使用它的`Etag` 协议头。 + +4. [`SessionMiddleware`](#django.contrib.sessions.middleware.SessionMiddleware "django.contrib.sessions.middleware.SessionMiddleware") + + 放在`UpdateCacheMiddleware`之后:会修改 `大量`协议头。 + +5. [`LocaleMiddleware`](#django.middleware.locale.LocaleMiddleware "django.middleware.locale.LocaleMiddleware") + + 放在`SessionMiddleware`(由于使用会话数据)和?`CacheMiddleware`(由于要修改`大量`协议头)之后的最上面。 + +6. [`CommonMiddleware`](#django.middleware.common.CommonMiddleware "django.middleware.common.CommonMiddleware") + + 放在任何可能修改相应的中间件之前(因为它会生成`ETags`)。 + + 在`GZipMiddleware`之后,不会在压缩后的内容上再去生成`ETag`。 + + 尽可能放在靠上面的位置,因为[`APPEND_SLASH`](settings.html#std:setting-APPEND_SLASH)或者[`PREPEND_WWW`](settings.html#std:setting-PREPEND_WWW)设置为?`True`时会被重定向。 + +7. [`CsrfViewMiddleware`](#django.middleware.csrf.CsrfViewMiddleware "django.middleware.csrf.CsrfViewMiddleware") + + 放在任何假设CSRF攻击被处理的视图中间件之前。 + +8. [`AuthenticationMiddleware`](#django.contrib.auth.middleware.AuthenticationMiddleware "django.contrib.auth.middleware.AuthenticationMiddleware") + + 放在`SessionMiddleware`之后:因为它使用会话存储。 + +9. [`MessageMiddleware`](#django.contrib.messages.middleware.MessageMiddleware "django.contrib.messages.middleware.MessageMiddleware") + + 放在`SessionMiddleware`之后:会使用基于会话的存储。 + +10. [`FetchFromCacheMiddleware`](#django.middleware.cache.FetchFromCacheMiddleware "django.middleware.cache.FetchFromCacheMiddleware") + + 放在任何修改`大量`协议头的中间件之后:协议头被用来从缓存的哈希表中获取值。 + +11. [`FlatpageFallbackMiddleware`](contrib/flatpages.html#django.contrib.flatpages.middleware.FlatpageFallbackMiddleware "django.contrib.flatpages.middleware.FlatpageFallbackMiddleware") + + 应该放在最底下,因为它是中间件中的最后一手。 + +12. [`RedirectFallbackMiddleware`](contrib/redirects.html#django.contrib.redirects.middleware.RedirectFallbackMiddleware "django.contrib.redirects.middleware.RedirectFallbackMiddleware") + + 应该放在最底下,因为它是中间件中的最后一手。 + + + diff --git a/SUMMARY.md b/SUMMARY.md index 7566cdfae35285ff33fbb46b4b895616ff9bc052..03b5ca56d65fac4ac44ec0e8bf6752f57acbbc11 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -11,11 +11,11 @@ + [第6部分:静态文件](1_2_6_Part 6 Static files.md) + [高级教程](1_3.md) + [如何编写可重用的应用 ](1_3_1_How to write reusable apps.md) - + [为Django编写首个补丁](1_3_2_Writing your first patch for Django_.md) + + [为Django编写首个补丁](1_3_2_Writing your first patch for Django.md) + [模型层](2_The model layer.md) + [模型](2_1.md) + [模型语法](2_1_1_Model syntax.md) - + [字段类型]() + + [字段类型](2_1_2_Field types.md) + [元选项](2_1_3_Meta options.md) + [模型类](2_1_4_Model class.md) + [查询集](2_2.md) @@ -33,7 +33,7 @@ + [高级](2_5.md) + [管理器](2_5_1_Manager.md) + [原始的SQL查询](2_5_2_Performing raw SQL queries.md) - + [事务]() + + [事务](2_5_3_Transactions.md) + [聚合](2_5_4_Aggregation.md) + [自定义字段]() + [多数据库](2_5_6_Multiple databases.md) @@ -42,7 +42,7 @@ + [条件表达式](2_5_9_Conditional Expressions.md) + [数据库函数](2_5_10_Database Functions.md) + [其它](2_6.md) - + [支持的数据库]() + + [支持的数据库](2_6_1_Supported databases.md) + [遗留的数据库](2_6_2_Legacy databases.md) + [提供初始数据](2_6_3_Providing initial data.md) + [优化数据库访问](2_6_4_Optimize database access.md) @@ -55,8 +55,9 @@ + [装饰器](3_1_4_Decorators.md) + [参考](3_2.md) + [内建的视图](3_2_1_Built-in Views.md) - + [请求/响应 对象]() + + [请求/响应 对象](3_2_2_Request response objects.md) + [TemplateResponse 对象](3_2_3_TemplateResponse objects.md) + + [TemplateResponse 和SimpleTemplateResponse](3_2_4_TemplateResponse objects.md) + [文件上传](3_3.md) + [概览](3_3_1_Overview.md) + [File 对象](3_3_2_File objects.md) @@ -67,7 +68,7 @@ + [概览](3_4_1_Overview.md) + [内建显示视图](3_4_2_Built-in display views.md) + [内建编辑视图](3_4_3_Built-in editing views.md) - + [使用Mixin]() + + [使用Mixin](3_4_4_Using mixins.md) + [API参考](3_4_5_API reference.md) + [分类索引](3_4_6_Flattened index.md) + [高级](3_5.md) @@ -85,7 +86,7 @@ + [网页设计助手(已废弃)]() + [人性化](4_2_4_Humanization.md) + [面向程序员](4_3.md) - + [模板API]() + + [模板API](4_3_1_Template API.md) + [自定义标签和过滤器]() + [表单](5_Forms.md) + [基础](5_1.md) @@ -96,14 +97,14 @@ + [高级](5_2.md) + [模型表单]() + [整合媒体](5_2_2_Integrating media.md) - + [表单集]() - + [自定义验证]() + + [表单集](5_2_3_Formsets_.md) + + [自定义验证](5_2_4_Customizing validation.md) + [开发过程](6_The development process.md) + [设置](6_1.md) + [概览](6_1_1_Overview.md) + [完整列表设置]() + [应用程序](6_2.md) - + [概览]() + + [概览](6_2_Overview.md) + [异常](6_3.md) + [概览](6_3_Overview.md) + [django-admin 和 manage.py](6_4.md) @@ -130,6 +131,7 @@ + [点击劫持保护](8_3_Clickjacking protection.md) + [伪造跨站请求保护]() + [加密签名](8_5_Cryptographic signing.md) + + [安全中间件](8_6_Security Middleware.md) + [国际化和本地化](9_Internationalization and localization.md) + [概述](9_1_1_Overview.md) + [国际化]() @@ -149,7 +151,7 @@ + [发送邮件]() + [组织 feeds (RSS/Atom)]() + [分页](13_6_Pagination.md) - + [消息框架]() + + [消息框架](13_7_Messages framework_.md) + [序列化]() + [会话](13_9_1_Sessions.md) + [网站地图]() @@ -162,5 +164,5 @@ + [重定向](14_4_1_Redirects.md) + [信号](14_5_Signals.md) + [系统检查框架](14_6_System check framework.md) - + [网站框架]() + + [网站框架](14_7_The sites framework_.md) + [Django中的Unicode编码]()