Removing the magic

"magic-removal" 分支力图扫除 Django 代码库中的近两年来积攒的缺陷. 大部分变化集中在数据库 API 及移除一些不必要的 magic, 其它一些变化主要在于提高了框架的简单性和可用性.

这些变化将被整合在下一个 Django 发行版中, 也就是0.92.

本文档说明了该分枝带来的变化.

TOC(inline, RemovingTheMagic)

如何得到这个分枝

欢迎试用! 通过 Subversion 你可以得到该分支. 地址是: http://code.djangoproject.com/svn/django/branches/magic-removal .

在一个机器上同时运行两个版本的 Django

Here's one way to use Django's trunk and magic-removal branch on the same machine. This assumes Django's trunk (or a release such as 0.90 or 0.91) is installed:

# Get the magic-removal code somewhere on your filesystem. In this example, we use /home/python/django.
$ cd /home/python/django
$ svn co http://code.djangoproject.com/svn/django/branches/magic-removal

# This will have created a "magic-removal" directory.

# Whenever you want to use magic-removal, set the environment variable {{{PYTHONPATH

to the directory containing magic-removal.export PYTHONPATH=/home/python/django/magic-removal }}}

概述

magic-removal 中最大的改变是:

Status

希望这个消息不会让你昏倒, Django 仍在紧锣密鼓的开发当中.

你需要做的数据库改变

要升级你以前安装的 Django , 你需要对数据库做一些改变. 特别是, 当你数据库已经有了很多数据时.

对核心数据表进行了重新命名

我们对一系列核心 Django 表进行了重命名. 要更新 MySQL 和 SQLite, 执行这些SQL语句:

ALTER TABLE auth_groups RENAME TO auth_group;
ALTER TABLE auth_groups_permissions RENAME TO auth_group_permissions;
ALTER TABLE auth_messages RENAME TO auth_message;
ALTER TABLE auth_permissions RENAME TO auth_permission;
ALTER TABLE auth_users RENAME TO auth_user;
ALTER TABLE auth_users_groups RENAME TO auth_user_groups;
ALTER TABLE auth_users_user_permissions RENAME TO auth_user_user_permissions;
ALTER TABLE content_types RENAME TO django_content_type;
ALTER TABLE core_sessions RENAME TO django_session;
ALTER TABLE django_flatpages RENAME TO django_flatpage;
ALTER TABLE django_flatpages_sites RENAME TO django_flatpage_sites;
ALTER TABLE django_redirects RENAME TO django_redirect;
ALTER TABLE sites RENAME TO django_site;
DROP TABLE packages;
ALTER TABLE django_content_type rename package to app_label;
ALTER TABLE django_content_type rename python_module_name to model;

PostgreSQL 则不同, 要更新 PostgreSQL, 执行下列 SQL 语句:

BEGIN;

ALTER TABLE auth_groups RENAME TO auth_group;
ALTER TABLE auth_groups_id_seq RENAME TO auth_group_id_seq;
ALTER TABLE auth_group ALTER COLUMN id DROP DEFAULT;
ALTER TABLE auth_group ALTER COLUMN id SET DEFAULT nextval('public.auth_group_id_seq'::text);

ALTER TABLE auth_groups_permissions RENAME TO auth_group_permissions;
ALTER TABLE auth_groups_permissions_id_seq RENAME TO auth_group_permissions_id_seq;
ALTER TABLE auth_group_permissions ALTER COLUMN id DROP DEFAULT;
ALTER TABLE auth_group_permissions ALTER COLUMN id SET DEFAULT nextval('public.auth_group_permissions_id_seq'::text);

ALTER TABLE auth_messages RENAME TO auth_message;
ALTER TABLE auth_messages_id_seq RENAME TO auth_message_id_seq;
ALTER TABLE auth_message ALTER COLUMN id DROP DEFAULT;
ALTER TABLE auth_message ALTER COLUMN id SET DEFAULT nextval('public.auth_message_id_seq'::text);

ALTER TABLE auth_permissions RENAME TO auth_permission;
ALTER TABLE auth_permissions_id_seq RENAME TO auth_permission_id_seq;
ALTER TABLE auth_permission ALTER COLUMN id DROP DEFAULT;
ALTER TABLE auth_permission ALTER COLUMN id SET DEFAULT nextval('public.auth_permission_id_seq'::text);

ALTER TABLE auth_users RENAME TO auth_user;
ALTER TABLE auth_users_id_seq RENAME TO auth_user_id_seq;
ALTER TABLE auth_user ALTER COLUMN id DROP DEFAULT;
ALTER TABLE auth_user ALTER COLUMN id SET DEFAULT nextval('public.auth_user_id_seq'::text);

ALTER TABLE auth_users_groups RENAME TO auth_user_groups;
ALTER TABLE auth_users_groups_id_seq RENAME TO auth_user_groups_id_seq;
ALTER TABLE auth_user_groups ALTER COLUMN id DROP DEFAULT;
ALTER TABLE auth_user_groups ALTER COLUMN id SET DEFAULT nextval('public.auth_user_groups_id_seq'::text);

ALTER TABLE auth_users_user_permissions RENAME TO auth_user_user_permissions;
ALTER TABLE auth_users_user_permissions_id_seq RENAME TO auth_user_user_permissions_id_seq;
ALTER TABLE auth_user_user_permissions ALTER COLUMN id DROP DEFAULT;
ALTER TABLE auth_user_user_permissions ALTER COLUMN id SET DEFAULT nextval('public.auth_user_user_permissions_id_seq'::text);

ALTER TABLE content_types RENAME TO django_content_type;
ALTER TABLE content_types_id_seq RENAME TO django_content_type_id_seq;
ALTER TABLE django_content_type ALTER COLUMN id DROP DEFAULT;
ALTER TABLE django_content_type ALTER COLUMN id SET DEFAULT nextval('public.django_content_type_id_seq'::text);

ALTER TABLE core_sessions RENAME TO django_session;

ALTER TABLE django_flatpages RENAME TO django_flatpage;
ALTER TABLE django_flatpages_id_seq RENAME TO django_flatpage_id_seq;
ALTER TABLE django_flatpage ALTER COLUMN id DROP DEFAULT;
ALTER TABLE django_flatpage ALTER COLUMN id SET DEFAULT nextval('public.django_flatpage_id_seq'::text);

ALTER TABLE django_flatpages_sites RENAME TO django_flatpage_sites;
ALTER TABLE django_flatpages_sites_id_seq RENAME TO django_flatpage_sites_id_seq;
ALTER TABLE django_flatpage_sites ALTER COLUMN id DROP DEFAULT;
ALTER TABLE django_flatpage_sites ALTER COLUMN id SET DEFAULT nextval('public.django_flatpage_sites_id_seq'::text);

ALTER TABLE django_redirects RENAME TO django_redirect;
ALTER TABLE django_redirects_id_seq RENAME TO django_redirect_id_seq;
ALTER TABLE django_redirect ALTER COLUMN id DROP DEFAULT;
ALTER TABLE django_redirect ALTER COLUMN id SET DEFAULT nextval('public.django_redirect_id_seq'::text);

ALTER TABLE auth_permission DROP COLUMN package;
ALTER TABLE django_content_type DROP CONSTRAINT "$1";

DROP TABLE packages;

ALTER TABLE sites RENAME TO django_site;
ALTER TABLE sites_id_seq RENAME TO django_site_id_seq;
ALTER TABLE django_site ALTER COLUMN id DROP DEFAULT;
ALTER TABLE django_site ALTER COLUMN id SET DEFAULT nextval('public.django_site_id_seq'::text);

ALTER TABLE django_content_type rename package to app_label;
ALTER TABLE django_content_type rename python_module_name to model;

COMMIT;

Database 表命名方式做了改动

以前 Database 表的名字通过连接 app_labelmodule_name 来生成. 比如: polls_polls.

由于现在已经没有了 module_name 的概念, 数据表的名字现在被设计成通过连接 app_label 和 model 类的名字(小写)来自动生成.比如: polls_poll.

和以前一样, 这种行为可以通过在你的 model 中的内嵌类 class Meta 的指定db_table属性来改变..

你要么显式的指定 db_table 以适应新的命名方式, 也可以直接在数据库中对数据表进行改名.我们建议设置 db_table, 因为这个更容易一些.

你需要做的代码更改

Model 类 及 Field 类 重命名/重定位

导入 models 由原来的 from django.core.meta 变更为 from django.db.models.

   1 from django.db import models
   2 
   3 class Person(models.Model):
   4     first_name = models.CharField(maxlength=30)
   5     last_name = models.CharField(maxlength=30)

直接访问你的 model 类, 而不是通过那个令人费解的中间模块(magic modules)

直接从定义model类的模块导入 model 类, 去掉了 django.models.*.

   1 from myproject.people.models import Person
   2 p = Person(first_name='John', last_name='Smith')
   3 p.save()

名字空间简化

django.utils.httpwrappers 变更为 django.http.

django.core.exceptions.Http404 变更为 django.http.Http404.

django.core.template 变更为 django.template.

django.core.formfields 变更为 django.forms.

django.core.extensions 变更为 django.shortcuts.

django.core.extensions.DjangoContext 变更为 django.template.RequestContext.

需要从你的settings文件中的 TEMPLATE_LOADERS 设置值中的将 ".core" 删除, 旧代码:

   1 TEMPLATE_LOADERS = (
   2     'django.core.template.loaders.filesystem.load_template_source',
   3     'django.core.template.loaders.app_directories.load_template_source',
   4 #    'django.core.template.loaders.eggs.load_template_source',
   5 )

新代码:

   1 TEMPLATE_LOADERS = (
   2     'django.template.loaders.filesystem.load_template_source',
   3     'django.template.loaders.app_directories.load_template_source',
   4 #    'django.template.loaders.eggs.load_template_source',
   5 )

自定义模板标签也需要做一个类似的修改.旧代码:

   1 from django.core import template
   2 register = template.Library()

新代码:

   1 from django.template import Library
   2 register = Library()

"auth" 和 "core" models 被分开且被移动到 django.contrib, 具体如下:

如果你正使用session的话, Session 中间件从 django.middleware.sessions.SessionMiddleware 被移动到 django.contrib.sessions.middleware.SessionMiddleware. 请确保更新你的 MIDDLEWARE_CLASSES 设置.

同样的, Session model 由 django/models/core.py 被移动到 django/contrib/sessions/models.py. 如果你正使用 Session model ,一定要注意这个改变.

'packages' 模块不再存在

Packages 不再存在 (它们实在多余). 如果已经完成依据 content-types 或 permissions 查询功能,你需要稍微修改一下你的代码:

旧代码

新代码

contenttypes.get_list(package__label__exact='foo')

ContentType.objects.filter(package__exact='foo')

permissions.get_list(package__label__exact='foo')

Permission.objects.filter(package__exact='foo')

model 语法的变更

Moved admin options to 'class Admin'

不要再从 class META 定义 admin=meta.Admin 参数 , 所有的管理选项都放到了 class Admin 内.

旧代码:

   1 class Person(meta.Model):
   2     first_name = meta.CharField(maxlength=30)
   3     last_name = meta.CharField(maxlength=30)
   4     class META:
   5         admin = meta.Admin(
   6             list_display = ('first_name', 'last_name')
   7         )

新代码:

   1 class Person(models.Model):
   2     first_name = models.CharField(maxlength=30)
   3     last_name = models.CharField(maxlength=30)
   4     class Admin:
   5         list_display = ('first_name', 'last_name')

如果你使用 admin 界面却没有指定任何 admin 选项, 只要在那个内嵌类中放一个 pass 语句.

旧代码:

   1 class Person(meta.Model):
   2     first_name = meta.CharField(maxlength=30)
   3     last_name = meta.CharField(maxlength=30)
   4     class META:
   5         admin = meta.Admin()

新代码:

   1 class Person(models.Model):
   2     first_name = models.CharField(maxlength=30)
   3     last_name = models.CharField(maxlength=30)
   4     class Admin:
   5         pass

数据库连接重命名/重定位

对于任何使用原始数据库连接的代码, 使用 django.db.connection ,不再使用 django.core.db.db.

旧代码:

   1 from django.core.db import db
   2 cursor = db.cursor()

新代码:

   1 from django.db import connection
   2 cursor = connection.cursor()

如果你需要那些 Backend-specific 函数, 他们现在位于 django.db.backend.

旧代码:

   1 from django.core import db
   2 db.quote_name('foo')

新代码:

   1 from django.db import backend
   2 backend.quote_name('foo')

同样的, 不同的 backend 功能已经分到三个不同的模块中 -- base.py, creation.pyintrospection.py. 之所以这样修改是为了改善性能和节省内存, 这样一来, Django 日常使用就不必将内省模块载入内存.

Model 方法不再自动访问 datetime 和 db 模块

以前, 每个 model 方法都能访问 datetime 模块及 db 变量(表示当前的数据库连接). 现在你必须显式的导入它们.

旧代码:

   1     def some_method(self):
   2         print datetime.datetime.now()
   3         cursor = db.cursor()
   4         cursor.execute("UPDATE something;")

新代码:

   1 import datetime
   2 from django.db import connection
   3 
   4 # ...
   5 
   6     def some_method(self):
   7         print datetime.datetime.now()
   8         cursor = connection.cursor()
   9         cursor.execute("UPDATE something;")

Descriptor fields

所有 "table-level" 的函数 -- (在表范围内得到数据记录的方式胜于 performing instance-specific tasks 的方式) -- 现在通过一个 model 类的 objects 属性访问.它们不是一个 model 实例对象的直接方法, 因为我们希望保持 "表范围" 和 "行范围" 两个不同的名字空间.

一个 model 类的 objects 属性是一个 django.db.models.manager.Manager 类的实例. 一个管理器有一系列方法,所有这些方法都返回一个 QuerySet 实例.

每个 QuerySet 都有下列方法, 返回一个经过适当处理的当前结果集的克隆:

下面是一些例子,使用下面的 models:

   1 class Reporter(models.Model):
   2     fname = models.CharField(maxlength=30)
   3     lname = models.CharField(maxlength=30)
   4 
   5 class Site(models.Model):
   6     name = models.CharField(maxlength=20)
   7 
   8 class Article(models.Model):
   9     headline = models.CharField(maxlength=50)
  10     reporter = models.ForeignKey(Reporter)
  11     pub_date = models.DateField()
  12     sites = models.ManyToManyField(Site)

旧的语法

新的语法

reporters.get_list()

Reporter.objects.all()

reporters.get_list(fname__exact='John')

Reporter.objects.filter(fname='John')

reporters.get_list(order_by=('-lname', 'fname'))

Reporter.objects.order_by('-lname', 'fname')

reporters.get_list(fname__exact='John', order_by=('lname',))

Reporter.objects.filter(fname='John').order_by('lname')

reporters.get_object(pk=3)

Reporter.objects.get(pk=3)

reporters.get_object(complex=(Q(...)|Q(...)))

Reporter.objects.get(Q(...)|Q(...))

reporters.get_object(fname__contains='John')

Reporter.objects.get(fname__contains='John')

reporters.get_list(fname__ne='John')

Reporter.objects.exclude(fname='John') (note that ne is no longer a valid lookup type)

(not previously possible)

Reporter.objects.exclude(fname__contains='n')

reporters.get_list(distinct=True)

Reporter.objects.distinct()

reporters.get_list(offset=10, limit=5)

Reporter.objects.all()[10:15]

reporters.get_values()

Reporter.objects.values()

reporters.get_in_bulk([1, 2])

Reporter.objects.in_bulk([1, 2])

reporters.get_in_bulk([1, 2], fname__exact='John')

Reporter.objects.filter(fname='John').in_bulk([1, 2])

Date lookup

articles.get_pub_date_list('year')

Article.objects.dates('pub_date', 'year')

Latest-object lookup

articles.get_latest() (required get_latest_by in model)

Article.objects.latest() (with get_latest_by in model)

(Not previously possible)

Article.objects.latest('pub_date') # Latest by pub_date (overrides get_latest_by field in model)

Many-to-one related lookup

article_obj.reporter_id

article_obj.reporter.id

article_obj.get_reporter()

article_obj.reporter

reporter_obj.get_article_list()

reporter_obj.article_set.all()

reporter_obj.get_article_list(headline__exact='Hello')

reporter_obj.article_set.filter(headline='Hello')

reporter_obj.get_article_count()

reporter_obj.article_set.count()

reporter_obj.add_article(headline='Foo')

reporter_obj.article_set.add(headline='Foo')

(Alternate syntax)

reporter_obj.article_set.add(article_obj)

("values" lookup, etc., not previously possible)

reporter_obj.article_set.values()

Many-to-many related lookup

article_obj.get_site_list()

article_obj.sites.all()

article_obj.set_sites([s1.id, s2.id])

article_obj.sites.clear(); article_obj.sites.add(s1); article_obj.sites.add(s2)

article_obj.set_sites([s1.id]) # deletion

article_obj.sites.remove(s2)

site_obj.get_reporter_list()

site_obj.reporter_set.all()

注意关联对象查询使用关联对象的默认管理器, 这意味着访问关联对象的API与通过管理器访问关联对象的API已经完全一致.

还要注意不能从以下实例中访问管理器:

   1 p = Person.objects.get(pk=1)
   2 p.objects.all() # Raises AttributeError

覆盖默认的管理器名称: ('objects')

如果一个 model 已经有一个 objects 属性, 你需要给 objects 管理器指定一个另外的名字.

   1 class Person(models.Model):
   2     first_name = models.CharField(maxlength=30)
   3     last_name = models.CharField(maxlength=30)
   4     objects = models.TextField()
   5     people = models.Manager()
   6 
   7 p = Person(first_name='Mary', last_name='Jones', objects='Hello there.')
   8 p.save()
   9 p.objects == 'Hello there.'
  10 Person.people.all()

自定义管理器及多管理器

只要你愿意,你可以创建任意多个管理器. 在必要时(比如在 admin 中), Django 会按照管理器定义顺序使用第一个定义的管理器.

如果你定义了至少一个自定义管理器, 就不能使用默认的 "objects" 管理器.

   1 class Person(models.Model):
   2     first_name = models.CharField(maxlength=30)
   3     last_name = models.CharField(maxlength=30)
   4     people = models.Manager()
   5     fun_people = SomeOtherManager()

如果一个管理器需要访问它的关联 model 类, 应该使用 self.model. 例子:

   1 class PersonManager(models.Manager):
   2     def get_fun_person(self):
   3         try:
   4             return self.get(fun=True)
   5         except self.model.DoesNotExist:
   6             print "Doesn't exist."

让 admin 使用自定义管理器

有时你需要使用一个不同的管理器以用于 admin 的特定显示(比如在 admin 中只显示符合某些条件的对象). 你可以通过在Admin 声明中定义 manager 选项来实现:

   1 class LivingPeopleManager(models.Manager):
   2     def get_query_set(self):
   3         return super(LivingPeopleManager, self).get_query_set().filter(is_alive=True) 
   4 
   5 class Person(models.Model):
   6     name = models.CharField(maxlength=50)
   7     is_alive = models.BooleanField()
   8     
   9     class Admin:
  10         manager = LivingPeopleManager()

(参阅 "覆盖默认的 QuerySets" 了解 QuerySets 的更多信息)

添加了一个更强大的覆盖 model 方法的方式, 删除了硬编码的 _pre_save(), _post_save(), 等等.

Proper subclassing of methods now works, 你可以子类化自动的 save()delete() 方法. 删除了 _pre_save(), _post_save(), _pre_delete()_post_delete() 钩子. 例子:

   1 class Person(models.Model):
   2     first_name = models.CharField(maxlength=30)
   3     last_name = models.CharField(maxlength=30)
   4 
   5     def save(self):
   6         self.do_something()
   7         super(Person, self).save() # Call the "real" save() method.
   8         self.do_something_else()

你甚至可以跳过保存这一步 (如同 requested #1014).

   1 class Person(models.Model):
   2     first_name = models.CharField(maxlength=30)
   3     last_name = models.CharField(maxlength=30)
   4 
   5     def save(self):
   6         if datetime.date.today() > datetime.date(2005, 1, 1):
   7             super(Person, self).save() # Call the "real" save() method.
   8         else:
   9             # Don't save.
  10             pass

改名! DoesNotExist 异常

不再是 people.PersonDoesNotExist, 现在是 Person.DoesNotExist.

旧代码:

   1 from django.models.myapp import people
   2 try:
   3     people.get_object(pk=1)
   4 except people.PersonDoesNotExist:
   5     print "Not there"

新代码:

   1 from path.to.myapp.models import Person
   2 try:
   3     Person.objects.get(pk=1)
   4 except Person.DoesNotExist:
   5     print "Not there"

admin URLconf path 变得更短

URLconf 中的 include 变得更短.

get_object_or_404 及 get_list_or_404 现在接受 model 类做为参数,而不再是模块

旧代码:

   1 get_object_or_404(polls, pk=1)

新代码:

   1 get_object_or_404(Poll, pk=1)

传递给 generic views 的参数做了改变

由于不再有 module_name 的概念, 传递给 generic views 的 "info_dicts" 不再接受 "app_label""module_name" 参数而是使用新的 "queryset" 参数(一个 QuerySet 的实例).

旧代码:

   1 info_dict = {
   2     'app_label': 'blog',
   3     'module_name': 'entries'
   4 }

新代码:

   1 from myproject.blog.models import Entry
   2 info_dict = {'queryset': Entry.objects.all()}

generic views 中的模板名字改变

由于不再有 module_name 的概念, generic views 不再创建基于 "module_name" 的模板.无论何处用到了 module_name, 现在他们都变成了 model_name, 一个 model 名字的小写版本.

注意在这里仍然保留了 app_label.

下面的例子假定 models 定义在 myproject/blog/models.py.

将设置文件 settings 移到一个实例中

Settings 从一个专门的模块django.conf.settings 中被移动到一个django.conf 模块实例中.现在你需要导入 settings 对象并以该实例的一个属性的方式引用 settings .

Django machinery 封装可以通过变更 settings 实例(带有一个访问 per-thread 或 per-location 全局变量代理 proxy 实例使用这一功能.

删除 SilentVariableFailure 异常

旧的行为: 模板中子类django.core.template.SilentVariableFailure的任何异常将静默.

新的行为: 模板系统中任何拥有 silent_variable_failure 属性的异常将静默. django.core.template.SilentVariableFailure不再存在.

request.user 现在设置成通过 middleware 访问

过去常常在 mod_python 和 wsgi 处理器中设置. 你需要在你的 settings.py 文件中的MIDDLEWARE_CLASSES 里的"django.contrib.sessions.middleware.SessionMiddleware" 之后 的某处 添加 "django.contrib.auth.middleware.AuthenticationMiddleware" . 否则访问 request.user 会引发 AttributeError.

Authentication 做了整理,变得更加统一

以前, 授权系统至少要在四个不同的位置进行设置. 现在所有的东西都被整理到 django.contrib.auth 中. 如下:

如果你用到了这些类或函数, 你需要根据上文更新代码.

操纵器界面的改动

旧的:

   1 from django.core import formfields
   2 from django.models.PROJECT import MODELMODULE
   3 ...
   4 manipulator = MODELMODULE.AddManipulator()
   5 ...
   6 form = formfields.FormWrapper(manipulator, new_data, errors) 

新的:

   1 from django.forms import FormWrapper
   2 from PROJECT.APP.models import MODELNAME 
   3 ...
   4 manipulator = MODELNAME.AddManipulator()
   5 ...
   6 form = FormWrapper(manipulator, new_data, errors) 

django.VERSION

变量 django.VERSION 由一个四元素 tuple 变更为一个三元素 tuple.

Old: VERSION = (0, 9, 1, 'magic-removal')

New: VERSION = (0, 91, 'magic-removal')

你可以使用的新功能

Models 支持属性

与以前不同, 现在models也支持属性了.

   1 from django.db import models
   2 
   3 class Person(models.Model):
   4     first_name = models.CharField(maxlength=30)
   5     last_name = models.CharField(maxlength=30)
   6 
   7     def _get_full_name(self):
   8         return "%s %s" % (self.first_name, self.last_name)
   9     full_name = property(_get_full_name)

你能覆盖默认的 QuerySets

你能指定一个管理者使用的默认 QuerySet (参阅上文的 "Descriptor fields"). 例子:

   1 class PublishedBookManager(models.Manager):
   2     def get_query_set(self):
   3         return super(PublishedBookManager, self).get_query_set().filter(is_published=True)
   4 
   5 class Book(models.Model):
   6     title = models.CharField(maxlength=50)
   7     author = models.CharField(maxlength=30)
   8     is_published = models.BooleanField()
   9     published_objects = PublishedBookManager()

团队仍然需要做的

删除自动的 manipulators, 有利于 validation-aware models

状态: 尚未完工

改变子类语法

状态: 沿未完工

参阅 ModelInheritance

RemovingTheMagic (last edited 2009-12-25 07:10:31 by localhost)