Django下一個版本是1.7,增長了相似South的migration功能,修改Model後能夠在不影響現有數據的前提下重建表結構。這真是個千呼萬喚始出來的feature了,因此作個簡單的整理分享。文章包含部分源代碼,對具體怎麼實現不感興趣能夠忽略。 python
從Django官網或直接pip下載1.7b版本,建立project和app: mysql
修改articles/modules.py文件,增長Article,accounts到dmyz/settings.py文件的INSTALLED_APPS,如下是對這兩個文件的修改: git
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
# ===== articles/modules.py =====
# encoding: utf8
from
django
.
db
import
models
from
django
.
contrib
.
auth
.
models
import
User
class
Article
(
models
.
Model
)
:
title
=
models
.
CharField
(
max_length
=
18
,
null
=
True
)
# ===== dmyz/settings.py =====
INSTALLED_APPS
=
(
'django.contrib.admin'
,
'django.contrib.auth'
,
'django.contrib.contenttypes'
,
'django.contrib.sessions'
,
'django.contrib.messages'
,
'django.contrib.staticfiles'
,
'articles'
,
)
|
在dmyz/settings.py文件中調整數據庫設置。按照官方文檔的說明,支持得最好的是postgresql數據庫,其次是mysql,目前sqlite不能實現完整的migration功能。本文是在64位Window+Cgywin環境下寫的,使用的是mysql5.6版。設置完成後執行syncdb(不執行syncdb也不影響執行makemigrations建立migration文件的操做)命令建立數據庫。 github
首先要建立migrations,新版Django執行manager.py startapp會生成migrations/目錄,makemigrations命令生成的文件會存到migrations/目錄下。 sql
建立migrations/文件夾,寫入__init__.py文件和migration文件使用的是如下代碼: 數據庫
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
# django/core/management/commands/makemigrations.py
writer
=
MigrationWriter
(
migration
)
if
self
.
verbosity
>=
1
:
self
.
stdout
.
write
(
" %s:\n"
%
(
self
.
style
.
MIGRATE_LABEL
(
writer
.
filename
)
,
)
)
for
operation
in
migration
.
operations
:
self
.
stdout
.
write
(
" - %s\n"
%
operation
.
describe
(
)
)
# 若是增長 --dry-run參數就不寫入migration文件,只顯示描述結果
if
not
self
.
dry_run
:
migrations_directory
=
os.path
.
dirname
(
writer
.
path
)
if
not
directory_created
.
get
(
app_label
,
False
)
:
if
not
os.path
.
isdir
(
migrations_directory
)
:
os
.
mkdir
(
migrations_directory
)
init_path
=
os.path
.
join
(
migrations_directory
,
"__init__.py"
)
if
not
os.path
.
isfile
(
init_path
)
:
open
(
init_path
,
"w"
)
.
close
(
)
# We just do this once per app
directory_created
[
app_label
]
=
True
migration_string
=
writer
.
as_string
(
)
with
open
(
writer
.
path
,
"wb"
)
as
fh
:
fh
.
write
(
migration_string
)
|
檢測app目錄下是否存在migrations/目錄,不存在就新建,再以write的方式操做__init__.py文件,最後把生成的migration代碼寫到文件中。 django
MigrationWriter(Line 1)在writer.py文件中定義。Python代碼用縮進來劃分邏輯,下面這段代碼用了三個方法(indent/unindent/feed),調用indent/unindent時給self.indentation增/減1,須要縮進時調用feed方法補上對應的空格實現縮進: session
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
# django/db/migrations/writer.py
imports
=
set
(
)
for
arg_name
in
argspec
.
args
[
1
:
]
:
arg_value
=
normalized_kwargs
[
arg_name
]
if
(
arg_name
in
self
.
operation
.
serialization_expand_args
and
isinstance
(
arg_value
,
(
list
,
tuple
,
dict
)
)
)
:
if
isinstance
(
arg_value
,
dict
)
:
self
.
feed
(
'%s={'
%
arg_name
)
self
.
indent
(
)
for
key
,
value
in
arg_value
.
items
(
)
:
arg_string
,
arg_imports
=
MigrationWriter
.
serialize
(
value
)
self
.
feed
(
'%s: %s,'
%
(
repr
(
key
)
,
arg_string
)
)
imports
.
update
(
arg_imports
)
self
.
unindent
(
)
self
.
feed
(
'},'
)
else
:
self
.
feed
(
'%s=['
%
arg_name
)
self
.
indent
(
)
for
item
in
arg_value
:
arg_string
,
arg_imports
=
MigrationWriter
.
serialize
(
item
)
self
.
feed
(
'%s,'
%
arg_string
)
imports
.
update
(
arg_imports
)
self
.
unindent
(
)
self
.
feed
(
'],'
)
else
:
arg_string
,
arg_imports
=
MigrationWriter
.
serialize
(
arg_value
)
self
.
feed
(
'%s=%s,'
%
(
arg_name
,
arg_string
)
)
imports
.
update
(
arg_imports
)
self
.
unindent
(
)
self
.
feed
(
'),'
)
return
self
.
render
(
)
,
imports
def
indent
(
self
)
:
self
.
indentation
+=
1
def
unindent
(
self
)
:
self
.
indentation
-=
1
def
feed
(
self
,
line
)
:
self
.
buff
.
append
(
' '
*
(
self
.
indentation
*
4
)
+
line
)
def
render
(
self
)
:
return
'\n'
.
join
(
self
.
buff
)
|
接下來修改articles/models.py,增長一個field,再次執行makemigrations: app
1
2
3
4
|
# articles/modules.py
class
Article
(
models
.
Model
)
:
title
=
models
.
CharField
(
max_length
=
18
,
null
=
True
)
author
=
models
.
OneToOneField
(
User
,
null
=
True
)
|
自動檢測新舊兩個modle的差別是一個至關麻煩的工做,autodatector.py的代碼比其餘文件都長,但邏輯是很清晰的。主要是從上一個migration中獲取以前的Model列表,寫到set中,現有Model也是一樣可以的操做,遍歷這兩個set的差集,獲取差集Model中全部的field,若是field的定義相同,就詢問用戶是不是一個rename的model,不然視爲建立。 post
autodatectory.py在測試的過程當中raise了幾個錯誤,代碼量也很多,因此只附上源代碼連接,不貼在原文裏了:
https://raw.githubusercontent.com/django/django/stable/1.7.x/django/db/migrations/autodetector.py
以前的兩次makemigrations操做只是生成migration文件,尚未對數據庫進行操做,接下來執行migrate命令:
執行後數據庫articles_article這張表會增長author_id字段,執行過的migration文件會記錄到django_migrations表中,避免重複執行。帶--fake參數執行migrate命令時,只將migration文件記錄到數據庫的django_migrations表,若是是用South的migration文件,fake操做就很關鍵了。
這是migration操做中處理數據庫的部分,主要代碼都在django/db/migrations/operations/目錄下,拆分紅4個文件:base.py fields.py models.py special.py,和文件名錶達的含義同樣,文件中是針對Model/Field作Create/Rename/Delete的操做,調用這些文件是從djangp/db/migrations/migration.py文件開始的:
1
2
3
4
5
6
|
for
operation
in
self
.
operations
:
new_state
=
project_state
.
clone
(
)
operation
.
state_forwards
(
self
.
app_label
,
new_state
)
operation
.
database_forwards
(
self
.
app_label
,
schema_editor
,
project_state
,
new_state
)
project_state
=
new_state
return
project_state
|
在Line 4調用了database_forwards方法,傳入的第一個參數是app名稱,最後兩個參數原state和新的state,裏面包含全部字段的定義。schema_editor是根據數據庫指定的DatabaseSchemaEditor類,
以後的操做就是各類調用了:調用opration的方法,oprations調用根據具體的操做(add/alter/remove)調用db/backends/數據庫類型/schema.py的方法,真正實現對數據庫的操做,主要代碼以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
def
database_forwards
(
self
,
app_label
,
schema_editor
,
from_state
,
to_state
)
:
old_apps
=
from_state
.
render
(
)
new_apps
=
to_state
.
render
(
)
old_model
=
old_apps
.
get_model
(
app_label
,
self
.
old_name
)
new_model
=
new_apps
.
get_model
(
app_label
,
self
.
new_name
)
if
router
.
allow_migrate
(
schema_editor
.
connection
.
alias
,
new_model
)
:
schema_editor
.
alter_db_table
(
new_model
,
old_model
.
_meta
.
db_table
,
new_model
.
_meta
.
db_table
,
)
def
alter_db_table
(
self
,
model
,
old_db_table
,
new_db_table
)
:
self
.
execute
(
self
.
sql_rename_table
%
{
"old_table"
:
self
.
quote_name
(
old_db_table
)
,
"new_table"
:
self
.
quote_name
(
new_db_table
)
,
}
)
|
這篇文章在草稿箱裏存了半年(2013年11月)了,由於花了很多的時間看源碼,以及等bug修復,如今的beta2版本修復了以前M2M字段的問題,但autodetector仍然有bug(已經提交到Trac)。
South常年居於最受歡迎的Django應用列表,說明依據Model修改關係數據庫結構是開發中的一個重要的問題,解決這個問題能夠提高開發速度。固然也只是[開發]速度,關係數據庫常常用來存儲Web業務的核心數據,是Web應用最多見的性能瓶頸,South這種用了好幾年的模塊也不敢在生產環境數據庫上隨便操做,更不用說如今還有Bug的自帶migration了。
何時關係數據庫也能完美的實現freeschema,開發就更美好了:)