Django 1.7 自带migrations用法及源码

Django下一个版本是1.7,增加了类似South的migration功能,修改Model后可以在不影响现有数据的前提下重建表结构。这真是个千呼万唤始出来的feature了,所以做个简单的整理分享。文章包含部分源代码,对具体怎么实现不感兴趣可以忽略。

Prepare

从Django官网或直接pip下载1.7b版本,创建project和app:

$ pip install https://www.djangoproject.com/download/1.7b2/tarball/

$ python manage.py startproject dmyz

$ cd dmyz/

$ python manage.py startapp articles

修改articles/modules.py文件,增加Article,accounts到dmyz/settings.py文件的INSTALLED_APPS,以下是对这两个文件的修改:

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文件的操作)命令创建数据库。

makemigrations

首先要创建migrations,新版Django执行manager.py startapp会生成migrations/目录,makemigrations命令生成的文件会存到migrations/目录下。

$ python manage.py makemigrations articles

Migrations for ‘articles’:

0001_initial.py:

- Create model Article

$ ls articles/migrations/

__init__.py 0001_initial.py

创建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代码写到文件中。

MigrationWriter(Line 1)在writer.py文件中定义。Python代码用缩进来划分逻辑,下面这段代码用了三个方法(indent/unindent/feed),调用indent/unindent时给self.indentation增/减1,需要缩进时调用feed方法补上对应的空格实现缩进:

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:

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
)

$ python manage.py makemigrations articles

Migrations for ‘articles’:

0002_article_author.py:

- Add field author to article

自动检测新旧两个modle的差异是一个相当麻烦的工作,autodatector.py的代码比其他文件都长,但逻辑是很清晰的。主要是从上一个migration中获取之前的Model列表,写到set中,现有Model也是同样能够的操作,遍历这两个set的差集,获取差集Model中所有的field,如果field的定义相同,就询问用户是否是一个rename的model,否则视为创建。

autodatectory.py在测试的过程中raise了几个错误,代码量也不少,所以只附上源代码链接,不贴在原文里了:
https://raw.githubusercontent.com/django/django/stable/1.7.x/django/db/migrations/autodetector.py

migrate

之前的两次makemigrations操作只是生成migration文件,还没有对数据库进行操作,接下来执行migrate命令:

$ python manage.py migrate articles

Operations to perform:

Apply all migrations: articles

Running migrations:

Applying articles.0001_initial FAKED

Applying articles.0002_article_author OK

执行后数据库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
)
,

}
)

Afterword

这篇文章在草稿箱里存了半年(2013年11月)了,因为花了不少的时间看源码,以及等bug修复,现在的beta2版本修复了之前M2M字段的问题,但autodetector仍然有bug(已经提交到Trac)。

South常年居于最受欢迎的Django应用列表,说明依据Model修改关系数据库结构是开发中的一个重要的问题,解决这个问题可以提升开发速度。当然也只是[开发]速度,关系数据库经常用来存储Web业务的核心数据,是Web应用最常见的性能瓶颈,South这种用了好几年的模块也不敢在生产环境数据库上随便操作,更不用说现在还有Bug的自带migration了。

什么时候关系数据库也能完美的实现freeschema,开发就更美好了:)

时间: 2024-08-03 06:30:55

Django 1.7 自带migrations用法及源码的相关文章

仿快图系统自带图片浏览器应用源码项目

仿快图系统自带图片浏览器应用源码,最近在做一个微博i动态模块,需要查看他人相册照片或者微博内容图片等.看到QQ空间那个效果不错,尝试了不少方法来实现,均不是怎么理想.最初是想通过自定义GroupView和imageView来实现,结果在处理滑动事件和放大的图片拖曳不是很顺畅,自己也没深入解决,期望有高手实现了给分享下.后来看了网上的一些方法和帖子,尝试着拿别人的自定义包做一些修增自用.现在用gallery和imageVIew做的,感觉还不错.放上来最初的小demo,供大家参考分享. 源码下载:

Django的View(视图)、settings源码的解析、模板层

一.FBV与CBV 视图函数并不只是指函数,也可以是类 FBV:基于函数的视图,类似面向函数式编程 CBV:基于类的视图,类似面向对象编程 研究解析render源码: render:返回html页面:并且能够给该页面传值 分析:FBV视图原理 from django.shortcuts import render,HttpResponse # Create your views here. from django.template import Template,Context # FBV解析

手把手带你阅读Mybatis源码(三)缓存篇

前言 大家好,这一篇文章是MyBatis系列的最后一篇文章,前面两篇文章:手把手带你阅读Mybatis源码(一)构造篇 和 手把手带你阅读Mybatis源码(二)执行篇,主要说明了MyBatis是如何将我们的xml配置文件构建为其内部的Configuration对象和MappedStatement对象的,然后在第二篇我们说了构建完成后MyBatis是如何一步一步地执行我们的SQL语句并且对结果集进行封装的. 那么这篇作为MyBatis系列的最后一篇,自然是要来聊聊MyBatis中的一个不可忽视的

django 之csrf、auth模块及settings源码、插拔式设计

目录 基于django中间件拷贝思想 跨站请求伪造简介 跨站请求伪造解决思路 方式1:form表单发post请求解决方法 方式2:ajax发post请求解决方法 csrf相关的两个装饰器 csrf装饰器在CBV上的特例 django settings源码 auth模块简介 auth创建用户 auth扩展表 基于django settings配置文件实现插拔式设计 csrf:Cross Site Request Forgery protection 基于django中间件拷贝思想 # start.

Android中AsyncTask基本用法与源码分析(API 23)

原文链接 http://sparkyuan.github.io/2016/03/23/AsyncTask源码剖析(API 23)/ 转载请注明出处 Android的UI是线程不安全的,想在子线程中更新UI就必须使用Android的异步操作机制,直接在主线程中更新UI会导致程序崩溃. Android的异步操作主要有两种,AsyncTask和Handler.AsyncTask是一个轻量的异步类,简单.可控.本文主要结合API 23的源码讲解一下AsyncTask到底是什么. 基本用法 声明:Andr

Python的Django框架完成一个完整的论坛(源码以及思路)

一个完整的论坛,登录.注册.发表.头像.点赞.评论.分页.阅读排行等 使用Django2,Python3.5 开发工具:Pycharm5 需要的知识:Python基础知识,Django原理的理解以及使用,HTML,CSS,JS,jQuery等前端知识的简单理解,对数据库的简单理解 需要的知识可以参考的我的CSDN博客:http://blog.csdn.net/qq_41144008 源码 1:http://www.cnblogs.com/xuyiqing/p/8274959.html 2:htt

Django REST framework之解析器实例以及源码流程分析

解析器 我们都知道源生Django默认只能解析content-type:application/x-www-form-urlencoded头格式的,若果是json格式,form-data格式都要自己处理. 但是在restframework已经为我们做好了,只要配置上,就能帮我们解析请求的数据 举例说明: 表设计: 1 from django.db import models 2 3 4 class UserGroup(models.Model): 5 title = models.CharFie

手把手带你阅读Mybatis源码(二)执行篇

前言 上一篇文章提到了MyBatis是如何构建配置类的,也说了MyBatis在运行过程中主要分为两个阶段,第一是构建,第二就是执行,所以这篇文章会带大家来了解一下MyBatis是如何从构建完毕,到执行我们的第一条SQL语句的.之后这部分内容会归置到公众号菜单栏:连载中…-框架分析中,欢迎探讨! 入口(代理对象的生成) public static void main(String[] args) throws Exception { /******************************构

Android开发之Handler的用法(源码分享)

Handler主要接受子线程发送的数据, 并用此数据配合主线程更新UI.. 当应用程序启动时.Android首先会开启一个主线程 (也就是UI线程) , 主线程为管理界面中的UI控件,进行事件分发. 比方说, 你要是点击一个 Button ,Android会分发事件到Button上,来响应你的操作.  假设此时须要一个耗时的操作,比如: 联网读取数据或者读取本地较大的一个文件的时候,你不能把这些操作放在主线程中,,假设你放在主线程中的话,界面会出现假死现象, 假设5秒钟还没有完毕的话,.会收到A