把一个Django应用从1.4升级到1.11
2017-10-27 18:23:28 +08 字数:4292 标签: Django升级的动机 ¶
基本上,一张图就可以解释升级Django的全部动机。
由上图可见,近期LTS(long-term support)的版本有1.8、1.11、2.2三个。 另外,Django官网的文档已经不再支持1.7以前的了。 所以,无论从安全、维护、还是开发的角度考虑,都至少要把Django升级到这三个版本之一。
2017年来研究这个问题很奇怪。
正如孤在《Django项目从1.3.7升级到1.4.22》中所说, 在工作中,什么奇怪的项目都会遇到。 当这些个奇怪的项目,还有维护与再开发的需求时,这种连升N级的诡异需求就产生了。
孤有一种攒了一个多月的作业,到暑假的最后一天来写的感觉,仿佛少年。
1.4升级到1.5 ¶
pip uninstall -y Django
pip install 'Django<1.6'
模板中的url问题 ¶
‘url’ requires a non-empty first argument. The syntax changed in Django 1.5
这是由于原先的url
使用方式是直接接上函数名。
<a href="{% url foo.view.hello sth.id %}">{{ sth.name }}</a>
而新的方式,需要把它变成字符串参数。
<a href="{% url "foo.view.hello" sth.id %}">{{ sth.name }}</a>
原先的方式,在1.3版本里,解释时会出一些问题。
foo.view.hello
到底是一个view的函数名,还是一个模板的变量名,不能很好地区分。
因此,引入了一个{% load url from future %}
,支持新的使用方式。
详见Django 1.5 release notes。
如果原先用的是{% load url from future %}
,那么改成{% load url %}
即可。
如果原先用的是旧的方式,那么需要给函数名加上双引号。
find . -name '*.html' | xargs sed -i 's/{% url \([^" >][^ >]*\)/{% url "\1"/g'
参考《stackoverflow.com/questions/14882491》。
1.5升级到1.6 ¶
pip uninstall -y Django
pip install 'Django<1.7'
1.6版本对SQLite数据库、ORM方面,做了很多改动。 详见《Django 1.6 release notes》。
启动问题 ¶
ImportError: No module named markup
在1.3、1.4里有django.contrib.markup
这个App。
它在1.5里被废弃,在1.6中被加速删除。
详见《#18054 (deprecate contrib.markup) – Django》。
最根本的解决办法,就是不要去使用它。 但是在已经大量使用它的老旧项目中,没办法不用。 只能自行安装django-markup-deprecated,再顶一顶。
pip install django-markup-deprecated
find . -name '*.py' | xargs sed -i 's/django.contrib.markup/markup_deprecated/g'
在需要包名的地方,用markup_deprecated
代替django.contrib.markup
,模板文件中的{% load markup %}
可以不变。
数据库问题 ¶
TransactionManagementError at /admin/ ¶
Your database backend doesn’t behave properly when autocommit is off. Turn it on before using ‘atomic’.
在settings.py
中,数据库的配置位置,添加一行'ATOMIC_REQUESTS': True
就可解决。
详见《stackoverflow.com/questions/20039250》。
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'ATOMIC_REQUESTS': True,
...
}
}
django.conf.urls.defaults ¶
No module named defaults
在1.6中,早已被废弃的django.conf.urls.defaults
被正式删除了。
相关import,应替换为django.conf.urls
。
find . -name '*.py' | xargs sed -i 's/django.conf.urls.defaults/django.conf.urls/g'
1.6升级到1.7 ¶
pip uninstall -y Django
pip install 'Django<1.8'
参考:《Django 1.7 release notes | Django documentation》。
System check identified some issues ¶
System check identified some issues: … System check identified 15 issues (0 silenced).
You have unapplied migrations; your app may not work properly until they are applied. Run ‘python manage.py migrate’ to apply them.
1_6.W002 ¶
14个issue都是一样的内容:
(1_6.W002) BooleanField does not have a default value. HINT: Django 1.6 changed the default value of BooleanField from False to None. See https://docs.djangoproject.com/en/1.6/ref/models/fields/#booleanfield for more information.
当然,这个网址已经无法访问了。
前面说过,Django已经不再支持1.7以前的文档。
把URL中的1.6
换成1.11
,即可查看到最新文档。
这类issue的意思是,原先默认保存为False
的BooleanField
,今后会默认保存None
。
这种事可大可小,问题可能会出在读取新保存的数据库字段的时候。
只要修改代码中所有无默认值的BooleanField
,把默认值设为False
,即可消除所有警告,并且保持行为的一致。
1_6.W001 ¶
还有一个issue是关于测试的。
?: (1_6.W001) Some project unittests may not execute as expected. HINT: Django 1.6 introduced a new default test runner. It looks like this project was generated using Django 1.5 or earlier. You should ensure your tests are all running & behaving as expected. See https://docs.djangoproject.com/en/dev/releases/1.6/#new-test-runner for more information.
然而,一个在2017年把项目移交给我时,仍然保持在1.3版本的老外,有可能会写测试吗?
如果仅仅是消除警告,可以参考《stackoverflow.com/questions/25871261》。
添加以下代码进settings.py
。
TEST_RUNNER = 'django.test.runner.DiscoverRunner'
RemovedInDjango18Warning ¶
除了已经改动的警告,还有未来1.8版本的警告。 如果孤的目标是升级到1.7,这类问题倒是不用处理。然而……
RemovedInDjango18Warning: Creating a ModelForm without either the ‘fields’ attribute or the ‘exclude’ attribute is deprecated - form ComponentAdminForm needs updating
参考《stackoverflow.com/questions/28306288》,需要在类定义位置加上fields = '__all__'
。
例如:
class ExampleForm(forms.ModelForm):
class Meta:
model = Something
fields = '__all__'
MessageFailure at /admin/auth/group/add/ ¶
MessageFailure at /admin/auth/group/add/ You cannot add messages without installing django.contrib.messages.middleware.MessageMiddleware
与此同时,还有以下两个RemovedInDjango18Warning。
RemovedInDjango18Warning: XViewMiddleware has been moved to django.contrib.admindocs.middleware. RemovedInDjango18Warning: TransactionMiddleware is deprecated in favor of ATOMIC_REQUESTS.
参考《stackoverflow.com/questions/15852317》,需要在settings.py
中替换相关部件。
MIDDLEWARE_CLASSES = (
...
- 'django.middleware.doc.XViewMiddleware',
- 'django.middleware.transaction.TransactionMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
)
INSTALLED_APPS = (
...
+ 'django.contrib.messages',
)
unapplied migrations ¶
You have unapplied migrations; your app may not work properly until they are applied. Run ‘python manage.py migrate’ to apply them.
代码不需要改动,而数据库结构却需要变化。
在1.7,原先syncdb
的机制,被migrate
和makemigrations
所替换。
需要执行python manage.py migrate
,确保一些内置App的数据库结构被更新。
如果有交互式的提问,那么最好选no
。
因为,在没有测试的情况下,数据库结构变化这种事,还是一动不如一静。
The following content types are stale and need to be deleted:
auth | message
Any objects related to these content types by a foreign key will also
be deleted. Are you sure you want to delete these content types?
If you're unsure, answer 'no'.
Type 'yes' to continue, or 'no' to cancel:
自从有了这个机制,Django的自带App,对数据库结构的改动,愈发地肆无忌惮。
几乎每一个大版本,都要来一次python manage.py migrate
。
1.7升级到1.8 ¶
pip uninstall -y Django
pip install 'Django<1.9'
参考:《Django 1.8 release notes | Django documentation》。
RemovedInDjango19Warning ¶
RemovedInDjango19Warning: The django.forms.util module has been renamed. Use django.forms.utils instead.
from django.forms.util import ErrorList
简单地说,就是多个s
。
这样的改动,其实挺任性。
不过,也能看出Django的开发者们对代码优化的执着。
-from django.forms.util import ErrorList
+from django.forms.utils import ErrorList
1_8.W001 ¶
(1_8.W001) The standalone TEMPLATE_* settings were deprecated in Django 1.8 and the TEMPLATES dictionary takes precedence. You must put the values of the following settings into your default TEMPLATES dict: TEMPLATE_DIRS, TEMPLATE_DEBUG.
在1.8版本中,TEMPLATE_*
的设置方式被废弃,改为一个汇总的TEMPLATES
。
参考《Settings#TEMPLATES》。
TEMPLATE_DEBUG = DEBUG
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
)
TEMPLATE_DIRS = (
os.path.join(BASE_DIR, 'templates'),
)
TEMPLATE_CONTEXT_PROCESSORS = (
"django.contrib.auth.context_processors.auth",
"django.core.context_processors.debug",
"django.core.context_processors.i18n",
"django.core.context_processors.media",
"django.core.context_processors.request",
)
以上settings.py
中的配置,应替换为:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'APP_DIRS': False,
'DIRS': [
os.path.join(BASE_DIR, 'templates'),
],
'OPTIONS': {
'debug': DEBUG,
'context_processors': (
'django.contrib.auth.context_processors.auth',
'django.template.context_processors.debug',
'django.template.context_processors.i18n',
'django.template.context_processors.media',
'django.template.context_processors.static',
'django.template.context_processors.tz',
'django.template.context_processors.request',
'django.contrib.messages.context_processors.messages',
),
'loaders': (
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
)
}
},
]
'APP_DIRS'
不能为True
,否则会出现以下错误。
app_dirs must not be set when loaders is defined.
ImportError: No module named doc ¶
ImportError: No module named doc
关于这个问题,全网搜索都没有现成的答案。 其实,它是一个中间件改名导致的。
- 'django.middleware.doc.XViewMiddleware',
+ 'django.contrib.admindocs.middleware.XViewMiddleware',
ImportError: No module named transaction
这个,是中间件'django.middleware.transaction.TransactionMiddleware'
导致的,直接可以删除。
详见《#deprecation-removed-in-1.8》。
其实,如果在升级到1.7时把他俩删掉,就不会出问题。
‘module’ object has no attribute ‘commit_on_success’ ¶
‘module’ object has no attribute ‘commit_on_success’
在1.7时,其实就会发现警告。
RemovedInDjango18Warning: commit_on_success is deprecated in favor of atomic.
@transaction.commit_on_success
这需要把所有的@transaction.commit_on_success
,都改为@transaction.atomic
。
参考《stackoverflow.com/questions/21861207》。
find . -name '*.py' | xargs sed -i 's/@transaction.commit_on_success/@transaction.atomic/g'
1.8升级到1.9 ¶
pip uninstall -y Django
pip install 'Django<1.10'
RemovedInDjango110Warning ¶
共有以下两类警告,都是django.conf.urls
接口变化所致。
RemovedInDjango110Warning: django.conf.urls.patterns() is deprecated and will be removed in Django 1.10. Update your urlpatterns to be a list of django.conf.urls.url() instances instead.
RemovedInDjango110Warning: Support for string view arguments to url() is deprecated and will be removed in Django 1.10
这个需要对所有urls.py
文件,进行较大改动。
原先使用patterns()
实现的URL指定,现在需要改成url
列表。
urlpatterns = patterns(
(r'^admin/', include(admin.site.urls)),
)
以上样例,需要改为:
urlpatterns = [
url(r'^admin/', admin.site.urls),
]
1.9升级到1.10 ¶
pip uninstall -y Django
pip install 'Django<1.11'
参考《Django 1.10 release notes》。
NoReverseMatch ¶
NoReverseMatch at /accounts/login/ ¶
Reverse for ‘django.contrib.auth.views.login’ with arguments ‘()’ and keyword arguments ‘{}’ not found. 0 pattern(s) tried: []
登录时,遇到以上信息。
这个问题其实是受url
的改动影响,需要改动的地方非常多。
以下引用,来自《Django 1.10 release notes》。
- The
LOGOUT_URL
setting is removed as Django hasn’t made use of it since pre-1.0. If you use it in your project, you can add it to your project’s settings. The default value was'/accounts/logout/'
.- The ability to
reverse()
URLs using a dotted Python path is removed.- The ability to use a dotted Python path for the
LOGIN_URL
andLOGIN_REDIRECT_URL
settings is removed.- Support for string
view
arguments tourl()
is removed.
这部分,其实是在1.9的RemovedInDjango110Warning中有提到的。 只是,那时不需要修改模板文件,警告就消除了。
在1.10的模板中,使用url
需要一个标识符(identification,参考《#reverse-resolution-of-urls》),也即指定url
函数的name
参数。
url(r'accounts/', include([
url(r'^login/$', login, name='login'),
url(r'^logout/$', logout, name='logout'),
])),
上例中,把login
这个view,标识符为'login'
。
在模板文件中,使用{% url 'login' %}
即可得到计算后的URL。
<form method="post" action="{% url 'login' %}">
所有被模板引用的url
,都需要做这样的修改。
这就没办法用什么find
、sed
来处理了,只能纯手工操作。
不过好在有Vim的record/execute功能,几十处修改也就几分钟的事。
No named cycles in template ¶
No named cycles in template. ‘row2,row1’ is not defined
参考《stackoverflow.com/questions/40603961》与《#cycle》,可知应该把原先模板中的cycle
使用进行替换。
主要原因,见《Django 1.10 release notes》。
Support for the syntax of
{% cycle %}
that uses comma-separated arguments is removed.
比如:
<tr class="{% cycle row2,row1 %}">
就应该替换为:
<tr class="{% cycle 'row2' 'row1' %}">
这个也需要Vim进行批量处理。
1.10升级到1.11 ¶
pip uninstall -y Django
pip install 'Django<1.12'
执行python manage.py runserver
后,发现完全没有警告,直接可以运行!
然而,查看《Django 1.11 release notes》,发现是空欢喜一场。
执行python -Wd manage.py runserver
后,大量的RemovedInDjango20Warning如约而至。
Deprecating warnings are no longer loud by default ¶
Unlike older versions of Django, Django’s own deprecation warnings are no longer displayed by default. This is consistent with Python’s default behavior.
不过,还好1.11是LTS版本,维护周期比2.0还长。 升级到了终点。
最终的数据库汇总升级 ¶
开发用的数据库,是跟着一个个小版本一路升上来的。
而生产环境的数据库,直接执行python manage.py migrate
,可能会有问题。
django.db.utils.OperationalError: table “django_content_type” already exists
参考《stackoverflow.com/questions/29760817》,使用--fake-initial
参数可以解决问题。
python manage.py migrate --fake-initial
总结 ¶
呼呼……且让孤喘一会儿!
本质上,这是一个把废弃接口与用法,替换成新版本形式的一个过程。 通过这次升级历险记,孤对Django近年的发展,有了一个笼统的了解。